Ubuntu Port Connectivity - ss, lsof, nc, curl Troubleshooting Guide

Ubuntu Port Connectivity - ss, lsof, nc, curl Troubleshooting Guide

What You'll Learn

  • How to isolate "can't connect" issues to network / server / application layer
  • Avoid common mistakes like "assuming it's ufw" or "only checking if service is running"
  • Master the command set (ss / lsof / nc / curl) for quick diagnosis

Quick Summary

When "can't connect", check in this order:

  1. DNS correct? (if needed)
  2. Can reach the port? nc -vz HOST PORT
  3. Server listening? ss -lntp | grep :PORT
  4. What process? lsof -i :PORT
  5. HTTP response? curl -I http(s)://HOST
  6. Service and logs systemctl status / journalctl

Prerequisites

  • OS: Ubuntu (server side)
  • Target: Server beginners
  • Focus here is on "investigation and isolation" (fixes link to other articles)

1. First, Clarify What's Not Connecting

Conclusion: Establish HOST, PORT, and protocol first — without them, diagnosis cannot start.

Establish these 3 things first:

  • HOST: Domain or IP
  • PORT: 22 / 80 / 443 / 3000 / 8080 / 3306 etc.
  • Protocol: SSH? HTTP? Database?

Examples:

  • SSH not connecting -> HOST=example.com PORT=22 (or 2222)
  • Web not loading -> PORT=80/443
  • API dead -> PORT=3000/8080

If this is unclear, you'll be lost forever.

2. Client Side: Can You Reach It? (nc)

Conclusion: Run nc -vz HOST PORT first — three outcomes each point to a different layer.

Key point: Test reachability from client BEFORE touching the server.

2-1. nc for Connectivity Check (TCP)

$ nc -vz example.com 22

2-2. Different Port (e.g., 2222)

$ nc -vz example.com 2222

2-3. Reading the Results (Crucial)

  • succeeded -> Network path works (now check server/app)
  • timed out -> Can't reach (FW/SG/route/DNS/server down)
  • refused -> Reached but nothing listening (service stopped/wrong port/wrong bind)

timed out can be ufw, but cloud FW (SG etc.) causes the same symptom. Fixing only ufw often doesn't help, so check server-side listening in the next step.

3. Server Side: Is It Listening? (ss)

Conclusion: ss -lntp | grep :PORT — check whether the bind is 127.0.0.1, not 0.0.0.0.

When nc refused, this tells you definitively.

3-1. Show All Listening Sockets

$ sudo ss -lntp
  • -l: Listening sockets
  • -n: Numeric (no DNS lookup)
  • -t: TCP
  • -p: Show process (needs sudo)

3-2. Filter Specific Port (e.g., 80)

$ sudo ss -lntp | grep ':80 '

3-3. Common Trap: 127.0.0.1 Only

If you see:

LISTEN 0 4096 127.0.0.1:3000 ...

This only accepts localhost connections. For external access, it needs to bind to 0.0.0.0:3000 (app config side).

4. Who's Holding the Port? (lsof)

Conclusion: lsof -i :PORT finds the owner — an unexpected process means a port conflict.

ss shows this too, but lsof output is more human-readable.

4-1. Show Process Holding Port 80

$ sudo lsof -i :80

4-2. Port 3000 Example

$ sudo lsof -i :3000

If an unexpected process is holding the port, another service is conflicting.

5. HTTP: Check Response (curl)

Conclusion: Use curl -I for HTTP status — a reachable port can still return 500/502/503.

For web/API, "port open but 500 error" is common.

5-1. Headers Only

$ curl -I http://example.com
$ curl -I https://example.com

5-2. Status Code Meanings

  • 200: OK
  • 301/302: Redirect (check if intentional)
  • 403: Forbidden (WAF/Basic auth/IP restriction)
  • 404: Not found (routing/path)
  • 500: Internal error (check logs)
  • 502/503/504: Upstream/backend problem

6. DNS Troubleshooting (When HOST Is a Domain)

Conclusion: If IP works but domain fails, it is DNS, CDN, or certificate — check with dig.

"Domain only doesn't connect" is often DNS, cert, or redirect issues.

6-1. Check What IP the Domain Resolves To

$ dig example.com +short

6-2. Test Directly with IP (Bypass DNS)

$ nc -vz 203.0.113.10 80
$ curl -I http://203.0.113.10

IP works but domain doesn't -> DNS (or CDN/WAF) is likely the issue

7. Service Check (systemctl / journalctl)

Conclusion: When nc succeeds but the app fails, check service logs with journalctl.

When nc succeeded but app isn't responding or returning errors:

7-1. Service Status

$ sudo systemctl status nginx
$ sudo systemctl status apache2
$ sudo systemctl status ssh

7-2. Logs (Fastest)

$ sudo journalctl -u nginx -n 200
$ sudo journalctl -u ssh -n 200

Don't stop at "it's running". Services can crash immediately after start or be in restart loops - check logs.

8. Error Messages Explained

Conclusion: Timed out means blocked route; refused means nothing listening; SSL means cert.

8-1. nc: connect ... timed out

Causes: ufw, cloud FW (SG), corporate FW, routing, server down

8-2. nc: connect ... refused

Causes: Nothing listening, wrong port, process down

8-3. curl: (7) Failed to connect

Causes: Can't reach (timeout) or nothing listening

8-4. curl: (60) SSL certificate problem

Causes: Certificate/hostname/intermediate cert issue

8-5. curl -I returns 502 Bad Gateway

Causes: nginx/apache -> upstream (app) is dead or wrong port

9. Things to Avoid

Conclusion: Never disable the firewall first — use nc and ss to isolate the problem.

Don't: Disable FW Immediately to Work Around It

ufw disable is a last resort. Use nc and ss to determine the problem layer first.

Don't: Assume Port 22 Without Checking

If it's actually listening on 2222, "allowed 22" wastes time.

Don't: Skip Checking Server-Side Listening (ss) and Blame the App

If nothing is listening, the problem is before the app. Follow the isolation order.

Copy-Paste Template

# Client side (reachability)
nc -vz example.com PORT

# Server side (listening check)
sudo ss -lntp | grep ":PORT "

# Server side (process holding port)
sudo lsof -i :PORT

# HTTP response check
curl -I http://example.com
curl -I https://example.com

# Service/logs
sudo systemctl status <service>
sudo journalctl -u <service> -n 200

Summary

  • Use nc to first confirm reachable/not reachable
  • Use ss to confirm listening/not listening
  • Use curl to check if app is responding correctly
  • When stuck, return to the difference between timed out / refused / succeeded

Next Reading