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.retransmissionfilter, then look at the conversation. - Zero windows —
tcp.analysis.zero_window. Receiver isn't reading from the socket fast enough; backpressure indicator. - TLS handshake failure —
tls.alert_message. Server tells you the reason (unknown CA, expired cert, bad cipher suite). - DNS NXDOMAIN floods —
dns and dns.flags.rcode == 3. Misconfigured client or a typo somewhere. - Unexpected SYNs —
tcp.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.