Install

tcpdump is in the base install on most distros; if not:

sudo apt install tcpdump
sudo dnf install tcpdump
sudo pacman -S tcpdump
sudo apk add tcpdump

It needs root (or CAP_NET_RAW + CAP_NET_ADMIN capabilities) to capture. On systems with strict policy, setcap on the binary lets a regular user run it:

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

The first capture

# List interfaces
sudo tcpdump -D

# Capture on a specific interface; -n disables DNS resolution (much faster output)
sudo tcpdump -ni eth0

# More verbose; -v / -vv / -vvv add increasing detail
sudo tcpdump -nvv -i eth0

# Limit to 100 packets then exit
sudo tcpdump -nc 100 -i eth0

The default output is one line per packet. From left to right: timestamp, protocol, src → dst, flags, options, length.

BPF filter syntax

Filters are positional expressions combining primitives with and / or / not:

# By host
sudo tcpdump -n host 10.0.5.20
sudo tcpdump -n src host 10.0.5.20
sudo tcpdump -n dst host 10.0.5.20

# By port
sudo tcpdump -n port 443
sudo tcpdump -n tcp port 80 or tcp port 443
sudo tcpdump -n port not 22                      # exclude SSH noise

# By network
sudo tcpdump -n net 192.168.1.0/24
sudo tcpdump -n src net 10.0.0.0/8

# By protocol
sudo tcpdump icmp
sudo tcpdump arp
sudo tcpdump udp

# Combinations
sudo tcpdump -n 'host 10.0.5.20 and (tcp port 5432 or tcp port 5433)'
sudo tcpdump -n 'not (port 22 or port 5353 or arp)'

Single-quote complex expressions to keep the shell out of the parsing.

The output flags worth remembering

-n             Don't resolve hostnames / ports (fast, prints raw IPs)
-nn            Also don't resolve port numbers to service names
-i <intf>      Interface (or 'any' for all)
-c <N>         Exit after N packets
-s 0           Capture full packets (default snaplen used to be 96; modern tcpdump default is 262144, full)
-w file.pcap   Write raw packets to a file (later open in Wireshark or read with -r)
-r file.pcap   Read from a file instead of live capture
-v / -vv       More verbose decoding
-X             Hex + ASCII dump of packet contents
-A             ASCII-only dump (useful for HTTP / plain protocols)
-e             Include the link-layer header (MAC addresses)
-tttt          Human-readable timestamps
-G <sec> -W N  Rotate the output file every <sec> seconds, keep N files

Save for later, analyze with Wireshark

# Capture matching traffic to a file (binary pcap format)
sudo tcpdump -nni eth0 -w /tmp/web.pcap 'host 10.0.5.20 and tcp port 443'

# Capture only TLS ClientHello + ServerHello (small, useful for cert debugging)
sudo tcpdump -nni eth0 -w /tmp/tls-handshake.pcap \
    '(tcp port 443) and (tcp[((tcp[12] & 0xf0) >> 2)] = 22)'

# Stop with Ctrl-C
# Then scp /tmp/web.pcap to your laptop and open in Wireshark

The big bit-arithmetic expression above is the classic "match TLS handshake bytes" filter — it inspects byte 0 of the TCP payload (after the variable IP+TCP headers) and matches if it equals 22, which is the TLS record-type byte for Handshake.

Useful one-liners

# See HTTP requests in plain text (only un-encrypted)
sudo tcpdump -nA -s 0 -i eth0 'tcp port 80 and (((ip[2:2]-((ip[0]&0xf)<<2))-((tcp[12]&0xf0)>>2))!=0)'

# Top talkers right now (capture briefly, sort)
sudo timeout 10 tcpdump -nl -i eth0 -c 5000 2>/dev/null \
    | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head

# Watch for SYN floods (high SYN-only rate from one source)
sudo tcpdump -ni eth0 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn' | head -50

# Detect MTU issues: ICMP "Fragmentation Needed"
sudo tcpdump -ni eth0 'icmp and icmp[0] = 3 and icmp[1] = 4'

# Suspect ARP activity (changes to known mappings)
sudo tcpdump -ni eth0 arp -v

# DHCP traffic
sudo tcpdump -ni eth0 -s 0 port 67 or port 68

Capture rotation for long-running diagnostics

sudo tcpdump -nni eth0 \
    -w /tmp/diag-%Y%m%d-%H%M.pcap \
    -G 300 -W 12 \
    -z gzip \
    'host 10.0.5.20'

# -G 300: rotate every 5 minutes
# -W 12:  keep 12 files (1 hour)
# -z gzip: compress rotated files
# %Y%m%d-%H%M: filename uses strftime fields

tcpdump and containers

Containers have their own network namespaces. To capture from inside a container:

# For Docker / Podman containers
sudo nsenter -t $(docker inspect -f '{{.State.Pid}}' <container>) \
    -n tcpdump -ni eth0

# Or run a sidecar
docker run --rm -it --net container:<target> nicolaka/netshoot tcpdump -ni eth0

For Kubernetes pods, kubectl debug (or the netshoot debug image) gives the same access.

The big mental model

Two things to internalize:

  1. tcpdump shows you what's on the wire. Not what the application thinks it sent — what actually crossed the NIC. If tcpdump doesn't see it, the application's send call didn't make it out; if tcpdump sees it but the other side doesn't reply, the network is doing something. This split makes most network bugs solvable.
  2. Filter early, look later. A loose filter floods the screen and slows the process; an over-tight filter misses the packet you needed. Start moderately specific, then narrow as you confirm what you're looking at. -w to a file gives you the freedom to re-filter offline.

When to use tcpdump vs Wireshark

  • tcpdump on the server, capture to a pcap, scp to a laptop, open in Wireshark for analysis — the most common workflow.
  • tshark (Wireshark's CLI) is a richer tcpdump — same captures, but with display filters and the full protocol dissector. See the tshark tutorial.
  • tcpdump is preinstalled or one-package-install on every Unix; tshark sometimes isn't. For the smallest-tooling debugging, tcpdump is the universal pick.