tcpdump Cheatsheet
Common Recipes
Use this tcpdump reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Is anything arriving at all
Start here before you write a clever filter. Most "the filter is broken" reports are actually "the traffic is not on this interface".
# Twenty packets, no filter, with MAC addresses sudo tcpdump -i eth0 -nn -e -c 20 # Is this port seeing anything in either direction sudo tcpdump -i eth0 -nn -c 20 'tcp port 8080' # Handshakes only, the fastest reachability check sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-syn != 0 and port 8080'
Reading the result:
| You see | Means |
|---|---|
| SYN out, nothing back | Dropped upstream, firewall or blackhole route |
| SYN out, RST back | Reached the host, nothing listening on that port |
| SYN out, ICMP unreachable back | A router or firewall is refusing explicitly |
| SYN, SYN-ACK, then RST | It connected then the app rejected it |
| SYN retried at 1s, 2s, 4s | Nothing is answering, that is TCP backoff |
DNS
# All DNS, both transports sudo tcpdump -i eth0 -nn 'udp port 53 or tcp port 53' # Decoded queries and answers sudo tcpdump -i eth0 -nn -s 0 -v 'udp port 53' # Queries only, not the answers, using the QR bit sudo tcpdump -i eth0 -nn 'udp port 53 and udp[10] & 0x80 = 0' # Answers only sudo tcpdump -i eth0 -nn 'udp port 53 and udp[10] & 0x80 != 0' # Failed lookups: rcode nonzero in the low nibble of the second flags byte sudo tcpdump -i eth0 -nn 'udp port 53 and udp[11] & 0x0f != 0' # DNS over TLS and DNS over HTTPS ports sudo tcpdump -i eth0 -nn 'tcp port 853 or tcp port 443'
The DNS header starts 8 bytes into the UDP payload, so udp[10] is the
first flags byte and its top bit is QR (0 = query, 1 = response).
udp[11] & 0x0f is the RCODE, where 3 is NXDOMAIN and 2 is SERVFAIL.
TLS handshakes only
The handshake is where the useful information is (SNI, version, cipher, certificate), and it is the small part of the connection.
# Every TLS record type on 443 sudo tcpdump -i eth0 -nn -s 0 'tcp port 443' # Handshake records only, content type 0x16 sudo tcpdump -i eth0 -nn -s 0 \ 'tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2)] = 0x16)' # ClientHello only: handshake record whose first handshake byte is 0x01 sudo tcpdump -i eth0 -nn -s 0 \ 'tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2)] = 0x16) and (tcp[((tcp[12:1] & 0xf0) >> 2) + 5] = 0x01)' # Alerts, content type 0x15, which is how handshakes fail sudo tcpdump -i eth0 -nn -s 0 \ 'tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2)] = 0x15)'
| Content type | Hex | Record |
|---|---|---|
| 20 | 0x14 | ChangeCipherSpec |
| 21 | 0x15 | Alert |
| 22 | 0x16 | Handshake |
| 23 | 0x17 | Application data |
To read the SNI hostname, capture to a file and use tshark, which has a
dissector: tshark -r c.pcap -Y tls.handshake.type==1 -T fields
-e tls.handshake.extensions_server_name.
HTTP with payload
# Requests and responses as readable text sudo tcpdump -i eth0 -nn -A -s 0 'tcp port 80' # Only packets carrying data, no bare acks cluttering it sudo tcpdump -i eth0 -nn -A -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' # GET requests only sudo tcpdump -i eth0 -nn -A -s 0 \ 'tcp port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' # Just the request lines and Host headers, piped sudo tcpdump -l -i eth0 -nn -A -s 0 'tcp port 80' | \ grep --line-buffered -E '^(GET|POST|PUT|DELETE|Host:)'
-A prints the raw bytes, so a gzipped or chunked response body is
unreadable noise. For anything beyond the headers, write a pcap and open it
in Wireshark, which reassembles and un-gzips.
Long capture for an intermittent fault
# Headers only, 20 x 100 MB ring, own SSH excluded, privileges dropped sudo tcpdump -i eth0 -nn -s 96 -C 100 -W 20 -Z tcpdump \ -w /var/tmp/ring.pcap \ "not (host ${SSH_CLIENT%% *} and port ${SSH_CLIENT##* })" # Hourly files with timestamps, keep 24 hours then exit sudo tcpdump -i eth0 -nn -s 96 -G 3600 -W 24 -Z tcpdump \ -w '/var/tmp/cap-%Y%m%d-%H%M%S.pcap'
Then when the fault fires, pick the right file by name and narrow it:
capinfos -a -e /var/tmp/cap-20260915-14*.pcap tcpdump -r /var/tmp/cap-20260915-1400.pcap -nn -ttt \ 'host 10.0.0.5 and tcp[tcpflags] & (tcp-rst|tcp-fin) != 0'
Finding retransmissions and resets
BPF cannot see a retransmission, because that needs connection state and BPF is per-packet. So capture with tcpdump and analyze with tshark.
# Resets, which BPF CAN do sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0' # Zero-window advertisements, a stalled receiver sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-ack != 0 and tcp[14:2] = 0' # Retransmissions, needs a display filter tshark -r capture.pcap -Y tcp.analysis.retransmission # Everything TCP flagged as a problem, with a count tshark -r capture.pcap -Y tcp.analysis.flags | wc -l # Which conversations are worst tshark -r capture.pcap -q -z conv,tcp
A quick eyeball test for retransmissions without any tooling: run with
-ttt and look for the same sequence number reappearing after roughly
0.2 s, 0.4 s, 0.8 s. That doubling is the RTO backoff.
Measuring RTT
# ICMP round trip, the simplest baseline sudo tcpdump -i eth0 -nn -ttt icmp # Handshake RTT: time between the SYN and its SYN-ACK sudo tcpdump -i eth0 -nn -ttt 'tcp[tcpflags] & tcp-syn != 0 and host 10.0.0.9' # Wireshark computes it for you tshark -r capture.pcap -Y 'tcp.analysis.ack_rtt > 0.2' \ -T fields -e frame.number -e ip.dst -e tcp.analysis.ack_rtt # Slowest ack round trips, sorted tshark -r capture.pcap -T fields -e tcp.analysis.ack_rtt 2>/dev/null | \ sort -gr | head -10
The SYN to SYN-ACK gap is the cleanest RTT you can get, because it is one
packet each way with no application think-time in between. -ttt prints
the delta directly, so the number you want is on the SYN-ACK line.
Live counting and one-liners
# Packets per second on a port sudo tcpdump -i eth0 -nn -l -q 'tcp port 443' | \ awk '{n++} END {print n}' & # Top talkers from a capture file tcpdump -nn -r capture.pcap 2>/dev/null | \ awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head # Every distinct destination port contacted tcpdump -nn -r capture.pcap 'tcp[tcpflags] & tcp-syn != 0' 2>/dev/null | \ awk '{print $5}' | tr -d ':' | awk -F. '{print $NF}' | \ sort -n | uniq -c | sort -rn | head # Just the count of matching packets, no per-packet output tcpdump -nn -r capture.pcap --count 'tcp port 443'
For anything more structured than this, tshark -T fields -e ... gives you
real columns instead of awk on a text format that was never meant to be
parsed.
Recipe index
| Goal | Command core |
|---|---|
| Only DNS | 'udp port 53 or tcp port 53' |
| Only TLS handshakes | 'tcp port 443 and (tcp[((tcp[12:1]&0xf0)>>2)] = 0x16)' |
| HTTP with payload | -A -s 0 'tcp port 80' |
| One host pair | 'host A and host B' |
| Exclude my SSH | 'not (host <me> and port 22)' |
| Ring buffer | -C 100 -W 20 -w ring.pcap |
| Hourly rotation | -G 3600 -W 24 -w 'cap-%Y%m%d-%H%M%S.pcap' |
| New connections | 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn' |
| Resets | 'tcp[tcpflags] & tcp-rst != 0' |
| Retransmissions | tshark -Y tcp.analysis.retransmission |
| RTT | -ttt on SYN traffic, or tcp.analysis.ack_rtt |
| Zero window | 'tcp[14:2] = 0' |
| Fragments | 'ip[6:2] & 0x1fff != 0' |