Andrew Mercer
on this page

References

  • https://danielmiessler.com/study/tcpdump
  • https://gist.github.com/tuxfight3r/9ac030cb0d707bb446c7
  • http://www.commandlinefu.com/commands/using/tcpdump
  • man pcap-filter — the canonical BPF filter syntax reference

1. What tcpdump actually does

tcpdump is a thin CLI wrapper around libpcap, which talks to the kernel's packet capture facility (AF_PACKET on Linux, BPF device on BSD/macOS). Every filter expression you write gets compiled down to BPF (Berkeley Packet Filter) bytecode and pushed into the kernel, so filtering happens before packets are copied to userspace — this is why filtering is cheap and sniffing an interface at line rate with a tight filter is fine, but tcpdump -i eth0 with no filter on a busy link is not.

Two things worth internalizing early: - Capture ≠ display. -v/-vv/-vvv and -X/-A control how much detail is printed, not how much is captured. Use -w to capture everything to disk regardless of verbosity. - Filter expression ≠ display filter. Unlike Wireshark, tcpdump's capture filter (BPF) cannot do things like "show me only retransmitted TCP segments" — that requires post-processing (usually with tshark or Wireshark itself on the resulting pcap).

2. Basic invocation & permissions

tcpdump -D                     # list available interfaces
tcpdump -i eth0                # capture on a specific interface
tcpdump -i any                 # capture on all interfaces (Linux "cooked" capture, adds a pseudo-header)

tcpdump needs CAP_NET_RAW (and CAP_NET_ADMIN for some interface modes). Rather than running as root:

sudo setcap cap_net_raw,cap_net_admin+eip $(which tcpdump)

then any user in the right group can capture without sudo.

3. Core flags reference

Flag Meaning
-i <iface> interface to sniff (any for all)
-n don't resolve hostnames
-nn don't resolve hostnames or port names (show raw port numbers)
-v, -vv, -vvv increasing verbosity of decoded output
-e print link-layer (Ethernet/MAC) header
-X print packet payload in hex and ASCII
-XX same as -X but includes the link-layer header in the hex dump
-A print payload in ASCII only (good for quick text-protocol inspection)
-c <n> stop after capturing n packets
-s <n> snap length — bytes captured per packet (-s0 / -s 262144 = full packet, default is often already full on modern tcpdump)
-w <file> write raw packets to a pcap file instead of decoding to stdout
-r <file> read packets back from a pcap file
-G <secs> rotate the output file every N seconds (used with -w)
-W <n> limit the number of rotated files (ring buffer) before overwriting
-C <size> rotate the output file once it reaches <size> (in MB)
-z <cmd> run <cmd> <savefile> each time a -G/-C rotation completes (e.g. compress or ship the file)
-tttt print full, human-readable date+time on every line (default timestamps are relative and easy to misread)
-l line-buffer stdout — needed if you're piping tcpdump into grep/awk and want to see output live
-K don't verify checksums (useful when capturing on the sending host, where checksum offload makes checksums look "wrong")
-p don't put the interface into promiscuous mode
-U write each packet to the savefile immediately, not just when the buffer fills

4. Filter expression language (BPF)

Filters are built from primitives combined with and/or/not (or &&/||/!):

  • Type: host, net, port, portrange
  • Direction: src, dst (default is both)
  • Protocol: ip, ip6, tcp, udp, icmp, icmp6, arp, vlan
tcpdump 'src host 10.0.0.5 and dst port 443'
tcpdump 'net 192.168.0.0/24'
tcpdump 'tcp portrange 8000-8010'
tcpdump 'vlan and host 10.0.0.5'          # match inside a VLAN-tagged frame

Advanced BPF: matching on raw bytes/flags

You can index directly into the packet with proto[offset:size]. The most common real-world use is matching TCP flags without a helper keyword:

# SYN packets only (start of a connection)
tcpdump 'tcp[13] & 2 != 0'

# SYN-ACK
tcpdump 'tcp[13] & 18 == 18'

# RST packets (connection resets/refusals — great for finding blocked ports)
tcpdump 'tcp[13] & 4 != 0'

# FIN
tcpdump 'tcp[13] & 1 != 0'

(byte 13 of the TCP header is the flags byte; the bit values are the same as the flag bits: FIN=1, SYN=2, RST=4, PUSH=8, ACK=16, URG=32).

There are also shorthand keywords in modern tcpdump: tcp-syn, tcp-ack, tcp-fin, tcp-rst, tcp-push, e.g. tcpdump 'tcp[tcpflags] == tcp-syn'.

5. Common filter recipes

Sniff by destination / source IP

tcpdump -i any -vvv 'dst 45.63.18.98' or 'dst 108.61.191.230'   # traffic TO these IPs
tcpdump -i any -vvv 'src 45.63.18.98' or 'src 108.61.191.230'   # traffic FROM these IPs

Note: when combining or across quoted primitives like this, each side is evaluated independently — for combined AND/OR logic wrap the whole thing in one filter string: tcpdump -i any 'dst 45.63.18.98 or dst 108.61.191.230'.

IPv6 traffic only

tcpdump -vvvv -ttt -i any icmp6

-ttt prints a delta timestamp (time since the previous printed packet) — handy for spotting retransmission/timeout patterns. Compare against -tttt (absolute wall-clock time) when you need to correlate with logs elsewhere.

Sniff by host — to pcap

tcpdump -i eth0 -vvv ip host 10.25.20.52 -n -c 5000 -w 10.25.20.52_dump.pcap
tcpdump -i eth0 -vvv ip host 10.25.20.52 -n -w 10.25.20.52_dump.pcap
tcpdump -i eth0 -vvv dst host 10.25.20.52 or src host 10.25.20.52 -n > 10.25.20.52_dump.txt

Read it back:

tcpdump -r 10.25.20.52_dump.pcap
tcpdump -r 10.25.20.52_dump.pcap -nn -X                # re-apply verbosity/hex on replay
tcpdump -r 10.25.20.52_dump.pcap 'tcp port 443'        # you can even re-filter on replay

host matches both source and destination — dst host X or src host X above is equivalent to just host X, spelled out.

Sniff by host — stdout

tcpdump -i eth0 dst host 208.67.222.222 or dst host 208.67.220.220 or src host 208.67.222.222 or src host 208.67.220.220 -n

Same idea, simplifiable to: tcpdump -i eth0 -n host 208.67.222.222 or host 208.67.220.220.

Sniff specific ports

tcpdump -vvv -i eth0 port 22

Ignoring things

tcpdump -i any -vvv port not 22              # ignore a specific port
tcpdump -i any -vvv not arp                  # ignore ARP noise
tcpdump -i any -vvv port not 22 and not arp  # ignore ssh and arp
tcpdump -i any -vvv not port 22 and not host metadata.google.internal

This pattern — excluding your own management traffic (SSH, cloud metadata endpoint, DNS) — is the standard way to declutter a capture down to "the thing I actually care about."

Test if a flow is encrypted

flow = connection from src ip/port to dst ip/port and the data in the middle

tcpdump -X port 995

If -X's ASCII column shows recognizable plaintext (headers, credentials, protocol banners) the flow is cleartext; high-entropy/garbled output on a port that should be TLS (e.g. 995 = POP3S) confirms it's actually encrypted. This is a fast way to validate that a "should be TLS" service actually negotiated TLS rather than falling back to plaintext.

Sniff VLAN IDs

tcpdump -i eth0 -vvv -nn -e

-e prints the link-level header on each line — needed to see 802.1Q VLAN tags and MAC addresses. With -e -vvv on a trunk port you'll see vlan <id> inline in the decode.

Misc — top 10 talkers

tcpdump -tnn -c 2000 -i eth0 | awk -F "." '{print $1"."$2"."$3"."$4}' | sort | uniq -c | sort -nr | awk '$1 > 10'

Misc — IPs communicating on an interface

tcpdump -i wlan0 -n ip | awk '{ print gensub(/(.*)\..*/,"\\1","g",$3), $4, gensub(/(.*)\..*/,"\\1","g",$5) }' | awk -F " > " '{print $1"\n"$2}'

Record a day's traffic in 15-minute chunks

tcpdump -G 900 -w '%Y-%m-%d_%H:%M:%S.pcap' -W 96

-G 900 rotates every 900s (15 min); -W 96 caps it at 96 files = 24 hours, then wraps around (ring buffer) so disk usage is bounded. Add -z gzip to compress each file the moment it's rotated:

tcpdump -G 900 -W 96 -z gzip -w '/var/log/pcaps/%Y-%m-%d_%H:%M:%S.pcap'

Namespace / DHCP capture examples

ip netns exec qdhcp-f6e9be13-26d8-4277-82bd-e6aa903d7abe tcpdump -vnes0 -i tap13d3e279-0c port 67 or port 68 -w /tmp/director_dhcp.dump
tcpdump -vnes0 -i any port 67 or port 68 -w /tmp/contoller_dhcp.dump

-e (link header) + -s0 (full snaplen) + -n is the standard combo for DHCP debugging, since you often need MAC addresses (-e) and the full DHCP options list (-s0) to diagnose lease/relay issues. ip netns exec <ns> tcpdump ... is the general pattern for sniffing inside a network namespace (OpenStack/Neutron here, but identical technique for any netns — including container netns, see §7).

6. Advanced techniques

Remote live capture piped straight into Wireshark

No need to scp a pcap back — stream it live over SSH into a local GUI:

ssh user@remote-host "tcpdump -i eth0 -U -s0 -w -" | wireshark -k -i -

-U flushes each packet immediately so there's no buffering delay over the pipe.

Capturing inside a Kubernetes pod (no tcpdump in the image)

Most production container images don't ship tcpdump. Options, cheapest first:

# 1. Ephemeral debug container sharing the target pod's network namespace (k8s 1.23+)
kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container> -- tcpdump -i any -w /tmp/cap.pcap

# 2. If you have node access: find the pod's network namespace and enter it directly
crictl inspect <container-id> | grep netns   # or: docker inspect --format '{{.NetworkSettings.SandboxKey}}' <id>
nsenter --net=<netns-path> tcpdump -i eth0

# 3. Sidecar pattern: add a tcpdump sidecar container with shareProcessNamespace/hostNetwork as appropriate

nicolaka/netshoot is the de-facto standard "networking swiss army knife" debug image (tcpdump, dig, curl, iproute2, tshark, etc. all preinstalled) — worth keeping pinned in your homelab tooling.

Snaplen matters more than people think

Older tcpdump defaulted to -s 96 or -s 68, silently truncating packets — which corrupts the checksum/length fields you see and breaks reassembly in Wireshark. Modern tcpdump defaults to full capture, but always pin it explicitly in scripts/automation: -s0 (unlimited) or -s 262144.

  • Always pair -nn with production captures — DNS/service-name resolution is a blocking syscall per packet and will cause drops on busy interfaces.
  • Increase the kernel capture buffer with -B <KB> if you see packets dropped by kernel in the summary stats (printed on exit or via SIGINFO/Ctrl+T on BSD, or -v counters on Linux).
  • Prefer writing straight to a fast local disk with -w, then filtering/decoding offline with -r — decoding to stdout while capturing competes with capture for CPU.

Extracting just TLS SNI (which host a client is connecting to, even without decrypting)

tcpdump -i any -nn -A 'tcp port 443 and (tcp[((tcp[12:1] & 0xf0) >> 2):1] = 0x16)' -w - | \
  tshark -r - -Y "tls.handshake.extensions_server_name" -T fields -e tls.handshake.extensions_server_name

This is the standard technique for identifying which domains a host is talking to over TLS purely from the ClientHello, without breaking encryption — useful for egress auditing.

Legal/ethical note

Only capture traffic on networks/hosts you own or are explicitly authorized to monitor. Passive sniffing of others' traffic (even "just to test") crosses into wiretapping in most jurisdictions.

7. Quick cheat sheet

tcpdump -D                                   # list interfaces
tcpdump -i eth0 -nn                          # basic, no name resolution
tcpdump -i any -nn host 1.2.3.4              # by host
tcpdump -i eth0 -nn port 443                 # by port
tcpdump -i eth0 -nn 'tcp[tcpflags] == tcp-syn'  # new connections only
tcpdump -i eth0 -w cap.pcap -s0 -c 10000      # capture to disk, full packets, bounded count
tcpdump -r cap.pcap -nn -X                    # replay with hex+ascii
tcpdump -i eth0 -G 900 -W 96 -z gzip -w '/var/log/pcap/%F_%T.pcap'  # rotating, compressed, bounded