The packet sniffer every network debugger reaches for — when "is the traffic even arriving?" needs a real answer. You'll capture packets, read the cryptic output, write filters that isolate exactly what you want, save captures for Wireshark, and debug real problems (connection refused, TLS handshakes, DNS, retransmissions). Hands-on throughout. Work top to bottom, or jump to a part.
The journey:
| You need | Why |
|---|---|
| A Linux/macOS host with tcpdump | Capturing requires root/sudo |
| Root or sudo | Raw packet capture is privileged |
| Basic TCP/IP familiarity | IPs, ports, the TCP handshake |
Install with apt install tcpdump / brew install tcpdump. In containers, capture from the node or a debug sidecar sharing the network namespace. Only capture on systems you're authorized to — packets can contain sensitive data.
tcpdump reads packets straight off a network interface and prints (or saves) them. It answers questions higher-level tools can't:
When curl hangs or a service "can't connect," tcpdump tells you whether packets reach the box at all — separating a network/firewall problem from an application one. It's the ground truth beneath every other network tool.
# List interfaces, then capture on one
sudo tcpdump -D
sudo tcpdump -i eth0
# The flags you'll always want:
sudo tcpdump -i eth0 -nn -c 20
The essential flags:
-i eth0 — which interface (-i any for all).-nn — don't resolve hostnames or port names (faster, and shows real IPs/ports). Use this almost always.-c 20 — stop after 20 packets (otherwise it runs forever).-v / -vv — more verbosity (TTL, options, flags).Without -nn, tcpdump does reverse-DNS on every IP — slow and misleading. Make -nn a habit.
A typical line (with -nn):
10:15:02.123456 IP 10.0.0.5.54321 > 10.0.0.9.443: Flags [S], seq 12345, win 64240, length 0
10:15:02.124890 IP 10.0.0.9.443 > 10.0.0.5.54321: Flags [S.], seq 98765, ack 12346, win 65160, length 0
10:15:02.125001 IP 10.0.0.5.54321 > 10.0.0.9.443: Flags [.], ack 98766, win 64240, length 0
Decode it: timestamp IP src.port > dst.port: Flags [...], .... The TCP flags are the story:
| Flag | Meaning |
|---|---|
[S] |
SYN — start of the 3-way handshake |
[S.] |
SYN-ACK — server accepting |
[.] |
ACK — acknowledgement (the . is ACK) |
[P.] |
PSH-ACK — data being pushed |
[F.] |
FIN — graceful close |
[R] / [R.] |
RST — connection reset/refused |
The three lines above are a healthy SYN → SYN-ACK → ACK handshake. If you see [S] then [R.], the port is closed/refused. If you see [S] with no reply, a firewall is dropping it.
Raw capture is a firehose; filters isolate what matters:
sudo tcpdump -nn -i any host 10.0.0.9 # to/from one host
sudo tcpdump -nn -i any port 443 # one port (either direction)
sudo tcpdump -nn -i any src 10.0.0.5 and dst port 5432 # source host to a dest port
sudo tcpdump -nn -i any 'tcp port 80 or tcp port 443' # http/https
sudo tcpdump -nn -i any net 10.0.0.0/24 # a subnet
Combine with host, src, dst, port, src port, dst port, net, tcp/udp/icmp, joined by and/or/not. Quote the expression when it contains or/(/) so the shell doesn't eat it.
Those filters are BPF — a tiny, kernel-level filtering language, so unmatched packets are dropped before they ever reach tcpdump (efficient even under heavy traffic). Beyond the basics you can match on flags and fields:
# Only SYN packets (new connection attempts) — great for spotting scans/refused
sudo tcpdump -nn 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'
# Only RSTs (refused/reset connections)
sudo tcpdump -nn 'tcp[tcpflags] & tcp-rst != 0'
# ICMP (ping, unreachables)
sudo tcpdump -nn icmp
BPF runs in the kernel, so a precise filter is both faster and produces readable output. Start broad (host X), then tighten (host X and port Y and tcp-syn) as you narrow the problem.
For anything nontrivial, capture to a file and analyze in Wireshark (its GUI, protocol decoding, and stream reassembly are far better than reading terminal lines):
# Write raw packets to a pcap file (don't print) — full payloads
sudo tcpdump -nn -i any -w capture.pcap -s 0 port 443
# Rotate files so a long capture doesn't fill the disk
sudo tcpdump -nn -i any -w cap-%H%M.pcap -G 300 -W 12 port 443 # 5-min files, keep 12
# Read a saved capture back (offline, no privileges needed)
tcpdump -nn -r capture.pcap
-w file.pcap writes binary packets (open in Wireshark); -r reads them back. -s 0 captures full packets (older tcpdump truncated by default). Use -G/-W to rotate and cap disk use for long captures.
Concrete playbooks:
tcpdump -nn host <server> and port <p>. See [S] then [R.]? The server actively refused (nothing listening / firewall reject). See [S] with no reply? A firewall is silently dropping (DROP vs REJECT).[P.] and response, and watch for retransmissions (the same seq sent twice) which signal packet loss.tcpdump -nn -i any udp port 53 shows queries and whether a response comes back.tcpdump -nn port 443 shows the handshake starting; a RST right after [S.] points at a TLS or app-layer reject.The core value: tcpdump tells you which side of the wire the problem is on.
For text protocols you can print payloads (use judiciously — payloads may hold secrets):
# -A prints ASCII payload (great for HTTP headers)
sudo tcpdump -nn -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
# -X shows hex + ASCII
sudo tcpdump -nn -X port 80 -c 5
-A (ASCII) reveals plaintext HTTP requests/responses; -X shows hex and ASCII side by side. HTTPS payloads are encrypted — you'll see the handshake but not the content, which is the point of TLS. For deep payload analysis, save to pcap and use Wireshark's "Follow Stream."
-nn — skip DNS/port-name resolution for speed and clarity.-c N so a quick check doesn't run forever; -w + -G/-W to rotate long captures.kubectl debug).-nn. tcpdump then does slow reverse-DNS on every packet and hides the real ports.-c or rotation. An unbounded -w capture silently fills the disk.-s 0 for full capture.cat. Use tcpdump -r or Wireshark; pcap is binary.-A/-w can record plaintext credentials — handle captures as sensitive.sudo tcpdump -nn -i any port 443 -c 10, then curl https://example.com and identify the [S] → [S.] → [.] sequence.curl a closed port while capturing; find the [S] immediately answered by [R.].tcp-syn BPF expression, then only RSTs.-w cap.pcap -s 0, stop it, and read it back with -r (and open it in Wireshark if you have it).udp port 53 while resolving a name and confirm you see the query and response.Self-check:
-nn almost always the right choice?[S] [S.] [.] mean, and what does [S] followed by [R.] mean?ss, ip, firewalls) and the Nginx tutorial for the services behind the packets.You now have the loop: capture → read the flags → filter with BPF → save → debug which side of the wire is broken. That's tcpdump when the network is the suspect.