Kali Linux Networking for DevOps · Part 12 of 15
tcpdump for DevOps Engineers: Kali Linux Tutorial
Series curriculum (15 lessons)
Every tool we have used so far in this series tells you a story about the network. curl tells you a request failed. An application log says “connection reset by peer.” A monitoring dashboard shows elevated latency. These are all interpretations, filtered through layers of libraries and assumptions, and they are frequently misleading. When the stories stop making sense, you stop asking the software what it thinks happened and go read the record of what actually happened. That record is the packets on the wire, and tcpdump is how you read them.
tcpdump shows what the network actually did, not what the application claims happened.
Keep that sentence in your head for the whole lesson, because it is the entire reason packet capture is worth learning. An application can lie to you — not maliciously, but because it only sees the world through a socket API and a stack of retry logic. The packets cannot lie; they are the ground truth. When testing connectivity, reading ports and listening services, and staring at logs have all left you with contradictory evidence, tcpdump is the tiebreaker.
We will build the skill properly: capturing on an interface, narrowing the flood with filters, saving captures for later, and — most importantly — reading the output well enough to recognize a healthy handshake, a refused connection, a silent timeout, and a DNS lookup on sight.
What You Will Learn
- How to capture live traffic on an interface with
tcpdump -i - How to filter the flood by host, port, and protocol so you see only what matters
- How to write captures to a
.pcapfile for later analysis in Wireshark - How to read a TCP three-way handshake and know a connection succeeded
- How to recognize connection refused (
RST) versus a silent timeout (retries) - How to spot DNS queries and responses in a capture
- How to capture safely and legally, including inside Docker containers with the right Linux capability
- A repeatable workflow for using capture as the application-versus-network tiebreaker
Why Packet Capture Is Different
Most of the diagnostics in this series read state: the routing table, the ARP cache, listening sockets, DNS records. State tells you how the machine is configured. Packet capture reads events — the actual frames arriving and leaving in real time. That distinction matters because plenty of failures leave the configuration looking perfect while the traffic tells a different tale. A firewall silently dropping packets does not change your routing table. A server sending a TCP reset does not show up in ss. You only see these things by watching the wire.
tcpdump puts the interface into a mode where the kernel copies every matching frame up to the tool, which decodes the headers and prints a one-line summary per packet. It ships on essentially every Unix-like system and is the reference implementation for the BPF (Berkeley Packet Filter) syntax that Wireshark and dozens of other tools reuse — learn tcpdump and you have learned the filter language for the whole ecosystem.
🔐 Security Note — Only capture on interfaces and systems you own or are explicitly authorized to assess. Packet capture reads everyone’s traffic on that interface, not just yours: credentials, tokens, cookies, and personal data can all land in a capture. Treat every
.pcapfile as sensitive — store it with tight permissions, never paste raw captures into a ticket or chat, and delete them when the investigation is done. This is authorized infrastructure validation, not surveillance.
Capturing on an Interface
The most basic invocation names the interface to listen on:
tcpdump -i eth0
-i eth0 tells tcpdump to capture on the eth0 interface. If you are not sure which interface carries the traffic you care about, list them first with ip -br addr (from the interfaces lesson) or pass -i any to capture across all of them. You will need sudo, because reading raw packets is privileged.
Run this on a busy host and you get a firehose — every packet scrolls past faster than you can read. That is expected, and it is exactly the problem filters solve. But first, look at the shape of a single line:
14:22:31.442 IP 10.0.0.5.51524 > 10.0.0.9.443: Flags [S], seq 12, win 64240
Read left to right: a timestamp, the protocol (IP), the source address.port, a > arrow, the destination address.port, then the TCP details after the colon. Flags [S] is the TCP flag field — here, a SYN. Once you can parse that one line, you can read a capture. Everything else is volume and filtering.
🔎 Troubleshooting Tip — Add
-nto stoptcpdumpfrom doing reverse-DNS and port-name lookups. Without it, the tool fires off its own DNS queries to turn10.0.0.9into a hostname and443intohttps, which both slows the output and, confusingly, injects DNS traffic into the very capture you are trying to read. Almost always run with-n.
tcpdump -n -i eth0
Filtering by Host
The first way to cut the flood down is to name the machine you care about:
tcpdump -n -i eth0 host 192.168.1.10
host 192.168.1.10 keeps only packets where that address is either the source or the destination. This is the filter you reach for when you are debugging traffic to one specific server — a database, an upstream API, a load balancer. Everything to and from that host, nothing else.
You can be more precise when you only care about one direction. src host 192.168.1.10 keeps only packets from that address; dst host 192.168.1.10 keeps only packets to it. When you are chasing “is the server even replying?”, src host <server> isolates the answer immediately: if you see request packets leave but nothing comes back with the server as source, the server is not responding.
Filtering by Port
The second axis is the port — which service the traffic belongs to:
tcpdump -n -i eth0 port 443
port 443 keeps only packets with 443 as the source or destination port, so you see HTTPS traffic and ignore everything else. Swap in port 22 for SSH, port 5432 for PostgreSQL, port 53 for DNS. This is how you answer “is anything actually reaching the service on this port?” — which pairs naturally with the ports and listening services lesson, where you confirmed something was listening. Here you confirm traffic is arriving.
Filters combine with and, or, and not. To watch HTTPS traffic to one specific host and nothing else:
tcpdump -n -i eth0 host 192.168.1.10 and port 443
That expression is the workhorse of real debugging: one host, one port, the exact conversation you are investigating and nothing to distract you.
Filtering by Protocol
The third axis narrows by transport protocol, which matters because — as you learned in TCP vs UDP — the two behave completely differently on the wire.
tcpdump -n -i eth0 tcp
tcp keeps only TCP segments. This is what you want when you are reasoning about connections, handshakes, and resets, because all of that machinery is TCP-specific. UDP has none of it.
tcpdump -n -i eth0 udp
udp keeps only UDP datagrams. Reach for this when you are looking at DNS, DHCP, NTP, or any of the fire-and-forget protocols. A common combination is udp and port 53 to isolate DNS, which we will use shortly. Protocol filters stack with host and port filters exactly like the others: tcp and host 192.168.1.10 and port 443 is a perfectly ordinary, and very useful, expression.
Writing Captures to a File
Reading packets live is great for a fast-moving problem, but often you want to save traffic — to analyze it later, to hand it to a colleague, or to open it in a richer tool. That is what -w does:
tcpdump -n -i eth0 -w capture.pcap host 192.168.1.10 and port 443
-w capture.pcap writes raw packets to a file in the standard pcap format instead of printing decoded lines to your terminal. The file is not human-readable text — it is the binary record of the packets, headers and payloads intact. You open it later with tcpdump -r capture.pcap to replay it, or, far more comfortably, in Wireshark, whose graphical decoder is the subject of the next lesson.
Two flags make file captures manageable:
-c Nstops after capturingNpackets.tcpdump -c 100 -w capture.pcap ...grabs exactly 100 packets and exits — perfect for a bounded sample so a capture does not grow without limit.-n, as always, keepstcpdump’s own DNS lookups out of the file.
tcpdump -n -c 100 -i eth0 -w capture.pcap port 443
This says: capture on eth0, only port-443 traffic, stop after 100 packets, resolve nothing, write it all to capture.pcap. It is a clean, self-terminating capture you can safely leave running.
🏭 Why This Matters in Production — Intermittent failures are the hardest bugs because they are gone by the time you start looking. The fix is a bounded, filtered, file-based capture left running on the affected host:
-w, a tight filter, and-cor a rotation flag so it never fills the disk. When the failure recurs, you already have the packets. A capture you started after the incident proves nothing; a capture that was already running proves everything.
Reading the Signatures
Filtering gets you to the right packets. Now the real skill: recognizing what a handful of packets means. These four signatures cover most connection-level diagnostics.
A Healthy Handshake — SYN / SYN-ACK / ACK
Every TCP connection opens with a three-way handshake. When it completes, the connection is established and data can flow. In a capture with -n it looks like this:
10.0.0.5.51524 > 10.0.0.9.443: Flags [S]
10.0.0.9.443 > 10.0.0.5.51524: Flags [S.]
10.0.0.5.51524 > 10.0.0.9.443: Flags [.]
Read the flags: [S] is SYN, [S.] is SYN-ACK (the . means the ACK flag is set alongside SYN), and [.] is a bare ACK. The client asks to connect (SYN), the server agrees and acknowledges (SYN-ACK), the client acknowledges the agreement (ACK). Three packets, both directions, done:
client server
|------------ SYN -------->|
|<-------- SYN-ACK --------|
|------------ ACK -------->|
| connection ESTABLISHED |
When you see this sequence, the network path is fine and the service accepted the connection. If your application still reports a problem after a clean handshake, the fault is above the transport layer — TLS, HTTP, authentication, application logic. You have just moved the investigation up the stack.
Connection Refused — SYN / RST
Sometimes the client’s SYN gets an immediate, blunt rejection:
10.0.0.5.51524 > 10.0.0.9.80: Flags [S]
10.0.0.9.80 > 10.0.0.5.51524: Flags [R.]
[R.] is an RST (reset). The client sent SYN; the server replied “no” and tore the connection down instantly. This is the packet-level fingerprint of connection refused, and it tells you something precise: you reached the host — it is up, routable, and answered — but nothing is listening on that port, or a local firewall on the server actively rejected the connection. The problem is not the network path. It is the service: not started, bound to the wrong address, or crashed. This maps directly to the “connection refused → nothing listening / wrong port” signature you have seen throughout the series.
Timeout — SYN / SYN / SYN, No Reply
The most frustrating signature is the one where nothing comes back at all:
10.0.0.5.51524 > 10.0.0.9.443: Flags [S]
10.0.0.5.51524 > 10.0.0.9.443: Flags [S]
10.0.0.5.51524 > 10.0.0.9.443: Flags [S]
Three SYNs, all from the client, all to the same destination, spaced a few seconds apart — and no SYN-ACK, no RST, nothing in return. The client keeps retransmitting because it never heard back. This is the fingerprint of a connection timeout, and it means the packets are being silently dropped: a firewall configured to DROP (not REJECT), a security group or network ACL blocking the port, a network policy in Kubernetes, or a host that is simply down or off the network.
The crucial contrast: RST is a reply, silence is not. Connection refused means someone answered “no.” Timeout means no one answered at all. A DROP rule produces silence; a REJECT rule produces an RST. Being able to tell these two apart from the capture — one packet coming back versus none — is exactly the kind of ground truth that logs almost never give you, because to the application both just look like “it didn’t connect.”
refused: SYN --> <-- RST (host answered, port closed)
timeout: SYN --> (silence) (packets dropped somewhere)
SYN --> (silence)
SYN --> (silence)
DNS — Query and Response on UDP/53
Name resolution is usually a UDP round trip to port 53. Filter for it and you can watch resolution happen:
tcpdump -n -i eth0 udp and port 53
10.0.0.5.40213 > 10.0.0.2.53: A? api.example.com
10.0.0.2.53 > 10.0.0.5.40213: A 93.184.216.34
The first line is the client asking the DNS server (A?) for the A record of api.example.com. The second is the server answering with the address. Two datagrams, client to server on port 53 and back:
client --- A? api.example.com ---> DNS server
client <-- A 93.184.216.34 ------- DNS server
If you see the query leave but no response come back, resolution is failing at the server or on the path — the “could not resolve host” signature, and a cue to revisit DNS troubleshooting. If you see the response but it carries the wrong address, you have found a misconfiguration or stale record that no amount of application-log reading would have revealed. Notice, too, why -n matters here: without it, tcpdump’s own name lookups would clutter this exact view with extra port-53 traffic.
Capturing Inside Containers
Packet capture is enormously useful inside containerized environments, where the network is a stack of virtual bridges and the “it works on my host but not in the pod” class of problem lives. But capturing raw packets is a privileged operation, and there is a right and a wrong way to grant that privilege.
By default a container cannot capture, because it lacks the NET_RAW Linux capability — the specific permission required to open raw sockets. The correct fix is to grant only that capability:
docker run --rm -it --cap-add NET_RAW kali-net-lab tcpdump -n -i eth0
--cap-add NET_RAW adds the single capability tcpdump needs and nothing more. This is the principle of least privilege applied to packet capture: the container gets exactly the power required for the job.
🔐 Security Note — Do not reach for
--privilegedto maketcpdumpwork.--privilegedhands the container nearly all host capabilities and effectively removes the isolation boundary; a compromise inside that container becomes a compromise of the host. And never mount/var/run/docker.sockinto a capture container — the Docker socket is root-equivalent control over the entire engine.--cap-add NET_RAWgrants the one capability capture needs; the alternatives grant everything. The narrow grant is not just tidier, it is the difference between a contained blast radius and none.
In this series’ Docker network lab, the Kali container is built with a captures/ directory bind-mounted from the host. Write your .pcap files there with -w /captures/incident.pcap so they survive the container being removed and can be opened on the host in Wireshark. For more on the tool itself, the getting-started tcpdump for DevOps walkthrough is a gentler complement to this lesson; if you are on the Docker-focused path, the Kali on Docker series and its dedicated tcpdump packet capture lesson go deeper on capturing across user-defined bridge networks.
DevOps Perspective
🛠️ DevOps Perspective — Packet capture is the tiebreaker. Half of production incidents devolve into an argument: the app team says the network is dropping traffic, the network team says the app is broken, and the logs are ambiguous enough to support both stories. A five-minute filtered capture settles it with evidence instead of opinion. See a clean SYN / SYN-ACK / ACK to the dependency? The network delivered the connection — the bug is in the application. See three lonely SYNs and no reply? The app is fine and something is dropping packets between here and there. Reset on connect? The port is closed. You stop guessing which layer is broken and start observing it. In the troubleshooting stack — Application → TLS → Port → DNS → Gateway → Route → Interface — a capture lets you pin the failure to a specific layer instead of restarting services and hoping. That is the whole job: identify the layer before you touch anything.
Try It Yourself
🧪 Try It — Work through these on a host or lab you own. Each one trains you to read a specific signature rather than just run a command.
- See a healthy handshake. In one terminal run
sudo tcpdump -n -i any tcp and port 443. In another,curl https://example.com. Find the SYN / SYN-ACK / ACK. Confirm the connection established before any TLS or HTTP appeared.- Force a refusal. Capture
tcp and port 9999(a port with nothing listening), thencurl http://localhost:9999. Watch for the SYN followed immediately by an RST. That is “connection refused” on the wire.- Create a timeout. Capture traffic to a firewalled or non-routable address and try to connect. Watch the client fire repeated SYNs with no reply. Contrast the silence against the RST from step 2 — same failure to the app, completely different packets.
- Watch DNS. Run
sudo tcpdump -n -i any udp and port 53, thendig api.example.comor a plaincurl. Identify theA?query and the response. Note the resolved address.- Save and reopen. Repeat step 1 with
-w handshake.pcap -c 20, then open the file in Wireshark (next lesson) to see the same packets in a graphical decoder.
Common Problems
“tcpdump: eth0: You don’t have permission to capture.” Raw capture is privileged — run with sudo, or in a container add --cap-add NET_RAW. This is a permissions problem, not a tcpdump bug.
Nothing shows up at all. Your filter is probably too tight, or you are on the wrong interface. Drop the filter, or use -i any, confirm you see some traffic, then add filter terms back one at a time. A filter that matches nothing looks identical to a network with no traffic — rule out the filter first.
The output is a firehose you can’t read. Add filters (host, port, tcp/udp) and a bound like -c 200. You are not meant to read a live flood; you are meant to narrow it to the conversation you care about.
Reverse-DNS spam or unexpected port-53 packets in the capture. You forgot -n, so tcpdump is generating its own lookups. Add -n and re-run.
Container traffic is invisible from the host. It often rides a veth/bridge interface, not eth0. Capture on the bridge, use -i any, or capture from inside the container with --cap-add NET_RAW.
Troubleshooting Workflow
When capture is your tiebreaker, follow a disciplined loop rather than staring at scrolling packets:
Observe → app reports a failure; logs are ambiguous
Hypothesis→ "the network is dropping the connection"
Test → tcpdump -n -i any host <dep> and port <p>
Evidence → which signature appears?
SYN/SYN-ACK/ACK → network OK, look UP the stack
SYN/RST → port closed / nothing listening
SYN/SYN/SYN → dropped: firewall / SG / policy
DNS query, no reply → name resolution failing
Identify → pin the failure to ONE layer of the stack
Correct → fix that layer (start service, open firewall, fix DNS)
Validate → re-capture; confirm the healthy signature appears
The last step is the one people skip. A fix is not done until a fresh capture shows the healthy signature. The packets that diagnosed the problem are the same packets that prove you solved it.
What You Learned
tcpdump -i eth0captures live traffic on an interface;-i anyspans them all, and-nkeepstcpdump’s own DNS lookups out of your view.- Filters narrow the flood along three axes:
host <ip>(a machine),port <n>(a service), andtcp/udp(a protocol) — and they combine withand,or, andnot. -w capture.pcapsaves raw packets for Wireshark;-c Nbounds the capture so it self-terminates.- SYN / SYN-ACK / ACK means the connection established — the network is fine, look higher up the stack.
- SYN / RST means connection refused — you reached the host but nothing is listening on that port.
- SYN / SYN / SYN with no reply means a silent timeout — packets are being dropped by a firewall, security group, or policy. RST is an answer; silence is not.
- A DNS query on UDP/53 with no response means name resolution is failing on the server or path.
- Capture in containers with
--cap-add NET_RAWonly — never--privileged, never a mounteddocker.sock— and write.pcapfiles to the lab’s bind-mountedcaptures/directory. - Handle every capture as sensitive data, and only ever capture on systems you own or are authorized to assess.
- Packet capture is the tiebreaker that separates a network problem from an application problem when the logs cannot.
Because remember: tcpdump shows what the network actually did, not what the application claims happened. That is exactly why it settles arguments.
You can now capture the truth from the wire and read the essential signatures from a scrolling terminal. The next step is to open those same .pcap files in a graphical decoder that reassembles whole conversations, follows TCP streams, and dissects every protocol field for you. Continue to Part 13: Wireshark for DevOps, where we turn these raw packets into readable, clickable analysis.
Recommended Reading
- View Book on Amazon Affiliate link
Mastering Kali Linux for Advanced Penetration Testing
An advanced deep-dive into Kali for experienced security testers.
- View Book on Amazon Affiliate link
Kali Linux Penetration Testing Bible
A comprehensive reference for structured security-testing workflows with Kali.
- View Book on Amazon Affiliate link
Mastering Hacking With Kali Linux
A practical guide to security-testing techniques with Kali Linux.
Affiliate Disclosure: Some links on this page are affiliate links. If you purchase through one of these links, DevOps AI Toolkit may earn a commission at no additional cost to you. See our affiliate disclosure.
← Back to Kali Linux Networking for DevOps Back to Kali Linux