Install

# Debian / Ubuntu
sudo apt install wireshark tshark
# During install, answer YES to "allow non-superuser to capture" if you want
# captures without sudo for your user. Adds your user to the wireshark group.

# Headless server — tshark only
sudo apt install tshark

# Add yourself to the wireshark group; log out and back in
sudo usermod -aG wireshark $USER

Capture filters vs display filters

Two completely different filter languages. Newcomers conflate them and the syntax errors are confusing:

  • Capture filters — BPF syntax (same as tcpdump). Applied at the kernel before frames are copied to userspace. Limits which packets are recorded. Cheaper but less expressive.
  • Display filters — Wireshark's own language. Applied after capture to decide which packets to show / process. Filters the view, doesn't change what's recorded. Hundreds of supported fields.

Capture filter cheat sheet

# By host
host 192.168.1.10
src host 10.0.5.20
dst host fe80::1234

# By port
port 443
tcp port 80 or tcp port 443

# By protocol
icmp
arp
udp

# Combinations
host 10.0.5.20 and tcp port 5432
not port 22 and not port 5353            # exclude SSH and mDNS noise

# Capture only the first 64 bytes of each packet (headers only)
-s 64

Display filter cheat sheet

# IP address
ip.addr == 192.168.1.10
ip.src == 192.168.1.10
ip.dst != 192.168.1.10

# IPv6
ipv6.addr == fe80::1234

# TCP / UDP
tcp.port == 443
udp.port == 53

# Just SYN packets
tcp.flags.syn == 1 and tcp.flags.ack == 0

# Retransmissions / packet loss indicators
tcp.analysis.retransmission
tcp.analysis.duplicate_ack
tcp.analysis.zero_window

# HTTP
http
http.request.method == "POST"
http.response.code == 500
http.host == "api.example.com"

# DNS
dns
dns.qry.name == "example.com"
dns.flags.response == 0                   # queries only

# TLS handshake
tls.handshake.type == 1                   # ClientHello
tls.handshake.extensions_server_name      # SNI present

Capture to a file with tshark

# Capture to a file (-i interface, -w file)
sudo tshark -i eth0 -w /tmp/web.pcapng \
    -f 'host 10.0.5.20 and tcp port 443'

# Rotate every 100 MB, keep last 5 files
sudo tshark -i eth0 -w /tmp/rotate.pcapng \
    -b filesize:100000 -b files:5 \
    -f 'tcp port 443'

# Verbose decoded output to stdout
sudo tshark -i eth0 -V port 53 | head -50

# One line per packet with selected fields
sudo tshark -i eth0 -Tfields \
    -e frame.time \
    -e ip.src -e ip.dst \
    -e tcp.srcport -e tcp.dstport \
    -e http.request.method -e http.request.uri \
    -E header=y -E separator='|' \
    port 80

Useful one-liners

# Top 20 source IPs by packet count
sudo tshark -i eth0 -q -z conv,ip 2>/dev/null \
    | sort -k 6 -nr | head -20

# Top destinations by bytes
sudo tshark -i eth0 -q -z endpoints,ipv4

# HTTP response code distribution
tshark -r capture.pcapng -Y 'http.response' \
    -Tfields -e http.response.code | sort | uniq -c | sort -rn

# DNS query frequency
tshark -r capture.pcapng -Y 'dns.flags.response == 0' \
    -Tfields -e dns.qry.name | sort | uniq -c | sort -rn | head

# TLS SNI per client
tshark -r capture.pcapng -Y 'tls.handshake.extensions_server_name' \
    -Tfields -e ip.src -e tls.handshake.extensions_server_name \
    | sort -u

# Re-assemble HTTP request/response and dump bodies
tshark -r capture.pcapng --export-objects http,/tmp/http-objects/

Decrypting TLS (in dev environments)

Browsers and many HTTP libraries respect the SSLKEYLOGFILE environment variable: when set, they write per-session pre-master secrets to that file. Wireshark / tshark can use them to decrypt the captured packets.

# Set on the client before it makes the requests
export SSLKEYLOGFILE=/tmp/sslkeys.log

# Then capture as usual
sudo tshark -i eth0 -w /tmp/tls.pcapng -f 'tcp port 443'

# In Wireshark GUI: Edit → Preferences → Protocols → TLS → "(Pre)-Master-Secret log filename"
# In tshark:
tshark -r /tmp/tls.pcapng -o tls.keylog_file:/tmp/sslkeys.log -Y http

This is the legal-and-encouraged way to decrypt TLS for application debugging. It does not break TLS for anyone — it only works for sessions whose keys you have because the client wrote them out for you.

Remote capture from a headless server

SSH the capture stream to a local Wireshark for interactive analysis:

# On your laptop
ssh -t user@server "sudo tshark -i eth0 -f 'host 10.0.5.20' -w -" | wireshark -i - -k

tshark on the remote writes pcap to stdout; SSH streams it back; local Wireshark reads from stdin and starts displaying immediately. Stop with Ctrl-C.

Capture only the conversations you care about

For long-running captures, pre-filter aggressively to keep disk usage sane:

# Only the application's traffic + SSH for debugging
sudo tshark -i eth0 -w /tmp/app.pcapng \
    -f '(host 10.0.5.20 and tcp port 5432) or port 22'

# Or use a ringbuffer with a time cap (every 60 minutes, keep 24 files = 1 day)
sudo tshark -i eth0 -w /tmp/ring.pcapng \
    -b duration:3600 -b files:24 \
    -f 'tcp port 5432'

Common things to look for

  • Retransmissions everywhere — usually a flaky network path or an MSS/MTU mismatch. tcp.analysis.retransmission filter, then look at the conversation.
  • Zero windowstcp.analysis.zero_window. Receiver isn't reading from the socket fast enough; backpressure indicator.
  • TLS handshake failuretls.alert_message. Server tells you the reason (unknown CA, expired cert, bad cipher suite).
  • DNS NXDOMAIN floodsdns and dns.flags.rcode == 3. Misconfigured client or a typo somewhere.
  • Unexpected SYNstcp.flags.syn == 1 and tcp.flags.ack == 0. Reveals which client is trying to connect, when, to where.

The companion tools

  • mergecap — combine multiple pcap files chronologically.
  • editcap — slice a pcap by time / packet number, change file format, sanitize.
  • capinfos — summary stats of a pcap (duration, packet count, average rate).
  • dumpcap — the underlying capture engine; lower-privilege than tshark for capture-only.