LPIC-2 Advanced Network Troubleshooting: ss, tcpdump, nc, and Route Diagnosis
- Work a connectivity failure bottom-up — link, address, route, DNS, service — instead of guessing
- Confirm a usable default route with ip route get before you ever suspect DNS
- Map each diagnostic question to the right tool: ss, nc, tcpdump, dig/getent
- Read nc's two failure modes correctly: CONNECTION REFUSED (service down) versus TIMEOUT (firewall drop)
- Use -n everywhere so reverse-DNS never adds latency or hangs your diagnosis
Why a method beats a guess
Adopt the bottom-up layered method so you test causes in dependency order, not at random.
"The site is down" is not a diagnosis — it's a symptom with a dozen possible causes stacked on top of each other. The trap is to grab whatever tool you remember first (usually
pingordig) and start poking. If you guess wrong about which layer failed, every test after that is noise.The fix is a method. Networking is a stack, and each layer depends on the one beneath it: a name can't resolve over a connection that has no route; a route is useless if the address layer never came up; the address layer needs a working link. So you test in that order, bottom-up:
- Link — is the interface up and carrying signal?
- Address — does the interface have an IP?
- Route — is there a path to the destination, including a default route?
- DNS — does the name resolve to an address?
- Service — is the thing on the other end actually listening?
Think of it like an electrician tracing a dead outlet: you don't start by replacing the lamp. You check the breaker, then the wiring, then the socket, then the bulb — upstream before downstream. Each confirmed-good layer eliminates a whole class of causes and tells you the fault is higher up.
Confirm the route before you blame DNS
Prove a default route exists and is chosen for your destination — the layer everyone skips.
Here's the most common misdiagnosis in the field: a connection fails, the engineer runs
dig, sees it hang, and declares "DNS is broken." Butdighas to reach a resolver over the network — and if there's no route off the box, the DNS query never leaves either. The real fault is one layer down. Always confirm the route before you suspect DNS.Don't just look at the routing table and eyeball it — ask the kernel what it would actually do for your specific destination.
ip route getruns the real route-selection logic and tells you the exact path a packet would take.ip route get 192.0.2.1192.0.2.1 via 10.0.2.2 dev eth0 src 10.0.2.15 uid 0 cache- route get <dest>
- Run the kernel's real route lookup for one destination — not a table dump, the actual decision.
This is the healthy answer. The kernel says: to reach 192.0.2.1, sendvia 10.0.2.2(the next-hop gateway) outdev eth0, using source address10.0.2.15. A next-hop gateway plus an egress interface means the route layer is good — the fault is higher up, so now DNS or the service is a fair suspect.ip route get 192.0.2.1RTNETLINK answers: Network is unreachable
This is the broken answer, and it's decisive. Network is unreachablemeans there is no route to that destination — almost always a missing default route. Runningdignow would also hang, and you'd wrongly blame DNS. Confirm withip route(below): if there's no line startingdefault, that's your root cause.ip routedefault via 10.0.2.2 dev eth0 proto dhcp metric 100 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
The full table. The line that matters is `default via 10.0.2.2` — the catch-all route for any destination not on a directly-connected subnet (here, the 10.0.2.0/24line). No `default` line = no internet, no off-subnet DNS, nothing. That single missing line explains an enormous share of "DNS is down" reports.Check yourself
A connection fails and
dig example.comhangs with no answer. Before touching DNS, which command tells you whether a packet can even leave the box toward the destination?Who's listening? ss -tlnp
Confirm the service is bound and listening on the expected port and address.
Once you know packets can reach the host, the next question is about the far end: is there actually a service listening on the port you're trying to hit? Run this on the server itself.
ss(the modern replacement fornetstat) lists sockets, and the right flags turn it into a precise "who is listening, where, and as what process" report.sudo ss -tlnpState Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=842,fd=5)) LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1001,fd=6)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=712,fd=3))- -t
- TCP sockets only — where almost all services you'll diagnose live.
- -l
- Listening sockets only: things waiting for an inbound connection.
- -n
- Numeric ports and addresses — show :80, not "http". Skips reverse-DNS so the command never hangs on a name lookup.
- -p
- Show the owning process (name + PID). Needs root to see other users' sockets.
Read the Local Address:Port column closely. nginx and sshd listen on 0.0.0.0— every interface, so they're reachable from the network. But postgres listens on `127.0.0.1:5432` — loopback only. A remote client will never connect to it no matter how perfect the route is, because the service isn't bound to a reachable address. That bind-address detail is the kind of faultssexposes in one line.Check yourself
ss -tlnpshows your databaseLISTENing on127.0.0.1:5432, yet remote clients can't connect. Is the listening process down?Is the port open? nc — and the RST-vs-timeout signal
Probe the port from the client side and decode nc's two failure modes correctly.
Now test from the client's perspective.
nc(netcat) makes a single TCP connection attempt and reports what happened. This is the highest-value diagnostic in the whole lesson, because how it fails tells you exactly which layer to fix. First, success:nc -vz 192.0.2.10 80Connection to 192.0.2.10 80 port [tcp/*] succeeded!
- -v
- Verbose — print whether the connection succeeded or how it failed (you need this to read the result).
- -z
- Zero-I/O "scan" mode: just test if the port accepts a connection, then close it — send no data.
succeeded!means the full TCP handshake completed — route, firewall, and a listening service are all good. If this passes but the app still misbehaves, your problem is at the application layer, not the network.The two failures look superficially similar ("it didn't connect") but have opposite causes. This distinction is the heart of advanced network troubleshooting.
nc -vz 192.0.2.10 80nc: connect to 192.0.2.10 port 80 (tcp) failed: Connection refused
Connection refused comes back fast. It means the host replied with a TCP RST (reset): "I received your packet, but nothing is listening on that port." The packet reached the host — so the route and firewall are fine. The fault is the service: it's stopped, crashed, or (as the previous step showed) bound to the wrong address. Go fix the daemon, not the firewall. nc -vz 192.0.2.10 80nc: connect to 192.0.2.10 port 80 (tcp) failed: Connection timed out
Connection timed out hangs for seconds and then gives up. It means no reply came back at all — not even a polite refusal. Something is silently dropping your packet, classically a firewall with a DROP rule (a REJECT rule would have sent the RST you saw above). The route may be fine and the service may be running; a packet filter in the middle is eating the SYN. Go look at firewall rules — iptables/nftableson the host or a network ACL in the path.Check yourself
nc -vzto a port returns "Connection refused" almost instantly. Is a firewall silently dropping your packets, or is the service down?Is the packet even arriving? tcpdump -ni
Confirm on the wire whether the SYN reaches the host and whether anything replies.
When
nctimes out,tcpdumpsettles the argument by showing the actual packets on the interface. It's the ground truth: a CCTV camera on the network door. Run it on the server and watch what arrives while you probe from the client.The critical habit: always pass `-n`. Without it,
tcpdumptries to reverse-DNS every address it sees — which adds latency and, when DNS itself is the problem you're debugging, can maketcpdumphang before it prints a single packet.-nturns that off and shows raw IPs immediately.sudo tcpdump -ni eth0 'tcp port 80'listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 11:42:07.013 IP 192.0.2.50.51884 > 192.0.2.10.80: Flags [S], seq 0, win 64240, length 0 11:42:08.015 IP 192.0.2.50.51884 > 192.0.2.10.80: Flags [S], seq 0, win 64240, length 0 11:42:10.019 IP 192.0.2.50.51884 > 192.0.2.10.80: Flags [S], seq 0, win 64240, length 0
- -n
- Don't resolve addresses or ports to names — show raw IPs. Avoids latency and the hang that reverse-DNS causes when DNS is the very thing that's broken.
- -i eth0
- Capture on this specific interface — match it to the egress device ip route get reported.
- 'tcp port 80'
- A BPF filter: capture only TCP traffic on port 80 so the output isn't drowned in noise.
This is the firewall-drop signature. The client at 192.0.2.50sends a SYN (Flags [S]) — and you see it arrive at the host. But there's no `Flags [S.]` reply (SYN-ACK), and the client just retransmits the same SYN at 1s, 2s, 4s intervals. The packet got in but the host never answered: confirmation that something is dropping it (a DROP firewall rule), which is exactly thenctimeout case proven on the wire.Check yourself
Why must you pass -n to tcpdump (and ss, and traceroute) when DNS might be the problem you're debugging?
Does the name resolve? dig and getent
Separate a true name-resolution failure from the route/service faults already ruled out.
Only now — route confirmed, service and firewall understood — is DNS a fair suspect. Two tools answer two different questions, and conflating them hides bugs:
digasks a DNS server directly, bypassing the local config.getent hostsresolves the way your applications actually do — through/etc/nsswitch.conf, which means it honors/etc/hostsand the configured resolver order.
dig +short example.com192.0.2.10
- +short
- Print only the answer (the IP), suppressing the verbose query/header sections.
An IP comes back, so DNS resolution works at the server level. An empty result instead would mean the name didn't resolve — but read that carefully against the route step: if ip route getalready showedNetwork is unreachable, an emptydigis a symptom of the routing fault, not a DNS fault. Layer order keeps you honest.getent hosts example.com192.0.2.10 example.com
getentresolves through the name service switch — the exact path your apps use. The lesson is whendigandgetentdisagree: ifdigreturns the public IP butgetentreturns a different address (or nothing), the difference lives in `/etc/hosts` or `/etc/nsswitch.conf`, not in DNS.digtests the server;getenttests what your software will actually get.Try it in your live terminal
Walk the bottom-up method yourself with safe, read-only commands on the real sandbox.
You have a real root shell to your right. Every command below is read-only — it inspects, changes nothing. Run them in bottom-up order and narrate the answer to yourself at each layer: route → listening → name resolution. That sequence is the muscle memory this lesson is building.
ip routeLayer: route. Look for a line starting default via …. If it's missing, stop — nothing off-subnet will work, and any "DNS" symptom is really this.ip route get 192.0.2.1Layer: route (decisive). Ask the kernel the real path to a destination. A via … dev …answer means routing is healthy;Network is unreachablemeans it isn't — that's your root cause, not DNS.ss -tlnpLayer: service. List every listening TCP socket and its owning process. Check each bind address — 0.0.0.0is remote-reachable,127.0.0.1is local-only. Note the-nso it never stalls on a name lookup.getent hosts localhost; echo "--- example.com ---"; getent hosts example.com; echo "getent exit code: $?"Layer: DNS, the app's-eye view. getent resolves through nsswitch exactly as your software would. localhostanswers from/etc/hosts. example.com prints nothing and exits 2 (not found), because lab boxes have no internet access and its lookup has to go to DNS. Now comparedig example.com +time=2 +tries=1, which asks the nameserver directly and shows the failure in itsstatus:line. When getent and dig disagree, look at/etc/hostsand/etc/nsswitch.conf, not the nameserver.
- Troubleshoot bottom-up — link, address, route, DNS, service — so each confirmed layer eliminates a whole class of causes.
- Confirm the route first. A hung
digis a cue to runip route get <dest>, not to restart DNS — a missing default route mimics a DNS outage perfectly. - `nc` refused (RST) = service down (host is reachable); `nc` timeout (no reply) = firewall silently dropping the packet. The two failures have opposite fixes.
- `tcpdump -ni` is ground truth: SYN-arrives-no-reply confirms a drop; a returned RST confirms refusal; no SYN at all means the problem is upstream.
- Pass `-n` everywhere (tcpdump, ss, traceroute). Reverse-DNS adds latency and will hang your tools when DNS is the very thing that's broken.
| Symptom you see | Tool / command | What it actually means |
|---|---|---|
dig hangs, connection fails | ip route get <dest> | Likely no default route — fix routing, not DNS |
No default via line | ip route | No path off the subnet; off-subnet DNS and internet are dead |
| Service unreachable remotely | ss -tlnp | Check bind: 0.0.0.0 = remote-OK, 127.0.0.1 = local-only bug |
nc says Connection refused (fast) | nc -vz host port | TCP RST — host reachable, service is down |
nc says Connection timed out (hangs) | nc -vz host port | No reply — firewall is dropping the packet |
| SYN arrives, no SYN-ACK back | tcpdump -ni <if> | Packet drop confirmed on the wire (DROP rule) |
dig works but the app can't resolve | getent hosts <name> | Difference is in /etc/hosts or nsswitch.conf, not DNS |
