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 seeMeans
SYN out, nothing backDropped upstream, firewall or blackhole route
SYN out, RST backReached the host, nothing listening on that port
SYN out, ICMP unreachable backA router or firewall is refusing explicitly
SYN, SYN-ACK, then RSTIt connected then the app rejected it
SYN retried at 1s, 2s, 4sNothing 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 typeHexRecord
200x14ChangeCipherSpec
210x15Alert
220x16Handshake
230x17Application 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

GoalCommand 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'
Retransmissionstshark -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'