Skip to content

Phase 1 · FOUNDATIONS

Networking 102 — ss, dig, ping, a troubleshooting flow

Day 12 of 90 ~50 min 0/20 in phase Builds on Day 11

By the end of today

  • List listening ports and their owning processes with ss -tlnp
  • Resolve any name with dig +short and read its records
  • Isolate a fault fast with the DNS → network → port → app flow

Is it DNS, the network, the port, or the app?

Section 1 of 5 · ~2 min

When someone says “the site is down,” the amateur move is to guess — restart the server, clear a cache, hope. The professional move is to walk four layers in order, cheapest first, and let each one rule out a whole class of problem. Almost every reachability failure lives at exactly one of these layers, and each has a single tool that answers it:

  1. Does the name resolve?dig — turns example.com into an IP. No answer means DNS.
  2. Can I reach the host at all?ping (and traceroute to see where it dies) — proves the machine is up and routable.
  3. Is anything listening on the port?ss — shows which sockets are bound and which process owns them.
  4. Does the app actually answer?curl (from Day 11) — you have DNS, a route, and an open port; now read the HTTP status.
The four-layer troubleshooting flow: dig checks DNS, ping and traceroute check the network, ss checks the port, and curl checks the app — walked top to bottom. 1. DNS — does it resolve? dig +short 2. Network — is the host up? ping / traceroute 3. Port — is it listening? ss -tlnp 4. App — does it answer? curl -I
Walk it top to bottom — the layer that fails is the layer to fix.

Two of these tools are worth knowing cold. ss (socket statistics) replaced the old netstat; the flags stack: -t TCP, -u UDP, -l listening only, -n numeric (skip slow name lookups), -p show the owning process. sudo ss -tlnp — “what TCP is listening, and who owns it” — is one of the most-typed commands in ops. dig (domain information groper) queries DNS: dig +short name prints just the answer, plain dig name shows the full response with a status: line, and dig @resolver name asks a specific resolver instead of your configured one.

Real world: Troubleshooting is a phone call in four steps. Do you have the right number (DNS)? Does the line even connect (network)? Does someone pick up at that extension (port)? And once they answer, do they speak your language (app)? A busy signal, a dead line, and a wrong extension are three different problems — and you would never fix one by retrying the other.

That @resolver trick leans on a named example: Cloudflare’s public resolver at 1.1.1.1 (which answers to the name one.one.one.one). When your own lookups fail, dig @1.1.1.1 example.com asks Cloudflare directly — if it answers but your box does not, the domain is fine and your resolver is broken. That single test cleanly splits “the record is wrong” from “my machine’s DNS is misconfigured,” and it is the backbone of the DNS Detective mission on Day 14.

Hands-On Lab

Section 2 of 5 · ~4 min

Budget about 25 minutes. Open your WSL2 Ubuntu 24.04 terminal. You will stand up a tiny test server, inspect it with ss, then walk the DNS and network layers with dig and ping. Type each command and read every line of output. Note: the 127.0.0.53/.54 stub lines below appear only when systemd-resolved is your active resolver; a default WSL2 box may show no :53 listener at all and a WSL NAT nameserver in /etc/resolv.conf — so key on the deterministic python3 listener on :8000 that you start in step 2.

# 1. What TCP sockets are listening right now, and who owns them? (-p needs sudo)
sudo ss -tlnp
# Output (a fresh systemd-enabled WSL2 box — systemd-resolved owns the DNS stub):
# State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
# LISTEN  0       4096    127.0.0.53%lo:53    0.0.0.0:*          users:(("systemd-resolve",pid=142,fd=14))
# LISTEN  0       4096    127.0.0.54:53       0.0.0.0:*          users:(("systemd-resolve",pid=142,fd=12))
# 2. Stand up something to inspect: a throwaway web server on port 8000, in the background.
python3 -m http.server 8000 &
# Output:
# [1] 3517
# Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
# 3. Now find OUR listener. 0.0.0.0:8000 means "all interfaces" — reachable from outside, not just localhost.
sudo ss -tlnp | grep 8000
# Output:
# LISTEN  0  5  0.0.0.0:8000  0.0.0.0:*  users:(("python3",pid=3517,fd=3))
# 4. Add UDP with -u. systemd-resolved also listens on UDP :53 — DNS uses UDP first, TCP as fallback.
sudo ss -tulnp | grep 127.0.0.53
# Output:
# udp  UNCONN  0  0  127.0.0.53%lo:53  0.0.0.0:*  users:(("systemd-resolve",pid=142,fd=13))
# tcp  LISTEN  0  4096  127.0.0.53%lo:53  0.0.0.0:*  users:(("systemd-resolve",pid=142,fd=14))
# 5. dig is not on a minimal noble install — get it (dnsutils pulls bind9-dnsutils on 24.04).
sudo apt install -y dnsutils
# Output (trimmed):
# The following NEW packages will be installed:
#   bind9-dnsutils bind9-host bind9-libs
# ...
# Setting up bind9-dnsutils (1:9.18.30-0ubuntu0.24.04.2) ...
# 6. Just the answer: dig +short prints the resolved address and nothing else. Great for scripts.
dig +short one.one.one.one
# Output (Cloudflare's resolver answers to its own name — this pair is stable):
# 1.0.0.1
# 1.1.1.1
# 7. The full picture. Read status: NOERROR (the lookup worked) and the ANSWER SECTION.
dig one.one.one.one
# Output (trimmed to the parts that matter):
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24601
# ;; ANSWER SECTION:
# one.one.one.one.  300  IN  A  1.1.1.1
# one.one.one.one.  300  IN  A  1.0.0.1
# ;; Query time: 12 msec
# ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
# 8. Ask Cloudflare's resolver DIRECTLY with @1.1.1.1 — bypasses your local resolver entirely.
dig +short @1.1.1.1 example.com
# Output (this IP can change — DNS records are edited by their owners):
# 23.192.228.80
# 23.192.228.84
# 23.215.0.136
# ...
# 9. Query a specific record TYPE. NS = the nameservers authoritative for the zone.
dig +short NS example.com
# Output (IANA-managed, so very stable):
# a.iana-servers.net.
# b.iana-servers.net.
# 10. Network layer, DNS skipped: ping an IP directly. If this works, routing is fine.
ping -c 3 1.1.1.1
# Output (latency varies by connection):
# PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
# 64 bytes from 1.1.1.1: icmp_seq=1 ttl=57 time=8.42 ms
# 64 bytes from 1.1.1.1: icmp_seq=2 ttl=57 time=9.01 ms
# 64 bytes from 1.1.1.1: icmp_seq=3 ttl=57 time=8.77 ms
# --- 1.1.1.1 ping statistics ---
# 3 packets transmitted, 3 received, 0% packet loss, time 2003ms
# 11. Where does the path to a host go? traceroute maps the hops (install it first if missing).
sudo apt install -y traceroute && traceroute -m 8 1.1.1.1
# Output (intermediate hops and latencies vary; * means a hop that didn't reply):
# traceroute to 1.1.1.1 (1.1.1.1), 8 hops max, 60 byte packets
#  1  172.20.0.1 (172.20.0.1)  0.51 ms  0.42 ms  0.39 ms
#  2  * * *
#  3  1.1.1.1 (1.1.1.1)  9.10 ms  8.88 ms  8.95 ms
# 12. Stop the test server — %1 is the background job from step 2.
kill %1
# Output:
# [1]+  Terminated  python3 -m http.server 8000

Read it back before you close the terminal: you found what was listening (ss), resolved names and pointed at a specific resolver (dig), then proved the network path with ping and traceroute — the exact top-to-bottom flow you will run on every “it’s down” page for the rest of your career.

Common Errors & Fixes

Section 3 of 5 · ~2 min

These three are the ones newcomers hit in their first week of network debugging on Ubuntu 24.04. Read each error line slowly — parsing it is the diagnosis.

Common error: Reaching for dig on a fresh noble box before installing it.

Command 'dig' not found, but can be installed with:
sudo apt install bind9-dnsutils

Why: Ubuntu 24.04’s minimal images do not ship dig; it lives in the bind9-dnsutils package (the old dnsutils name is now a transitional wrapper for it). The shell’s command-not-found helper recognises the binary and tells you exactly which package provides it.

Fix: Install it: sudo apt install -y dnsutils (or bind9-dnsutils directly). nslookup ships in the same package if you prefer it. Confirm with dig -v.

How you’d spot it in prod: command not found in a CI job or container almost always means the tool is not installed in that image — the fix is an install line in the Dockerfile or pipeline, not on your laptop where it already exists.

Common error: Pinging by name when DNS is broken, instead of pinging the IP first.

ping: example.com: Temporary failure in name resolution

Why: ping asked the resolver to turn example.com into an IP and got nothing back, so it never sent a single packet. This is a name-resolution failure, not a network one — the host might be perfectly reachable by IP.

Fix: Split the layers. ping 1.1.1.1 (a raw IP) proves the network works; if that succeeds but the name fails, the problem is DNS. Then check /etc/resolv.conf and confirm with dig @1.1.1.1 example.com.

How you’d spot it in prod: This exact message in an app log means the service can route packets but cannot resolve names — usually a bad resolv.conf, an unreachable resolver, or a missing DNS entry, never the remote host being down.

Common error: Assuming a service is up because the host pings, then hitting a dead port.

curl: (7) Failed to connect to 127.0.0.1 port 9000 after 0 ms: Connection refused

Why: “Connection refused” means the packet reached the machine but nothing was listening on that port — the OS actively rejected it. The host is fine; the service is not bound where you expected (wrong port, crashed, or listening on 127.0.0.1 only).

Fix: Confirm what is actually listening: sudo ss -tlnp | grep :9000. Empty result means start the service or fix the port it binds to. Refused is a fast, honest answer — contrast it with a timeout, which usually means a firewall is silently dropping packets.

How you’d spot it in prod: “Connection refused” points at the app or its bind address; a hanging “timed out” points at a firewall or security group. The two symptoms send you to two completely different places, so read the exact wording before acting.

Network Troubleshooting Interview Questions

Section 4 of 5 · ~1 min

Cover the answers below and say your own version out loud first — especially the four-layer flow, which interviewers love to make you walk end to end. Recalling before revealing is what makes it stick. The four questions and answers render right after this note.

Go Deeper

Section 5 of 5 · ~1 min

Optional extras if you have ~30 more minutes today:

  • 5 min — Run man ss and read what -t, -u, -l, -n and -p each do; then try ss -tan to see established connections, not just listeners.
  • 10 min — Run dig +trace example.com and watch resolution walk from the root servers down to the authoritative nameserver — the whole DNS hierarchy in one command.
  • 10 min — Read the Ports and Sockets and DNS sections of the Networking for DevOps guide for the wider picture behind today’s tools.
How do you check what is listening on a port on Linux? Both

I reach for ss — the modern replacement for netstat. My default is `ss -tlnp`: -t is TCP, -l is listening sockets only, -n keeps it numeric so it does not hang on reverse lookups, and -p names the process and PID owning each socket, which usually needs sudo. Add -u to include UDP. So to see why nginx is unreachable, `sudo ss -tlnp | grep :443` tells me instantly whether anything is bound to 443 and which process it is. If nothing is bound, the service never started or crashed. If it is bound to 127.0.0.1 instead of 0.0.0.0, it is listening on localhost only — the classic 'works on the box, not from outside' bug.

Walk me through how you'd troubleshoot 'the website is down.' Both

I work in layers, cheapest first, so I never guess. First, does the name resolve? `dig +short site.com` — no answer means DNS. Second, can I reach the host at all? `ping` the IP; if it fails, `traceroute` shows where it dies, pointing at routing or a firewall. Third, is anything listening? `ss -tlnp` on the box, or probe the port from outside. Fourth, does the app answer correctly? `curl -I` and read the status code. Each step rules out a whole layer, so within a minute I have narrowed 'down' to DNS, network, port, or app — and I am fixing the right thing instead of restarting servers at random.

ping works but the app is unreachable — what does that tell you? Both

That the network layer is fine — the host is up and routable — so the problem lives higher up. ping only proves ICMP reaches the machine; it says nothing about whether your application port is open or the app is healthy. Next I check the port with `ss -tlnp` on the server, or try connecting from outside. Common causes: the service crashed or never bound to the port, it bound to 127.0.0.1 instead of a public address, or a firewall or cloud security group blocks the port while still allowing ICMP. Then I `curl` the endpoint — 'connection refused' means nothing is listening, while a timeout usually means a firewall silently dropping packets.

How do you tell a DNS problem from a network problem? Service

I split them deliberately. To test the network without DNS, I ping an IP directly — like `ping 1.1.1.1`. If that works but `ping example.com` fails, the network is fine and name resolution is broken. To confirm, `dig example.com`: NXDOMAIN or no answer points at the record or the zone, while a timeout points at the resolver itself. I also query a known-good resolver directly with `dig @1.1.1.1 example.com`; if that answers but my local one does not, the fault is my configured resolver, not the domain. Separating 'is it reachable' from 'does the name resolve' stops me chasing the wrong layer for twenty minutes.

Mark Day 12 complete

You can find where a service listens — tomorrow you learn to reach it securely with SSH keys, config and tunnels.

Stuck on today’s lab? Ask in Mission 90 Q&A