tcpdump is the standard command-line packet analyzer on Linux. It shows exactly what goes over the wire, which settles questions that logs cannot answer: did the request reach the server, did the server reply, and who closed the connection. In this tutorial you will install tcpdump on Ubuntu 24.04, capture traffic on the right interface, write filters that isolate the packets you care about, save captures for Wireshark and walk through three real troubleshooting cases.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS. The commands are the same on Debian 12 and Rocky Linux 9.
- A non-root user with
sudoprivileges. Capturing packets requires root privileges. - Optionally, Wireshark on your local machine to open saved captures.
Importantcaptures can contain passwords, cookies and personal data sent in clear text. Only capture traffic on systems you administer, keep capture files private and delete them when you are done.
Step 1 - Installing tcpdump and choosing an interface
Install the package:
sudo apt update
sudo apt install tcpdump
Check the version:
tcpdump --version
tcpdump version 4.99.4
libpcap version 1.10.4 (with TPACKET_V3)
OpenSSL 3.0.13 30 Jan 2024
tcpdump captures on one interface at a time. List the interfaces it can use:
sudo tcpdump -D
1.eth0 [Up, Running, Connected]
2.any (Pseudo-device that captures on all interfaces) [Up, Running]
3.lo [Up, Running, Loopback]
To find the interface that carries your public traffic, ask the kernel which one it uses to reach the internet:
ip route get 1.1.1.1
1.1.1.1 via 198.51.100.1 dev eth0 src 198.51.100.20 uid 1000
The name after dev is the interface. This guide uses eth0; replace it with yours (for example ens18). Use lo for traffic between local services, such as an application talking to a database on 127.0.0.1.
Step 2 - Running a first capture
Capture ten packets on eth0 and stop:
sudo tcpdump -i eth0 -nn -c 10 not port 22
-i eth0selects the interface.-nnshows IP addresses and port numbers instead of resolving names. Name resolution is slow and generates its own DNS traffic, which then shows up in the capture.-c 10stops after ten packets. Without it, tcpdump runs until you pressCtrl+C.not port 22excludes your own SSH session. Without it, every line tcpdump prints generates more SSH packets, which fill the screen.
A typical TCP line looks like this:
10:21:07.118532 IP 203.0.113.25.51544 > 198.51.100.20.443: Flags [S], seq 3120938841, win 64240, options [mss 1460,sackOK,TS val 1822 ecr 0,nop,wscale 7], length 0
Read it from left to right: the timestamp, the protocol (IP or IP6), the source address and port, the destination address and port, the TCP flags, the sequence number, the receive window, TCP options and the payload length. The flags are the most useful field:
| Flag | Meaning |
|---|---|
[S] | SYN, a client opens a connection |
[S.] | SYN-ACK, the server accepts it |
[.] | ACK only |
[P.] | PUSH with data |
[F.] | FIN, one side closes the connection |
[R] or [R.] | RST, the connection is refused or aborted |
Step 3 - Filtering traffic
Filters use the Berkeley Packet Filter syntax and are evaluated in the kernel, so only matching packets reach tcpdump. A precise filter is the difference between a readable capture and millions of irrelevant lines. Quote the filter when it contains parentheses or other characters the shell would interpret.
Filter by host, network or direction:
sudo tcpdump -i eth0 -nn host 203.0.113.25
sudo tcpdump -i eth0 -nn src net 192.0.2.0/24
sudo tcpdump -i eth0 -nn dst host 198.51.100.20
Filter by port or protocol:
sudo tcpdump -i eth0 -nn port 443
sudo tcpdump -i eth0 -nn udp port 53
sudo tcpdump -i eth0 -nn icmp
sudo tcpdump -i eth0 -nn portrange 8000-8100
Combine conditions with and, or and not:
sudo tcpdump -i eth0 -nn 'host 203.0.113.25 and (port 80 or port 443)'
Match TCP flags to see only connection attempts or resets. The first command shows SYN packets without ACK (new connections), the second any packet with RST set:
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) == tcp-syn'
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'
IPv6 traffic needs its own keyword, since icmp only matches ICMP for IPv4:
sudo tcpdump -i eth0 -nn ip6
sudo tcpdump -i eth0 -nn icmp6
Step 4 - Showing packet contents
For unencrypted protocols, printing the payload shows the actual request and response. -A prints it as ASCII, which suits HTTP, SMTP or Redis commands:
sudo tcpdump -i lo -nn -A -s 0 'tcp port 8080 and tcp[tcpflags] & tcp-push != 0'
The flag filter keeps only packets that carry data. -s 0 captures whole packets, which is already the default in current versions but makes the intent explicit. For binary protocols, -X prints the payload in hex and ASCII side by side.
Two more display options are useful in practice:
-vor-vvadds IP header details such as TTL and checksums, and decodes more of protocols like DNS.-ttttprints full dates, which helps when comparing with application logs.
Step 5 - Saving and reading capture files
Printing to the terminal is fine for quick checks. For anything longer, write raw packets to a pcap file with -w and analyze them afterwards with tcpdump or Wireshark.
On Ubuntu two protections affect where tcpdump can write: it drops root privileges to the tcpdump user before opening the output file, and its AppArmor profile only allows writing files with a .pcap extension. Write to /tmp and use the .pcap extension to avoid permission errors:
sudo tcpdump -i eth0 -nn -w /tmp/web.pcap -c 1000 'port 80 or port 443'
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
1000 packets captured
1003 packets received by filter
0 packets dropped by kernel
Check the last line: if the kernel dropped packets, the capture is incomplete and you should use a narrower filter. Read the file back, applying another filter if needed:
tcpdump -nn -r /tmp/web.pcap 'tcp[tcpflags] & tcp-rst != 0'
To open the capture in Wireshark, copy it to your local machine:
scp your_user@your_server_ip:/tmp/web.pcap .
For longer captures, limit the size and duration. -s 128 keeps only the first 128 bytes of each packet (enough for the headers), and -G with -W rotates to a new file every hour and stops after 24 files. The file name must contain a date format so each file gets a new name:
sudo tcpdump -i eth0 -nn -s 128 -G 3600 -W 24 -w '/tmp/eth0-%Y%m%d-%H%M.pcap' 'port 443'
Run long captures inside tmux or screen so they survive a dropped SSH session, and check free disk space with df -h /tmp before starting.
Step 6 - Troubleshooting real problems
Case 1: a client cannot connect to a web server
Capture the handshake between a specific client and the server port:
sudo tcpdump -i eth0 -nn 'host 203.0.113.25 and tcp port 443'
Then compare what you see with these patterns:
- SYN arrives, SYN-ACK goes back, then data: the network and the server are fine. Look at the application or TLS configuration.
- SYN arrives, RST goes back (
Flags [R.]): nothing listens on that port. Checksudo ss -tlnpon the server. - SYN arrives, no reply: the local firewall drops the packet. Check UFW, iptables or nftables rules.
- No SYN at all: the packet never reaches the server. The problem is upstream, in DNS, routing or a firewall in front of the server.
Case 2: slow or failing DNS resolution
Capture DNS queries and answers with verbose decoding:
sudo tcpdump -i eth0 -nn -v udp port 53
10:40:12.301442 IP (tos 0x0, ttl 64, id 3312, offset 0, flags [none], proto UDP (17), length 60)
198.51.100.20.40211 > 1.1.1.1.53: 45110+ A? example.com. (32)
10:40:12.313007 IP (tos 0x0, ttl 58, id 0, offset 0, flags [DF], proto UDP (17), length 76)
1.1.1.1.53 > 198.51.100.20.40211: 45110 1/0/0 example.com. A 93.184.215.14 (48)
Each query has an ID (45110) that the answer repeats, and the timestamps show how long the resolver took. Queries without answers, or answers containing NXDomain or ServFail, identify the failing lookup. On Ubuntu, applications query the local stub resolver at 127.0.0.53, so capture on lo to see which names they ask for and on eth0 to see what systemd-resolved sends upstream.
Case 3: checking which TLS name a client requests
HTTPS payloads are encrypted, but the start of a TLS handshake is not. With verbose output, tcpdump decodes the Server Name Indication (SNI) that the client sends in its ClientHello, which tells you which site a client is asking for on a server that hosts several:
sudo tcpdump -i eth0 -nn -v -c 20 'tcp dst port 443 and tcp[tcpflags] & tcp-push != 0'
For a detailed look at the handshake (versions, cipher suites, alerts), save a capture as in Step 5 and open it in Wireshark, which decodes TLS fully.
Troubleshooting
tcpdump: eth0: You don't have permission to perform this capture on that device: run tcpdump withsudo.tcpdump: /home/your_user/capture.pcap: Permission denied: tcpdump dropped privileges to thetcpdumpuser, or the file name lacks the.pcapextension required by AppArmor. Write to/tmp/name.pcapinstead.- No packets appear: you are capturing on the wrong interface or the filter is too strict. Try
-i anywith a broad filter such ashost 203.0.113.25. packets dropped by kernelis not zero: tcpdump could not keep up. Add a narrower filter, remove-vand payload printing, or write to a file with-winstead of the terminal.
Conclusion
You installed tcpdump, picked the right interface, filtered traffic by host, port, protocol and TCP flags, saved rotating pcap files and used captures to diagnose connection, DNS and TLS problems. As next steps, open your saved captures in Wireshark to use its protocol decoders and flow graphs, combine tcpdump with ss and mtr for a complete troubleshooting routine, and review your firewall rules when a capture shows packets arriving without replies.
