tcpdump for People Who Would Rather Use the Browser

Share
tcpdump for People Who Would Rather Use the Browser. Abstract tooling illustration in orange and dark grey on debugly.dev

I avoided tcpdump for years because I assumed it meant learning to read hex dumps. It does not. Ninety percent of what I use it for is answering yes or no questions: did the packet arrive, did we respond, how long did the handshake take, who sent the reset.

None of those require reading a byte of payload. They require knowing four flags and three filters.

This was written against tcpdump 4.99 on Ubuntu 24.04 and Debian 12. The syntax is the same on macOS with the caveat that the interface is usually en0 rather than any.

The only invocation you need to memorise

sudo tcpdump -ni any -s 0 -w /tmp/cap.pcap 'host api.example.com and port 443'
Flag What it does Why you want it
-n No DNS or port name resolution Resolution is slow and can hang mid capture
-i any All interfaces You rarely know which one the traffic uses
-s 0 Full packet, no truncation Default snaplen varies by version and loses TLS details
-w file Write to disk instead of printing Analyse later, in a proper tool, with no terminal noise

Write to a file. Always. Printing to the terminal feels productive and destroys the evidence the moment you need to look at it twice.

Then stop it with Ctrl-C and open /tmp/cap.pcap in Wireshark, or scp it to your laptop if the box is headless.

The three filters that cover almost everything

BPF syntax looks hostile and is actually three patterns.

By peer:

'host 10.0.4.17'
'src host 10.0.4.17'
'dst host 10.0.4.17'

By port, and remember that port means either end:

'port 5432'
'src portrange 30000-60000'

By TCP flags, which is where the debugging actually happens:

'tcp[tcpflags] & tcp-syn != 0'      # connection attempts
'tcp[tcpflags] & tcp-rst != 0'      # resets, the interesting ones
'tcp[tcpflags] & (tcp-fin|tcp-rst) != 0'   # everything that ends a connection

Combine with and, or and not. That is the whole language for our purposes.

The four questions tcpdump answers

Did the request ever reach this machine

sudo tcpdump -ni any 'dst port 8080 and tcp[tcpflags] & tcp-syn != 0' -c 10

If nothing prints while you fire requests at it, the traffic is not arriving. The problem is upstream: security group, firewall, ingress, DNS pointing somewhere else. You have just eliminated your entire application in ten seconds, which is worth more than any amount of log reading.

This is the same test that resolves the service is running, listening and completely unreachable.

Who is sending the reset

Resets are the highest signal event in a capture. Add -tttt for readable timestamps:

sudo tcpdump -ni any -tttt 'tcp[tcpflags] & tcp-rst != 0 and host 10.0.4.17'

Direction matters enormously. A reset from the peer means the remote end or something between you closed it. A reset from your own stack means your kernel refused the connection, usually because nothing was listening or the accept queue overflowed.

I chased an ECONNRESET for a day before realising from the source address that the reset was coming from the load balancer's idle timer, not the application. That is the sixty second ECONNRESET and a capture identifies it in about a minute.

How long did the handshake actually take

sudo tcpdump -ni any -tttt 'host api.example.com and port 443 and tcp[tcpflags] & tcp-syn != 0' -c 4

SYN, SYN-ACK, then the TLS client hello. The gap between the first two is your network round trip with no application involved. If that is 400ms, no amount of code optimisation is going to save you, and you should be looking at geography rather than your query.

The same breakdown without root access is available through curl, covered in the curl flag that tells you which part was slow.

Is the retransmission rate abnormal

sudo tcpdump -ni any -w /tmp/cap.pcap 'host 10.0.4.17' &
sleep 30; sudo pkill tcpdump
tshark -r /tmp/cap.pcap -q -z io,stat,0 | head

Or in Wireshark, filter tcp.analysis.retransmission. On a healthy datacentre link this should be near zero. A steady trickle means packet loss, and packet loss on TCP means latency, because every loss triggers a congestion window reduction.

Reading a capture without reading packets

Two Wireshark features do most of the work.

Statistics, Conversations, TCP. One row per connection with packet count, byte count and duration. Find the conversation that looks wrong and right click to filter to it. You now have one connection on screen instead of forty thousand packets.

Follow, TCP Stream. Reassembles the conversation. For plaintext protocols this shows you the actual HTTP request and response, which settles arguments about headers instantly. For TLS it shows you binary, which is still useful because you can see how much data moved and when.

Neither requires knowing what a TCP option is.

The two things that will bite you

tcpdump does not see loopback traffic the way you expect on some systems, and -i any has a quirk where it captures in Linux cooked mode, which changes the link layer header and occasionally confuses tshark filters. If you are debugging local traffic, use -i lo explicitly.

Capture files contain payloads. If you are capturing production traffic on a service that handles authentication, you are writing credentials and personal data to a file on disk. Capture on the narrowest filter you can, delete the file when you are done, and never attach a production pcap to a public issue.

That last one is not a theoretical concern. It is the reason I default to -s 96, which captures headers only, whenever the question I am asking is about connection behaviour rather than content. Headers answer almost everything above and carry none of the risk.

Learn the invocation, learn the three filters, and you will resolve more network arguments than most of the people on your team who do not use a browser. For the syscall level equivalent of the same skill, see strace for web developers.