tcpdump lets you see every packet flowing in and out of your machine. When your app says "connection refused" and you have no idea why, tcpdump shows you exactly what's happening on the wire.
It's installed on almost every Linux server by default. No GUI needed, no setup, just answers.
The basics
Capture all traffic on an interface:
# List available interfaces
tcpdump -D
# Capture on a specific interface
sudo tcpdump -i eth0
# Capture on all interfaces
sudo tcpdump -i any
You'll see output like this:
14:32:01.482839 IP 10.0.1.5.443 > 192.168.1.100.52314: Flags [P.], seq 1:248, ack 1, win 229, length 247
14:32:01.483012 IP 192.168.1.100.52314 > 10.0.1.5.443: Flags [.], ack 248, win 508, length 0
That's a lot of noise. The real power of tcpdump is in its filters.
Filtering traffic
By host
# Traffic to/from a specific host
sudo tcpdump -i any host 10.0.1.5
# Only traffic going TO a host
sudo tcpdump -i any dst host 10.0.1.5
# Only traffic coming FROM a host
sudo tcpdump -i any src host 10.0.1.5
By port
# All HTTP traffic
sudo tcpdump -i any port 80
# All HTTPS traffic
sudo tcpdump -i any port 443
# Multiple ports
sudo tcpdump -i any port 80 or port 443
# Port range
sudo tcpdump -i any portrange 8000-9000
By protocol
# Only TCP
sudo tcpdump -i any tcp
# Only UDP
sudo tcpdump -i any udp
# Only ICMP (ping)
sudo tcpdump -i any icmp
# Only DNS (UDP port 53)
sudo tcpdump -i any udp port 53
Combining filters
Filters compose with and, or, and not:
# HTTPS traffic to a specific host
sudo tcpdump -i any dst host 10.0.1.5 and port 443
# All traffic except SSH (so you don't capture your own session)
sudo tcpdump -i any not port 22
# DNS traffic from a specific source
sudo tcpdump -i any src host 192.168.1.100 and udp port 53
# HTTP or HTTPS but not from localhost
sudo tcpdump -i any '(port 80 or port 443) and not host 127.0.0.1'
That last one is important. Always exclude port 22 when capturing on a remote server or you'll flood your terminal with your own SSH session.
Useful flags you'll actually use
# -n: Don't resolve hostnames (faster output)
sudo tcpdump -i any -n port 80
# -nn: Don't resolve hostnames OR port names
sudo tcpdump -i any -nn port 80
# -c: Capture only N packets then stop
sudo tcpdump -i any -c 100 port 443
# -A: Print packet contents in ASCII (great for HTTP)
sudo tcpdump -i any -A port 80
# -X: Print packet contents in hex AND ASCII
sudo tcpdump -i any -X port 80
# -v, -vv, -vvv: Increasing verbosity
sudo tcpdump -i any -vv port 443
# -s0: Capture full packet (default truncates at 262144 bytes)
sudo tcpdump -i any -s0 port 80
# -r: Read from a saved capture file
tcpdump -r capture.dump
The -n flag alone makes tcpdump dramatically faster. Without it, tcpdump tries to reverse-DNS every IP address it sees, which adds latency and can even fail on isolated servers.
Real debugging scenarios
"The API isn't responding"
Your service calls an external API and gets timeouts. Is the request even leaving your server?
# Watch traffic to the API server
sudo tcpdump -i any -nn dst host api.example.com and port 443
# If you see SYN packets but no SYN-ACK, the remote isn't responding
# Flags [S] = SYN (your server trying to connect)
# Flags [S.] = SYN-ACK (remote server accepting)
# Flags [R.] = RST (connection refused)
What the TCP flags tell you:
- Only
[S] packets, no [S.] reply. Remote server isn't reachable. Check firewalls, security groups, or if the host is down.
[S] followed by [R.]. Port is closed. The service isn't running on that port.
[S] followed by [S.] then data. Connection works fine. Your problem is in the application layer.
"DNS isn't resolving"
# Watch all DNS queries
sudo tcpdump -i any -nn udp port 53
# You'll see something like:
# 10.0.1.5.41234 > 8.8.8.8.53: A? api.example.com
# 8.8.8.8.53 > 10.0.1.5.41234: A api.example.com 93.184.216.34
If you see the query going out but no response coming back, your DNS server is unreachable. If you see an NXDomain response, the domain doesn't exist.
"Something is making unexpected network calls"
# Capture all outbound connections and show destination IPs
sudo tcpdump -i any -nn 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'
# This captures only SYN packets (new outbound connections)
# Each line shows exactly where your server is trying to connect
This is invaluable for finding rogue connections, misconfigured services, or unexpected external calls from your application.
"The TLS handshake is failing"
# Capture TLS traffic with full packet contents
sudo tcpdump -i any -nn -X port 443 and host api.example.com
# Look for the Client Hello and Server Hello
# If you see the Client Hello but no Server Hello,
# the server is rejecting the TLS connection
# Save to a file for deeper inspection
sudo tcpdump -i any -w tls-debug.dump port 443 and host api.example.com
# Read it back and filter for RST packets
tcpdump -r tls-debug.dump -nn 'tcp[tcpflags] & tcp-rst != 0'
"Is the load balancer forwarding traffic?"
# On the backend server, check if traffic arrives from the load balancer
sudo tcpdump -i any -nn src host 10.0.0.50 and port 8080
# No packets? The LB isn't forwarding. Check health checks and routing rules.
# Packets arriving but app not responding? Problem is in your application.
Saving and analyzing captures
For anything beyond quick debugging, save the capture and analyze it with tcpdump's own filters:
# Capture with rotation (new file every 100MB, keep 10 files)
sudo tcpdump -i any -w capture.dump -C 100 -W 10 port 443
# Capture with time-based rotation (new file every 3600 seconds)
sudo tcpdump -i any -w capture_%Y%m%d_%H%M%S.dump -G 3600 port 443
# Read back with filters (no sudo needed for reading files)
tcpdump -r capture.dump -nn host 10.0.1.5
tcpdump -r capture.dump -nn 'tcp[tcpflags] & tcp-rst != 0'
The -C and -W flags prevent captures from filling up your disk. On a busy production server, an unrotated capture file can eat gigabytes in minutes.
Quick reference
The commands I use most, condensed:
# Debug API connectivity
sudo tcpdump -i any -nn dst host API_IP and port 443
# Watch DNS
sudo tcpdump -i any -nn udp port 53
# Find unexpected outbound connections
sudo tcpdump -i any -nn 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'
# Read HTTP request/response bodies (unencrypted only)
sudo tcpdump -i any -A -s0 port 80
# Capture everything except SSH for later analysis
sudo tcpdump -i any -w debug.dump not port 22
# Count packets per host
sudo tcpdump -i any -nn -c 1000 | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn
Takeaways
-
Always use -n or -nn. Skipping DNS resolution makes output faster and avoids confusing failures on isolated servers.
-
Filter aggressively. Unfiltered tcpdump on a busy server is useless. Start with host and port filters.
-
Exclude port 22 on remote servers. Unless you enjoy watching your own SSH packets scroll past.
-
Read the TCP flags. [S] is SYN, [S.] is SYN-ACK, [R.] is RST, [P.] is PUSH. These tell you exactly where a connection is failing.
-
Save captures for complex issues. Terminal output is fine for quick checks, but multi-step flows are easier to analyze when you can replay them with tcpdump -r and different filters.
-
Use rotation flags in production. -C and -W prevent captures from eating all your disk space.
tcpdump has saved me more times than I can count. Production issues where logs showed nothing, services that "should be working," network problems that nobody could explain. Every time, tcpdump cut through the noise and showed exactly what was happening on the wire.
It doesn't care about your application's abstractions. When every other debugging tool tells you "it should work," tcpdump shows you why it doesn't.