This post was originally published on go2linux.org. The domain is no longer mine, but I am the original author. I am republishing it here on garron.me with corrections and improvements.

tcpdump is the classic Linux packet sniffer. It prints one line per packet that crosses a network interface, or saves the packets to a file for later analysis.

Years ago, as part of my job, I had to help implement VLANs in a satellite hub. We needed to check whether the VLAN tags were attached to the Ethernet frames, and we had two sniffers: a professional Fluke analyzer and my IBM T30 laptop running tcpdump. The Fluke found the tagged and untagged frames, and so did the laptop, at a fraction of the cost.

Install

It is usually installed already. If not:

sudo apt install tcpdump      # Debian, Ubuntu
sudo dnf install tcpdump      # Fedora, RHEL
sudo pacman -S tcpdump        # Arch

Capturing packets needs root, so every example here uses sudo.

Choose the interface

List the interfaces tcpdump can capture on:

sudo tcpdump -D

Then capture on one of them with -i:

sudo tcpdump -i enp3s0

Interface names like eth0 are rare today; ip -br link shows the names on your machine. To listen on all interfaces at once use the pseudo-interface any:

sudo tcpdump -i any

Captures on any are not done in promiscuous mode, so you only see traffic addressed to or sent from the machine itself.

Press Ctrl+C to stop. tcpdump then reports how many packets were captured and how many the kernel dropped.

Options you will use every time

sudo tcpdump -i enp3s0 -nn -c 20
  • -n does not resolve IP addresses to host names. Without it tcpdump makes DNS lookups, which is slow and adds its own traffic to the capture.
  • -nn also leaves port numbers alone, so you see 443 instead of https.
  • -c 20 stops after 20 packets.

A line of output looks like this:

12:01:15.123456 IP 192.0.2.10.51514 > 198.51.100.7.443: Flags [S], seq 1234567890, win 64240, length 0

That is the timestamp, the protocol, source address and port, destination address and port, the TCP flags (S is SYN, . is ACK, P is PUSH, F is FIN, R is RST) and the payload length.

Capture filters

Without a filter you get everything, which on a busy server is useless. Add a filter expression at the end of the command.

By host:

sudo tcpdump -i enp3s0 -nn host 192.0.2.10
sudo tcpdump -i enp3s0 -nn src 192.0.2.10
sudo tcpdump -i enp3s0 -nn dst 192.0.2.10

By network:

sudo tcpdump -i enp3s0 -nn net 192.0.2.0/24

By port or port range:

sudo tcpdump -i enp3s0 -nn port 53
sudo tcpdump -i enp3s0 -nn portrange 8000-8100

By protocol:

sudo tcpdump -i enp3s0 -nn icmp
sudo tcpdump -i enp3s0 -nn udp
sudo tcpdump -i enp3s0 -nn arp

Combine them with and, or and not. Put the expression in single quotes when it has parentheses, so the shell does not interpret them:

sudo tcpdump -i enp3s0 -nn 'host 192.0.2.10 and (port 80 or port 443)'

When you are connected over SSH, your own session floods the output. Exclude it:

sudo tcpdump -i enp3s0 -nn not port 22

Only the packets that open new TCP connections (SYN without ACK):

sudo tcpdump -i enp3s0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'

Look inside the packets

-A prints the payload as text, which is enough for plain protocols such as HTTP or SMTP:

sudo tcpdump -i enp3s0 -nn -A port 80

-X prints it in hexadecimal and text side by side. Encrypted traffic (HTTPS, SSH) shows only noise, as it should.

Save to a file and read it back

-w writes the raw packets to a file instead of printing them:

sudo tcpdump -i enp3s0 -nn -w capture.pcap port 443

Read the file later with -r. Filters work here too, so you can capture broadly and narrow down afterwards:

tcpdump -nn -r capture.pcap
tcpdump -nn -r capture.pcap host 192.0.2.10

The same file opens in Wireshark, which is the comfortable way to follow a TCP stream or decode a protocol. The original version of this post recommended Ethereal; that project was renamed Wireshark in 2006.

On some distributions tcpdump drops root privileges to an unprivileged user after opening the interface, and then cannot write in your home directory. If you get Permission denied on the output file, write to /tmp or add -Z root.

Long captures: rotate the files

To leave a capture running while you wait for a problem to show up, limit the size of each file with -C (in millions of bytes) and the number of files with -W. When the last file is full tcpdump overwrites the first one:

sudo tcpdump -i enp3s0 -nn -w trace.pcap -C 100 -W 10

That keeps at most 1 GB on disk. To rotate by time instead, use -G with a file name containing strftime fields:

sudo tcpdump -i enp3s0 -nn -w 'trace-%Y%m%d-%H%M.pcap' -G 3600

See VLAN tags

This is the case from the story above. -e prints the link-level header, which includes the 802.1Q tag, and the vlan filter keeps only tagged frames:

sudo tcpdump -i enp3s0 -e -nn vlan
12:01:15.123456 00:11:22:33:44:55 > 66:77:88:99:aa:bb, ethertype 802.1Q (0x8100), length 102: vlan 100, p 0, ethertype IPv4 (0x0800), 192.0.2.10 > 192.0.2.1: ICMP echo request, id 1, seq 1, length 64

For one VLAN only:

sudo tcpdump -i enp3s0 -e -nn vlan 100

Capture on the physical interface. On a VLAN sub-interface such as enp3s0.100 the kernel has already removed the tag.

Pipe the output

tcpdump buffers its output when it is not writing to a terminal. Add -l to make it line buffered when you pipe it into another command:

sudo tcpdump -i enp3s0 -nn -l port 53 | grep example.com

Practical examples

DNS queries leaving the machine:

sudo tcpdump -i any -nn udp port 53

Check whether packets from a client reach the server at all:

sudo tcpdump -i enp3s0 -nn host 192.0.2.10 and port 443

Ping requests and replies:

sudo tcpdump -i enp3s0 -nn icmp

DHCP traffic:

sudo tcpdump -i enp3s0 -nn port 67 or port 68

See also

man tcpdump — full reference. man pcap-filter — the complete filter syntax.