LinuxXXII · Network Troubleshootingtcpdump
tcpdump and packet capture - seeing the bytes
What you'll learn
- Capture packets on a specific interface
- Filter for host, port, protocol, and TCP flags
- Read tcpdump output - timestamp, src/dst, flags, sequence
- Diagnose TCP handshake failures, resets, and retransmissions
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
tcpdump is the standard packet capture tool on Linux. It
shows exactly what is on the wire, which is the only way to
debug some classes of network problems. Modern alternatives
like tshark (Wireshark CLI) and ngrep have more features
but tcpdump is the one always installed.
The basics
sudo tcpdump -i eth0 # capture on eth0
sudo tcpdump -i any # capture on all interfaces
sudo tcpdump -i eth0 -n # no name resolution
sudo tcpdump -i eth0 -nn # no resolution of host or port
sudo tcpdump -i eth0 -w capture.pcap # write to file
sudo tcpdump -i eth0 -c 100 # stop after 100 packets
sudo tcpdump -i eth0 -s 96 # truncate to 96 bytes (headers only)
sudo tcpdump -i eth0 -A # ASCII output (for HTTP etc.)
sudo tcpdump -i eth0 -X # hex + ASCII
Production use almost always wants -n or -nn (skip DNS,
which can be slow) and -w file.pcap (write to file for
analysis).
You will see -s 0 in almost every tcpdump recipe on the
internet. It is a no-op on anything current: man 8 tcpdump
gives the default snaplen as 262144 bytes, which is larger
than any Ethernet frame you will capture. -s 0 mattered when
the default was 68 or 96 bytes and payload was silently
truncated. Set -s deliberately today only when you want
smaller captures — -s 96 keeps the headers and drops the
payload, which cuts file size by an order of magnitude on a
bulk-transfer link and keeps application data out of the pcap
in a regulated environment.
Filters
The filter language lets you select which packets to capture or display:
sudo tcpdump -i eth0 host 10.0.0.5 # to or from 10.0.0.5
sudo tcpdump -i eth0 src 10.0.0.5 # from 10.0.0.5 only
sudo tcpdump -i eth0 dst 10.0.0.5 # to 10.0.0.5 only
sudo tcpdump -i eth0 port 443 # port 443 any direction
sudo tcpdump -i eth0 tcp # TCP only
sudo tcpdump -i eth0 udp # UDP only
sudo tcpdump -i eth0 'tcp port 443' # TCP and port 443
sudo tcpdump -i eth0 'src 10.0.0.5 and port 443'
sudo tcpdump -i eth0 'net 10.0.0.0/24' # entire subnet
sudo tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn) != 0' # SYN packets
sudo tcpdump -i eth0 'tcp[tcpflags] & (tcp-rst) != 0' # RST packets
sudo tcpdump -i eth0 'port 443 and not host 10.0.0.5' # exclude one host
Combine with and, or, not. Parenthesise when needed.
Reading the output
12:34:56.789012 IP 10.0.0.10.443 > 10.0.0.5.51234: Flags [S.], seq 12345, ack 67890, win 64240, length 0
Reading this:
- Timestamp (
12:34:56.789012): when the packet was captured, with microsecond precision. - IP (or IP6, ARP, etc.): the layer-3 protocol.
- Source and destination (
10.0.0.10.443 > 10.0.0.5.51234): the source IP:port and destination IP:port. - Flags (
[S.]): TCP flags.[S]is SYN,[S.]is SYN-ACK (the.is ACK),[.]is ACK,[F]is FIN,[R]is RST,[P]is PSH. - Sequence and ACK numbers: TCP sequence state. Useful for detecting retransmissions.
- Window: the receive window size.
- Length: payload size (0 means no payload).
What to look for
Three-way handshake: capture should show [S] from
client, [S.] from server, [.] from client. Anything else
is a problem.
TCP reset (RST): a peer is rejecting the connection. Cause: no listener on the port, firewall rejecting, or the peer is sick.
Retransmissions: same SYN or same data packet sent multiple times. Cause: packet loss, congestion, or the peer not responding.
Out-of-order: packets arriving with sequence numbers earlier than expected. Cause: routing change, ECMP load-balancing with asymmetric paths.
Half-open connections: client sent SYN, never got a SYN-ACK, eventually gave up. Cause: server overloaded, SYN flood, firewall dropping.
Where tcpdump sits in the packet path
This is the single most useful fact about tcpdump, and it is
the one most operators never learn. tcpdump attaches at the
device layer through AF_PACKET. That is below netfilter.
It is not a view of what the host accepted. It is a view of
what crossed the NIC.
Inbound: wire -> NIC -> [tcpdump sees it here] -> netfilter
PREROUTING/INPUT -> socket
Outbound: socket -> netfilter OUTPUT/POSTROUTING ->
[tcpdump sees it here] -> NIC -> wire
So a packet appears in an ingress capture even when the INPUT chain drops it a microsecond later. That asymmetry is what makes the tool decisive. Run the capture on the destination host and read it like this:
| What the capture shows | What it proves |
|---|---|
| SYN present, no SYN-ACK, no RST | The packet arrived. The host discarded it. Local firewall DROP, or nothing bound to that address. |
| SYN present, RST returned | The packet arrived and was actively refused. No listener, or an explicit REJECT rule. |
| No SYN at all | It never reached this NIC. The fault is upstream: routing, a cloud security group, or a filter on the path. |
The inverse trap matters just as much. Because tcpdump sits
below netfilter on egress too, a packet you see leaving in a
capture has already passed OUTPUT. Seeing it in the capture
does not mean it reached the wire beyond the NIC, and it says
nothing about what a downstream firewall did to it.
Capture on both ends whenever you can. “Sent on the client, absent on the server” localises the drop to the path in one step, with no rule reading and no guessing.
# On the client, prove the SYN left
sudo tcpdump -i eth0 -nn 'host 10.0.0.10 and tcp port 443 and tcp[tcpflags] & tcp-syn != 0'
# On the server, prove whether it arrived and what came back
sudo tcpdump -i eth0 -nn 'host 10.0.0.5 and tcp port 443'
To see what netfilter itself did with a packet, you need a
different tool: nft monitor trace with a meta nftrace set 1
rule, or the LOG target. tcpdump cannot answer that
question, because it never sees the verdict.
Production recipes
# Capture all HTTP traffic for one host
sudo tcpdump -i eth0 -nn -A 'host 10.0.0.5 and tcp port 80'
# Capture TCP resets to find dropped connections
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-rst) != 0'
# Capture for 60 seconds then write to file
sudo timeout 60 tcpdump -i eth0 -nn -w /var/tmp/capture.pcap
# Capture only SYN packets (handshake attempts)
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) = 0'
# Capture to file for offline analysis
sudo tcpdump -i eth0 -nn -w /var/tmp/capture.pcap host 10.0.0.5
Capturing DNS on a stub-resolver host
The recipe everyone reaches for is tcpdump -i eth0 'udp port 53', and on any host running systemd-resolved — which this
course teaches as the modern default — it will not show you
the query you are looking for. The application resolves
against 127.0.0.53, so its query crosses the loopback
interface, never eth0. What eth0 carries is resolved’s onward
query to the upstream, and on a cache hit there is no onward
query at all. An empty eth0 capture is therefore not evidence
that the application did not ask.
# Leg 1: what the application actually asked for
sudo tcpdump -i lo -nn 'port 53'
# Leg 2: what the stub forwarded upstream (absent on a cache hit)
sudo tcpdump -i eth0 -nn 'port 53'
# Both legs at once
sudo tcpdump -i any -nn 'port 53'
Two details in those filters matter. Use port 53, not udp port 53: an answer larger than the advertised EDNS buffer
comes back truncated with the TC flag set and the resolver
retries over TCP/53, and a UDP-only filter makes exactly the
failure you are chasing invisible. And -i any on Linux uses
a cooked (SLL) capture with no Ethernet header, so -e and
any MAC-level filter stop working on it — use a named
interface when you need link-layer detail.
Rotate captures in production to limit disk use:
sudo tcpdump -i eth0 -nn -w /var/tmp/cap.pcap -W 5 -C 100
-W 5 keeps 5 files and -C 100 rotates when the current file
passes 100 million bytes. man 8 tcpdump is explicit that
the unit is 1,000,000 bytes, not 1,048,576 — so -C 100 is
~95.4 MiB, and -W 5 -C 100 puts a ceiling of roughly 500 MB
on the capture. Size that against real free space before you
start, and write to /var/tmp or a dedicated volume, never to
/: a capture that fills the root filesystem on a production
host turns a network investigation into an outage.
Knowledge check
Knowledge check · 5 questions
Q1. In tcpdump output, what does [S.] mean in the TCP flags?
Q2. A capture started with `tcpdump -i eth0` will also show traffic that traverses eth1.
Q3. Which tcpdump flags earn their place in a production capture? Select all that apply.
Q4. You capture on the destination host and see the inbound SYN arrive, with no SYN-ACK and no RST in reply. What has this proved?
Q5. If a packet appears in an ingress tcpdump capture, the host firewall has already accepted it.
Passing score: 75%. Answers are checked in this browser.