The Packet Never Lies: Advanced tcpdump Recipes for the Enterprise Engineer

The Power of Raw Packets

APM dashboards lie. Not maliciously — they lie the way any abstraction lies, by reporting the world through a lens that was built for a different question. A 200ms p99 spike in your APM tells you something is slow. It will not tell you that the spike is concentrated on flows that traverse a particular ECMP path, or that it correlates with a burst of TCP retransmissions on one specific uplink, or that a middlebox three hops away is silently clamping MSS and forcing fragmentation. Syslog is worse: it tells you what a device decided to log, which is a curated, lossy, after-the-fact summary of events that the vendor’s engineers thought you might care about. When the problem is novel — and the interesting ones always are — that curation works against you.

The raw packet does not have an opinion. It is not aggregated, sampled, rate-limited, or summarised. It is the actual bytes that left one interface and arrived at another, in order, with timestamps. When every other layer of instrumentation is telling you a story that doesn’t add up, the wire is where the story falls apart and the truth shows through. This post assumes you already believe that — you’ve run tcpdump -i eth0 host x.x.x.x a thousand times — and want to go further: precise bitwise filters that isolate exactly the packets you care about, recipes for the failure modes that actually show up in enterprise networks, a safe way to stream live captures into Wireshark without melting the link or capturing your own session in an infinite loop, and a working knowledge of how to do all of this when the box in front of you isn’t Linux at all.

If you haven’t already, my tcpdump deep dive on BPF filters, capture rotation, and FortiGate cross-mapping covers the fundamentals — command anatomy, -s 0, ring-buffer rotation with -W/-G/-C, and a side-by-side cheat sheet against diagnose sniffer packet. This post picks up where that one left off and goes deep on the filter expressions and recipes that separate “I ran a capture” from “I found the answer in four minutes.”

Section 1: The Magic of Bitwise Masking

Most engineers filter on host and port and stop there. That gets you which conversation. It does not get you which packet within that conversation — and in a TCP troubleshooting scenario, the packet that matters is very often one specific flag combination buried in a few hundred thousand otherwise-uninteresting ACKs.

BPF lets you reach directly into a header field as a byte offset and run a bitwise mask against it. The syntax looks intimidating the first time and trivial the tenth. The general form is:

proto[offset:size] & mask comparison value

proto is the protocol the offset is relative to (tcp, ip, icmp, ether…), offset is the byte position within that protocol’s header, size is how many bytes to read (1, 2, or 4), and mask is the bitmask you AND against before comparing. tcpdump gives you named shortcuts for the common TCP flag bits — tcp-syn, tcp-ack, tcp-fin, tcp-rst, tcp-push, tcp-urg — which expand to the correct mask values so you rarely need to write tcp[13] by hand. But understanding what they expand to is what lets you build the filter that isn’t in anyone’s cheat sheet.

The TCP flags live in byte 13 of the TCP header (tcp[13]), and each flag is a single bit:

FlagBitHex mask
FIN00x01
SYN10x02
RST20x04
PSH30x08
ACK40x10
URG50x20

Catching naked SYNs (connection attempts, not handshake replies)

A “naked SYN” — SYN set, ACK clear — is a connection attempt. A SYN with ACK set is a connection reply. Conflating the two is how people misdiagnose load and capacity problems; if you’re counting “new connection attempts” and you’re actually counting SYN-ACKs too, your numbers are wrong by a factor that depends on your success rate.

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

Read the right-hand side carefully — this is the pattern that trips people up. tcp[tcpflags] & tcp-syn != 0 only tells you the SYN bit is set; it says nothing about ACK, so it will also match SYN-ACKs. By masking against (tcp-syn|tcp-ack) first and comparing the masked result to tcp-syn, you require SYN to be set and ACK to be clear, in one expression. This is the canonical “naked SYN” filter, and it is the one you want when you’re counting genuine new-connection attempts — for capacity planning, for spotting a SYN flood, or for catching a client that’s retrying a connection that never completes.

SYN-ACK and RST-ACK in a single filter

Troubleshooting a connection that’s being killed mid-negotiation means watching for the server’s two possible replies to a SYN: it accepts (SYN-ACK) or it refuses (RST-ACK). You want both in one capture so you can see, in sequence, which one actually happens — and how often each does, when the symptom is intermittent.

tcpdump -i any -nn \
  'tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack) or
   tcp[tcpflags] & (tcp-rst|tcp-ack) == (tcp-rst|tcp-ack)'

Each clause uses the same “mask, then compare to the full combination” pattern: SYN-ACK requires both bits set and nothing else checked (so it’ll still match SYN-ACK with ECN bits riding along, which is correct — you don’t want CWR/ECE flags to make you miss a legitimate SYN-ACK). The OR combines two independent flag-pair checks into one BPF expression, which means a single pass over the wire gives you the full accept-or-reject picture for every attempted connection. This is the filter you reach for when “sometimes connections work and sometimes they get refused immediately” — point it at the server side and watch which reply dominates, and when the ratio shifts.

Isolating packets that actually carry data

Pure ACKs dominate any TCP capture by packet count and tell you almost nothing on their own. When you’re chasing an application-layer problem — a slow API response, a stalled file transfer, a stuck upload — you want only the packets that carry payload, because that’s where the actual conversation is happening.

tcpdump -i any -nn \
  'tcp and (ip[2:2] - ((ip[0] & 0x0f) * 4) - (tcp[12] >> 4) * 4) > 0'

This one is worth unpacking fully because the arithmetic is the same arithmetic you’ll reuse for the HTTP filter later:

  • ip[2:2] — the IP total length field (2 bytes, network byte order — tcpdump handles the endianness for you).
  • (ip[0] & 0x0f) * 4 — the IP header length. The low nibble of the first IP byte is the header length in 32-bit words, so multiply by 4 to get bytes. This matters because IP options can make the header longer than the usual 20 bytes, and a filter that assumes a fixed 20-byte header will misfire on any packet carrying options.
  • (tcp[12] >> 4) * 4 — the TCP header length, same idea: the high nibble of byte 12 is the data-offset field in 32-bit words.
  • Subtract both header lengths from the total IP length, and what’s left is the TCP payload size. If it’s greater than zero, the packet is carrying application data.

Compare this with the lazy version people sometimes reach for, tcp[tcpflags] & tcp-push != 0 — using PSH as a proxy for “has data.” It’s a heuristic, not a guarantee: middleboxes and some stacks set PSH inconsistently, and large transfers frequently send full-MSS segments without PSH set on every one. The arithmetic version is exact regardless of how any particular stack chooses to set its flags, which matters when the stack you’re debugging is the thing under suspicion.

Section 2: Enterprise Troubleshooting Recipes

Asymmetric routing detection

Asymmetric routing is the silent killer of stateful security and load-balancing infrastructure — a SYN goes out one path, the SYN-ACK comes back another, and a stateful firewall in the return path drops it because it never saw the original SYN establish state. The symptom from the application’s point of view is “connections hang and then time out,” which is indistinguishable from a dozen other problems unless you can see both directions of the same flow on the same box.

The recipe is to run the same precise filter, simultaneously, on every interface that could plausibly carry the flow, and diff what you see:

# On the suspected "outbound" interface — do we see SYNs leaving?
tcpdump -i eth0 -nn -w /var/tmp/eth0-out.pcap \
  'host 10.20.30.40 and tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'

# On the suspected "return" interface — do we see the SYN-ACK arriving?
tcpdump -i eth1 -nn -w /var/tmp/eth1-in.pcap \
  'host 10.20.30.40 and tcp[tcpflags] & (tcp-syn|tcp-ack) == (tcp-syn|tcp-ack)'

# Then correlate by sequence number / IP ID, not just by eyeballing counts
tcpdump -nn -r eth0-out.pcap -e -tt | awk '{print $1, $3, $5}' > out.txt
tcpdump -nn -r eth1-in.pcap  -e -tt | awk '{print $1, $3, $5}' > in.txt
diff <(cut -d' ' -f2- out.txt) <(cut -d' ' -f2- in.txt)

The tell isn’t “no traffic on one side” — it’s partial traffic: SYNs leaving on eth0 with no corresponding SYN-ACKs arriving on eth0, while eth1 shows SYN-ACKs arriving for connections it never saw a SYN for. That pattern, captured simultaneously on both interfaces with synchronised timestamps (-tt for epoch-with-microseconds — never rely on relative time when correlating across captures), is close to a smoking gun for an asymmetric path. The follow-up question is why — ECMP hashing on the upstream router, a second default route, policy-based routing on a firewall — but you can’t even start asking it until you’ve proven the asymmetry exists at the packet level.

A second variant of the same idea: data without a handshake. If you see established-looking data flow (PSH/ACK packets with payload) on an interface where you never captured the SYN for that 4-tuple, either your capture started after the connection was already up (check your timestamps against the TCP sequence numbers — a mid-stream sequence number well above the ISN is the giveaway), or the SYN genuinely took a different path. Both are useful answers; the filter that gets you there is just the data-bearing-packet filter from Section 1, scoped to the suspect host, watched on every candidate ingress.

Latency and packet loss: retransmissions and duplicate ACKs

Retransmissions and duplicate ACKs are TCP’s own instrumentation for loss and reordering — the stack is telling you, in the wire format, exactly when and where it had to recover from something going wrong. You don’t need to guess; you need to know how to ask tcpdump (or, more honestly, tshark, because retransmission detection requires stream-level state that BPF alone can’t express) to surface them.

# Live: flag retransmissions and dup-acks as they happen, with relative seq/ack numbers
tcpdump -i any -nn -S 'tcp port 443' | \
  awk '/ack/ && seen[$0]++ {print "DUP-ACK candidate:", $0} {print}'

# The honest way — let tshark do the stream analysis tcpdump can't
tshark -i eth0 -f 'tcp port 443' \
  -Y 'tcp.analysis.retransmission or tcp.analysis.duplicate_ack or tcp.analysis.fast_retransmission' \
  -T fields -e frame.time_relative -e ip.src -e ip.dst -e tcp.analysis.retransmission \
  -e tcp.analysis.duplicate_ack -e tcp.analysis.fast_retransmission

# From a saved capture: count and bucket by direction
tshark -r cap.pcap -Y 'tcp.analysis.retransmission' -T fields -e ip.src | sort | uniq -c | sort -rn
tshark -r cap.pcap -Y 'tcp.analysis.duplicate_ack'   -T fields -e ip.src | sort | uniq -c | sort -rn

Why lean on tshark here rather than fighting BPF into submission: a retransmission is not a property of a single packet. It’s a relationship between two packets — “this segment’s sequence number was already seen, and it wasn’t a window-probe or a keepalive.” Detecting that requires Wireshark’s stream-reassembly engine, which tracks per-flow state across the whole capture. BPF filters operate packet-by-packet with no memory of what came before; they simply cannot express “have I seen this sequence number in this direction of this flow already.” Use tcpdump to capture (it’s lighter weight and safer on a production interface), then hand the pcap to tshark to analyse. That division of labour — tcpdump for collection, tshark/Wireshark for interpretation — is the production pattern, and fighting it by trying to do stream analysis in a live BPF expression is a good way to waste an afternoon.

The interpretation, once you have the numbers: retransmissions sourced from your side, clustered on flows to one destination or one path, point at loss between you and that destination. Duplicate ACKs arriving from the far end, three or more in a row for the same sequence number, are the receiver telling the sender “I’m missing a segment, and I keep noticing because more data keeps arriving out of order” — that’s the trigger condition for fast retransmit, and a high rate of it on otherwise-healthy-looking links is one of the more reliable early indicators of a marginal physical link or a congested queue that hasn’t yet started dropping enough to show up in interface counters.

HTTP and application-layer sniffing from the CLI

Sometimes you need the actual request — the path, the headers, the method — and you need it now, without exporting a pcap to a workstation. The byte-offset arithmetic from Section 1 generalises directly: skip past the IP and TCP headers, then match literal bytes against the start of the payload.

# GET requests — "GET " is 0x47455420
tcpdump -i any -nn -A -s 0 \
  'tcp port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420)'

# POST requests — "POST" is 0x504f5354
tcpdump -i any -nn -A -s 0 \
  'tcp port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354)'

# HTTP responses — "HTTP" is 0x48545450 (useful for catching 5xx storms at the wire level)
tcpdump -i any -nn -A -s 0 \
  'tcp port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x48545450)'

(tcp[12:1] & 0xf0) >> 2 is the same TCP-header-length calculation from the data-payload filter, repurposed as an offset rather than a subtraction — “start reading this many bytes into the segment, from the start of the TCP header, and that’s where the payload begins.” The four-byte literal comparison is just the ASCII bytes of the method or status line, written as a big-endian hex integer.

This is plaintext-only, obviously — anything on 443 is TLS, and you cannot do this trick against it without terminating or mirroring decrypted traffic. But an enormous amount of internal enterprise traffic is still HTTP in the clear: health checks, internal APIs, legacy integrations, monitoring agents talking to collectors. When one of those starts misbehaving, this is the fastest way on earth to see the literal bytes of the request that’s failing, with no proxy, no instrumentation change, and no risk of the observation itself altering the behaviour.

For TLS, you give up the request line but keep the metadata that solves most problems anyway — and this is where I’d point you back at the SNI-extraction recipe in the original tcpdump deep dive (tshark -r cap.pcap -Y 'tls.handshake.type == 1' -T fields -e tls.handshake.extensions_server_name), because the ClientHello is cleartext and the SNI field alone answers “is this client even reaching the hostname it thinks it’s reaching” without decrypting a single byte.

Section 3: Safe Production Streaming — Live tcpdump to Local Wireshark

There is a category of problem that resists offline analysis: anything that depends on seeing the packet arrive in real time — watching a TLS handshake unfold interactively, correlating a capture against a live application log tail, or simply needing Wireshark’s coloring rules and protocol dissectors while the problem is actively happening rather than twenty minutes later. For that, you stream the capture live, over SSH, into a local Wireshark instance.

The core trick is that tcpdump -w - writes pcap data to stdout instead of a file, and Wireshark (or tshark) can read pcap data from stdin with -i -. SSH is the pipe that connects them across the network, encrypted, with no extra services to stand up on the remote box.

ssh user@remote-host \
  'sudo tcpdump -i eth0 -U -nn -s 0 -w - "not port 22"' \
  | wireshark -k -i -

Walk through every piece of that, because every piece is load-bearing:

  • sudo tcpdump ... -w - — capture on the remote box, write raw pcap frames to stdout instead of disk. No temp file, no cleanup, no risk of filling /var.
  • -U — flush the output buffer after every packet. Without it, tcpdump buffers its output for efficiency, and you’ll sit there watching nothing happen for what feels like an eternity while the buffer fills. For a live, interactive stream, -U is not optional — it’s the difference between “live” and “live, with a confusing thirty-second lag that makes you think the capture is broken.”
  • "not port 22"this is the line that prevents the catastrophic loop. Read on below; it deserves its own paragraph.
  • | wireshark -k -i - — pipe stdout from the SSH session straight into Wireshark, -k to start capturing immediately, -i - to read the capture stream from stdin rather than from a local interface. Swap wireshark for tshark if you’re on a box without a GUI, or want to apply a display filter and field extraction inline rather than eyeballing the GUI.

The loop you must not create

Here is the trap, stated as plainly as I can state it: the SSH session carrying your capture is itself network traffic on the interface you are capturing. If your filter doesn’t explicitly exclude it, tcpdump captures its own output stream, which it is in the process of sending back to you over that same SSH session, which generates more traffic for it to capture, which it sends back, forever — a feedback loop that will pin the CPU, balloon the link utilisation, and in the worst case make the box you’re trying to diagnose completely unreachable. On a production device, that’s not an embarrassing mistake; it’s an outage you caused while trying to investigate a different one.

not port 22 is the minimum viable exclusion, and it’s usually sufficient — but be precise about which port 22 you mean. If you’ve remapped SSH to a non-standard port, or if there’s other legitimate traffic on port 22 that you actually want to see (rare, but it happens with certain management overlays), exclude by the SSH session’s actual source/destination host-and-port pair instead:

# Tighter: exclude only this specific management session, not all SSH everywhere
ssh user@remote-host \
  'sudo tcpdump -i eth0 -U -nn -s 0 -w - "not (host <your-workstation-ip> and port 22)"' \
  | wireshark -k -i -

Either way, test the exclusion before you trust it. Start the stream, watch Wireshark for a few seconds, and confirm you are not seeing your own SSH session’s packets scroll past. If you are, stop immediately — don’t “let it settle down.”

Performance and best practices

  • Snaplen (-s): -s 0 (full capture, modern default on recent tcpdump builds) is fine for most things, but if you’re streaming over a constrained management link and only need headers — say, you’re chasing a routing or flag-level problem and don’t care about payload — cap it explicitly: -s 128 captures enough for Ethernet + IP + TCP/UDP headers and the first chunk of payload, and meaningfully cuts the bytes you’re pushing down the SSH tunnel.
  • Filter on the capture side, always: every byte you don’t filter out at the tcpdump stage is a byte you compress (SSH does this for you, transparently), encrypt, transmit, decompress, and hand to Wireshark — for nothing. host/port/net filters at the source are not a nicety on a live stream; they’re the difference between a usable session and one that chokes the link.
  • Buffer size (-B): on a busy interface, raise the kernel capture buffer (-B 8192 for 8 MiB) to reduce packets dropped by kernel. A live stream that’s dropping packets at the capture stage is lying to you just as surely as the APM dashboard was — you’re now drawing conclusions from a sample you don’t fully control.
  • CPU and bandwidth cost is real: SSH compression (-C on the ssh invocation) helps on slow links and costs CPU on both ends; weigh that trade-off based on which resource is scarcer on the remote box. On a heavily loaded production firewall or router, even tcpdump itself has a cost — keep the capture window and the filter as tight as the problem allows, and stop the session the moment you have what you need.
  • Prefer tshark over full Wireshark for long sessions: the GUI’s packet-list rendering has real overhead at high packet rates. If you’re not actively eyeballing the visual flow graph or coloring rules, tshark with a display filter and field extraction gets you the same analytical power for a fraction of the resource cost, and is scriptable besides.

Capturing on Other Platforms

tcpdump is a Linux/BSD tool, but the concepts — BPF-style filters, ring buffers, exporting to pcap — show up everywhere, wearing different syntax. Here’s the cross-vendor map for the platforms you’re most likely to be standing in front of when “just SSH in and run tcpdump” isn’t an option.

Debian / Linux (the baseline)

# Precise capture with rotation, ready for offline analysis
sudo tcpdump -i eth0 -nn -s 0 \
  -W 12 -G 1800 \
  -w '/var/captures/eth0-%Y%m%d-%H%M%S.pcap' \
  'host 10.0.0.5 and tcp port 443'

This is the form everything else in this post assumes — it’s the reference implementation. Every other platform below is, in one way or another, trying to give you a constrained version of this same capability.

Cisco IOS / IOS-XE — Embedded Packet Capture (EPC)

IOS doesn’t have a sniffer in the tcpdump sense; it has Embedded Packet Capture, which buffers frames in memory on the device and exports them as a pcap you move off-box. The filter language is access-list based, not BPF, which is the single biggest adjustment if you’re moving from Linux.

! Define what to capture with a standard or extended ACL
ip access-list extended CAP-FILTER
 permit tcp host 10.0.0.5 host 10.0.0.10 eq 443
 permit tcp host 10.0.0.10 eq 443 host 10.0.0.5

! Create the capture buffer and bind it to an interface and direction
monitor capture CAP1 interface GigabitEthernet0/1 both
monitor capture CAP1 access-list CAP-FILTER
monitor capture CAP1 buffer size 10
monitor capture CAP1 limit pps 1000

! Start it, let it run, stop it
monitor capture CAP1 start
monitor capture CAP1 stop

! Export the buffer as a pcap you can pull with SCP/TFTP and open in Wireshark
monitor capture CAP1 export tftp://10.0.0.50/cap1.pcap

The things that bite people: the buffer is finite and lives in DRAM, so buffer size and limit pps are not optional housekeeping — they’re what stands between you and either an incomplete capture or a control-plane CPU problem on a box that’s also trying to route traffic. both captures ingress and egress separately and will show you each packet potentially twice (once per direction relative to the interface) — useful for seeing exactly what the box itself rewrote (NAT, TTL decrement, QoS remarking) by diffing the in-copy against the out-copy of the same flow.

FortiOS — diagnose sniffer packet

I covered this in detail — including the full argument breakdown, verbosity levels, and the fgt2eth.pl conversion workflow to get a real pcap out of a CLI session — in the original tcpdump deep dive. The short version for cross-reference here:

diagnose sniffer packet any 'host 10.0.0.5 and tcp port 443' 4 0 a

Verbosity 4 gives you headers and payload; 0 for count means “run until I stop it”; a for absolute timestamps so you can correlate against logs without doing relative-time arithmetic in your head. The one thing worth restating here in an “enterprise recipes” context: FortiGate has no ring buffer. If the problem needs a long-running rotated capture — the kind you’d reach -W/-G for on Linux — don’t fight the platform. Put a Linux box on a SPAN/mirror port instead and let it do what it’s built for.

Juniper Junos — monitor traffic and tcpdump-on-the-box

This is the pleasant surprise for anyone coming from Linux: on Junos, the underlying capture engine genuinely is a libpcap-derived tool, and the CLI exposes BPF syntax almost verbatim.

# Quick interactive look — feels exactly like tcpdump because, under the hood, it largely is
monitor traffic interface ge-0/0/1 matching "host 10.0.0.5 and tcp port 443" no-resolve

# For anything you intend to keep, write to a file and pull it as a real pcap
monitor traffic interface ge-0/0/1 matching "tcp[tcpflags] & tcp-syn != 0" write-file /var/tmp/junos-cap.pcap no-resolve

# Pull it off-box for Wireshark
file copy /var/tmp/junos-cap.pcap user@workstation:/home/user/captures/

no-resolve is your -nn. matching "..." takes a quoted BPF-style filter — including the bitwise flag-masking syntax from Section 1, verbatim, which is not something you can say about any other vendor CLI in this list. If you’ve internalised the filters in this post, you already know how to capture on a Juniper box; the only new thing to learn is the verb. The one caveat: monitor traffic runs in the Junos shell against the Routing Engine’s view of traffic, so for transit traffic at scale you’re better served by port-mirroring (forwarding-options port-mirroring) to an external analyser — the same “don’t make the control plane do the data plane’s job” principle that applies to Cisco’s EPC and FortiGate’s sniffer.

VMware SD-WAN / VeloCloud — Edge packet capture

VeloCloud Edges expose packet capture through the Orchestrator UI (Monitor → Edge → Packet Capture / Live Mode) and, on-box, through the Edge’s diagnostic shell, which — like Junos — sits on a Linux-derived base and gives you something close to native tcpdump.

# From the Edge CLI / debug shell — interface names follow VeloCloud's GE convention
tcpdump -i GE1 -nn -s 0 -c 500 -w /tmp/edge-cap.pcap 'host 10.0.0.5 and tcp port 443'

# Pull the resulting file via the Orchestrator's remote diagnostics / SCP packaging,
# then open it locally exactly as you would any other pcap

The detail that matters operationally: an SD-WAN Edge sits at the seam between the overlay and the underlay, and which interface you capture on changes what you see entirely. Capture on the LAN-side GE interface and you see clear application traffic, pre-encapsulation. Capture on the WAN-side interface (or the VCMP/tunnel interface, depending on platform generation) and you see the encapsulated, possibly encrypted overlay traffic — the inner conversation is invisible unless you’re decrypting. This is precisely the same “pick the right point in the pipeline” problem I called out for FortiGate’s NAT/IPsec interfaces in the earlier post, and for VeloCloud specifically, it’s the single most common reason an engineer captures “the right host” and sees nothing useful: they captured on the wrong side of the tunnel boundary for the question they were actually asking.

Conclusion & Best Practices Cheat Sheet

None of this replaces understanding the protocol. A perfectly-crafted BPF filter pointed at the wrong interface, on the wrong side of a NAT or tunnel boundary, with a snaplen too short to capture the bytes you actually need, will hand you a pcap that’s both complete-looking and useless. The filters and recipes here are levers — they only move the right thing if you’ve already worked out where to pull.

Flag / PracticeWhat it doesProduction implication
-nnSuppresses hostname and port-name resolutionAlways on. DNS lookups during capture can hang tcpdump entirely on a broken network — the exact moment you can’t afford it to.
-s 0 (or explicit -s <n>)Sets snaplen — how many bytes per packet to keep0 for “give me everything” when disk/bandwidth allows; an explicit small value (128) when streaming live or capturing on a constrained link and you only need headers.
-w - / -rWrite raw pcap to stdout / read it backThe pairing that makes live-streaming and offline re-filtering possible — capture once, analyse as many times and as many ways as you need.
-c <n>Stop after N packetsThe single best defence against an unbounded capture filling a disk or a buffer on a box you don’t want to babysit.
-W / -G / -CRing-buffer rotation by time or sizeMandatory for anything that runs longer than you’re willing to watch it. No rotation plan means “I’ll deal with the full disk at 3 a.m.”
-UFlush output after every packetRequired for live streaming — without it, your “real-time” capture has an invisible, confusing buffering delay.
-B <KiB>Capture buffer sizeRaise it when you see packets dropped by kernel. A capture with drops cannot be trusted for anything sequence-sensitive — treat it as inadmissible evidence.
'not port 22' (or tighter)Excludes your own management sessionThe one line standing between “useful live capture” and “I just took down the box I was trying to fix.” Verify it’s working before you trust it.
-vvv / -X / -AVerbosity / hex+ASCII / ASCII payloadReach for these when you’ve already isolated the packets that matter and need to read what’s inside them — not as a first-pass firehose.
Capture with tcpdump, analyse with tshark/WiresharkDivision of labour between collection and interpretationStream-level questions (retransmissions, dup-acks, reassembly) need state that BPF cannot express. Don’t fight the tool; pipe to the one built for the job.

The wire doesn’t care what your dashboards say, what your runbook predicted, or how confident the vendor’s TAC engineer sounded on the call. It only contains what actually happened, in the order it actually happened, with nothing smoothed over. Get comfortable enough with these filters that reaching for them is reflexive rather than effortful, and you’ll find that the hardest problems — the ones that survive every other layer of troubleshooting — are usually the ones where the answer was sitting in the capture the entire time, waiting for someone to ask it the right question.