tcpdump Explained: Capturing Packets and Reading What You Caught

tcpdump Explained: Capturing Packets and Reading What You Caught

Logs tell you what each side believes happened. When those beliefs disagree, the packets are the only account both sides have to accept.

Find the interface first

# what can be captured
tcpdump -D
sudo tcpdump --list-interfaces

# or
ip -br addr

Most failed captures are captures on the wrong interface. any is the safe first move, at the cost of losing some link-layer detail.

sudo tcpdump -i any -n

-n is not optional in practice. Without it, tcpdump resolves every address and port to a name, which means DNS lookups generated by your own capture appearing in your capture. -nn also stops port-name translation, so you see 443 rather than https.

The flags that matter

sudo tcpdump -i eth0 -nn -v port 443
FlagEffect
-iInterface, or any
-n / -nnNo name resolution / also no port names
-c 100Stop after 100 packets
-w file.pcapWrite raw capture to a file
-r file.pcapRead a capture back
-v -vvMore detail
-APrint payload as ASCII
-XHex and ASCII
-eShow link-layer header, MAC addresses
-t -ttttNo timestamp / human readable timestamp
-Q in|outDirection only

-s 0 appears in every older guide. It set the snapshot length to unlimited, and current versions already capture whole packets, so you can leave it out.

Filters are the whole skill

An unfiltered capture on a busy host is unreadable. The filter language is BPF, and it is shared with Wireshark’s capture filters.

# host
sudo tcpdump -nn host 10.0.0.5
sudo tcpdump -nn src 10.0.0.5
sudo tcpdump -nn dst 10.0.0.5

# network
sudo tcpdump -nn net 10.0.0.0/24

# ports
sudo tcpdump -nn port 443
sudo tcpdump -nn portrange 8000-8100
sudo tcpdump -nn dst port 5432

# protocol
sudo tcpdump -nn icmp
sudo tcpdump -nn udp port 53
sudo tcpdump -nn arp

Combine with and, or, not:

# traffic to one host, excluding your own ssh session
sudo tcpdump -nn host 10.0.0.5 and not port 22

# either of two services
sudo tcpdump -nn 'port 80 or port 443'

# one direction, one host, one port
sudo tcpdump -nn 'src 10.0.0.5 and dst port 5432'

Quote filters containing parentheses or special characters, or the shell will interpret them.

sudo tcpdump -nn '(host 10.0.0.5 or host 10.0.0.6) and port 443'

Excluding your own session

Capturing on a server you are logged into over ssh generates traffic for every line it prints, which generates more traffic. Always exclude it:

sudo tcpdump -nn -i eth0 not port 22
sudo tcpdump -nn -i eth0 not host "${SSH_CLIENT%% *}"

Filtering on TCP flags

This is where tcpdump stops being a firehose and starts answering questions.

# connection attempts only
sudo tcpdump -nn 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'

# refused connections
sudo tcpdump -nn 'tcp[tcpflags] & tcp-rst != 0'

# handshakes and teardowns, no data
sudo tcpdump -nn 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'

# packets with an actual payload
sudo tcpdump -nn 'tcp and (ip[2:2] - ((ip[0]&0xf)<<2) - ((tcp[12]&0xf0)>>2)) != 0'

The SYN-without-ACK filter is the one to reach for when a service is refusing connections, because it shows you every attempt and nothing else.

Reading the output

14:23:01.123456 IP 10.0.0.5.51234 > 10.0.0.9.443: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
14:23:01.124001 IP 10.0.0.9.443 > 10.0.0.5.51234: Flags [S.], seq 987654321, ack 1234567891, win 65160, options [...], length 0
14:23:01.124100 IP 10.0.0.5.51234 > 10.0.0.9.443: Flags [.], ack 1, win 502, length 0

That is a completed three-way handshake. The flag notation:

NotationFlag
[S]SYN
[S.]SYN-ACK
[.]ACK only
[P.]PSH-ACK, data
[F.]FIN-ACK, closing
[R]RST, refused or aborted

The diagnostic patterns are short:

[S] repeatedly, no reply. Packets are not arriving or the reply is not returning. A firewall dropping silently, or a routing problem.

[S] then [R]. The host is reachable and nothing is listening on that port. Check the service, as our open ports guide covers.

Handshake completes, then [R] immediately. Something accepted and then rejected: a TLS mismatch, a proxy refusing, an application-level deny.

No reply at all versus a reject is the distinction that tells you whether a firewall is set to DROP or REJECT, and that narrows down which firewall.

Capture to a file, analyse elsewhere

For anything beyond a quick look, this is the workflow.

# capture, do not decode
sudo tcpdump -i eth0 -nn -w /tmp/capture.pcap port 443

# read it back later
tcpdump -nn -r /tmp/capture.pcap
tcpdump -nn -r /tmp/capture.pcap 'tcp[tcpflags] & tcp-rst != 0'

Two reasons this matters. Decoding to a terminal is what makes tcpdump fall behind and drop packets on a busy interface. And a .pcap can be filtered repeatedly with different questions, where a terminal capture can only be read once.

Ring buffers for long captures

When the problem happens twice a day and you do not know when:

# 100 MB per file, keep 10, oldest overwritten
sudo tcpdump -i eth0 -nn -w /var/tmp/cap.pcap -C 100 -W 10 port 443

# rotate hourly instead
sudo tcpdump -i eth0 -nn -w /var/tmp/cap-%Y%m%d-%H%M.pcap -G 3600 -W 24

-C and -W together bound the disk usage, which is what stops a forgotten capture filling /var at three in the morning.

Wireshark for the reading

tcpdump captures; Wireshark reads. They share the file format, so there is no conversion.

# copy it back and open it
scp server:/tmp/capture.pcap .
wireshark capture.pcap

# or pipe a live remote capture straight in
ssh server 'sudo tcpdump -i eth0 -nn -w - port 443' | wireshark -k -i -

That second one is worth keeping. It gives you live Wireshark analysis of a remote interface without installing anything on the server.

tshark is Wireshark’s command line form, and it decodes protocols far better than tcpdump:

# HTTP requests, as fields
tshark -r capture.pcap -Y http.request -T fields -e http.host -e http.request.uri

# TLS handshake details, including SNI
tshark -r capture.pcap -Y tls.handshake.type==1 -T fields -e tls.handshake.extensions_server_name

# follow one TCP stream as text
tshark -r capture.pcap -q -z follow,tcp,ascii,0

Wireshark display filters use a different syntax from BPF capture filters. port 443 captures; tcp.port == 443 displays. Mixing them up is a rite of passage.

Running it without root

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

Capabilities rather than sudo, which is tidier when several people need to capture. Debian and Ubuntu also have a wireshark group, configured through dpkg-reconfigure wireshark-common, that grants dumpcap the same way.

Containers and namespaces

Traffic inside a container namespace is not visible from the host interface.

# capture inside a container's namespace from the host
sudo nsenter -t "$(docker inspect -f '{{.State.Pid}}' mycontainer)" -n \
  tcpdump -nn -i eth0

# the bridge sees inter-container traffic
sudo tcpdump -nn -i docker0
sudo tcpdump -nn -i br-abc123

nsenter -n is the important one, because it runs tcpdump in the container’s network namespace using the host’s binary, so the container needs nothing installed. Our container networking guide covers the topology.

Treat the file as sensitive

A capture contains everything that was not encrypted: credentials on plaintext protocols, session cookies, tokens in URLs, internal hostnames, personal data.

  • Restrict it. chmod 600, and not in a world-readable directory
  • Delete it when the investigation is over
  • Filter at capture time rather than capturing everything and filtering later, when you can
  • Get authorisation on machines belonging to an employer or customer. Packet capture on shared infrastructure is precisely what acceptable use policies are written about

TLS protects payloads but not metadata: addresses, ports, timing, sizes, and the SNI hostname are all visible in a capture, which is frequently enough to be revealing on its own.

A short cheat sheet

# is anything arriving at all
sudo tcpdump -nn -i any -c 20

# who is trying to connect to this port
sudo tcpdump -nn -i eth0 'tcp[tcpflags] & tcp-syn != 0 and dst port 8080'

# DNS queries and answers
sudo tcpdump -nn -i any udp port 53

# is DHCP working
sudo tcpdump -nn -i eth0 'port 67 or port 68'

# who is answering ARP for an address
sudo tcpdump -nn -i eth0 arp and host 10.0.0.1

# plaintext HTTP, readable
sudo tcpdump -nn -A -i eth0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

# retransmissions and resets only
sudo tcpdump -nn -i eth0 'tcp[tcpflags] & tcp-rst != 0'

Start with ss and ip for state, as our network troubleshooting guide covers. Reach for tcpdump when state looks fine and behaviour does not.

Frequently Asked Questions

What is the difference between tcpdump and Wireshark?

They read the same capture format and differ in where they run. tcpdump is a command line tool present on nearly every server, which makes it the one you use to capture. Wireshark is a graphical analyser with protocol decoding and stream reassembly, which makes it the one you use to read a large capture. The usual workflow is capture with tcpdump, analyse in Wireshark.

Why does tcpdump show nothing when I know traffic is flowing?

Usually the wrong interface, or a filter that is too narrow. Check with tcpdump -D and try -i any first. On a switched network you only see traffic involving your own host unless a port mirror is configured, and traffic inside a bridge or container namespace needs capturing on that interface specifically.

Do I need root to run tcpdump?

Capturing requires privileges because it puts the interface into promiscuous mode and reads raw frames. You can grant the binary the cap_net_raw and cap_net_admin capabilities instead of using sudo, which is the tidier approach on a machine where several people need to capture.

What does -s 0 do and do I still need it?

It sets the snapshot length to capture whole packets rather than the first fragment. Modern tcpdump defaults to capturing the full packet already, so the flag is usually redundant. It appears in older documentation everywhere, which is why people still type it.

How do I capture without dropping packets on a busy server?

Write straight to a file with -w rather than decoding to the terminal, since formatting output is what makes tcpdump fall behind. Add a narrow filter, use -n to skip DNS lookups, and use ring buffers with -C and -W so the capture cannot fill the disk.

A capture may contain credentials, tokens, personal data and session cookies in anything not encrypted, so treat the file as sensitive: restrict access, and delete it when you are finished. On systems belonging to an employer or customer, get authorisation first, because packet capture on shared networks is exactly the activity that policies cover.