TShark
TShark is Wireshark without the GUI: it reads a saved PCAP or captures live, decodes packets with the same dissectors, and prints results you can grep, script, and paste into notes. Reach for it on a headless box, or when you want a repeatable one-liner instead of clicking through the GUI: triaging a capture, pulling DNS or HTTP out of a CTF PCAP, or confirming what a service actually puts on the wire. Only capture on networks and hosts you own or are explicitly authorized to monitor.
Mental model: source -> filter -> output. Pick -r file or -i IFACE, narrow with -Y "filter", then take the default summaries or -T fields.
New to packet capture? Core concepts
- Source: read a saved capture with
-r file, or sniff live with-i IFACE - Capture filter (BPF):
-f "tcp port 80", applied at the wire before decode, so unwanted traffic never hits disk - Display filter (Wireshark syntax):
-Y "http.request", protocol-aware, applied after packets are decoded - Dissectors: Wireshark’s decoders that turn raw bytes into named protocol fields; TShark shares them
- Fields:
-T fields -e ip.src -e dns.qry.nameprints named columns; confirm a name with-Vfirst - Statistics taps:
-z io,phs,-z conv,tcp,-z follow,...,-z credentials, with per-packet lines silenced by-q - Stream: a reassembled TCP conversation, both sides in order (
follow,tcp,ascii,N) - Ring buffer:
-b filesize: -b files:bounds a long capture to a fixed footprint
When to reach for TShark (and when not)
Reach for it to triage a PCAP on a headless box, pull DNS, HTTP, or cleartext creds out of a capture as scriptable columns, confirm what a service actually puts on the wire, or bake the whole analysis into a repeatable one-liner you can paste into notes.
Reach for something else when you need to grab raw packets on a minimal or constrained box (tcpdump, then hand the file back to TShark), follow a stream visually once the terminal gets cramped (Wireshark’s GUI shares the same dissectors and filters), or reshape the output (tidy columns with grep, awk, sort, uniq, or parse -T json with jq).
Install, update, verify#
sudo apt install -y tshark # Debian / Ubuntu / Kali
sudo dnf install -y wireshark-cli # Fedora / RHEL
brew install wireshark # macOS (includes tshark)
sudo apt install --only-upgrade -y tshark # update
tshark -v | head -n1 # version + build
command -v tshark # confirm it is on PATH
tshark -D # list capture interfaces (self-test)
On Debian/Kali the installer offers to let non-root users capture. Say yes, then add yourself and re-login so live capture needs no sudo:
sudo usermod -aG wireshark "$USER" # then log out/in, or run: newgrp wireshark
On Windows, install Wireshark from wireshark.org (it bundles the Npcap driver for live capture and drops tshark.exe in the install dir).
Flags you’ll actually use#
Skim these first; the cheat sheet below is these flags combined.
| Flag | Does | Flag | Does |
|---|---|---|---|
-r | Read a PCAP | -Y | Display filter (Wireshark syntax) |
-i | Capture on an interface | -f | Capture filter (BPF) at the wire |
-D | List capture interfaces | -w | Write raw packets to a file |
-c N | Stop after N packets | -a duration:N | Stop after N seconds |
-T fields | Column output | -e | Field to print (with -T fields) |
-E | Field format (header, separator) | -z | Statistics tap (add -q) |
-q | Suppress per-packet output | -V | Full decoded field tree |
-x | Hex + ASCII dump | -n | Disable name resolution (faster) |
-t ad | Absolute timestamps | -b | Ring buffer (filesize: / files:) |
--export-objects | Carve files (http,dir) | -2 | Two-pass analysis |
Cheat sheet#
Every build-up starts simple and adds one flag at a time, so you can stop at the line that does the job.
# Read a PCAP: stop at the line you need
tshark -r capture.pcap # 1. every packet, one summary line
tshark -r capture.pcap -Y "http" # 2. + narrow to a protocol
tshark -r capture.pcap -Y "http.request" \
-T fields -e http.host -e http.request.uri # 3. + just the columns you want
tshark -r capture.pcap -Y "http.request" \
-T fields -e http.host -e http.request.uri | sort -u # 4. + dedupe
# Live capture: stop at the line you need
sudo tshark -i eth0 # 1. everything on the wire
sudo tshark -i eth0 -f "tcp port 80" # 2. + BPF filter at the source
sudo tshark -i eth0 -f "tcp port 80" -w web.pcap # 3. + save raw to disk
sudo tshark -i eth0 -f "tcp port 80" -a duration:60 -w web.pcap # 4. + auto-stop after 60s
# Discover
tshark -D # list capture interfaces
tshark -r capture.pcap -q -z io,phs # protocol hierarchy: what is in the file
tshark -r capture.pcap -q -z conv,tcp # busiest TCP conversations
# Capture filters (BPF, at the wire, with -f)
sudo tshark -i eth0 -f "port 53" # by port
sudo tshark -i eth0 -f "host TARGET_IP" # by host
sudo tshark -i eth0 -f "net 192.168.56.0/24" # by subnet
sudo tshark -i eth0 -f "src host TARGET_IP" # by direction
sudo tshark -i eth0 -f "udp" # by protocol
sudo tshark -i eth0 -f "not arp and not stp" # negation
# Display filters (Wireshark syntax, after decode, with -Y)
tshark -r capture.pcap -Y "dns" # one protocol
tshark -r capture.pcap -Y "ip.addr == TARGET_IP" # one host, either direction
tshark -r capture.pcap -Y "tcp.port == 445" # one port
tshark -r capture.pcap -Y "http.response.code == 200" # a field value
tshark -r capture.pcap -Y "tcp.flags.syn==1 && tcp.flags.ack==0" # SYN, no ACK (scan)
tshark -r capture.pcap -Y 'frame contains "password"' # payload keyword
# Extract clean columns
tshark -r capture.pcap -Y "dns.flags.response == 0" \
-T fields -e ip.src -e dns.qry.name # who asked, for what
tshark -r capture.pcap -Y "http.request" \
-T fields -e ip.src -e http.host -e http.request.uri # source, host, path
tshark -r capture.pcap -Y "http.request" -T fields \
-E header=y -E separator=, -e frame.time -e ip.src -e http.host # CSV with a header row
# Streams, stats, loot
tshark -r capture.pcap -q -z follow,tcp,ascii,3 # reassemble stream index 3
tshark -r capture.pcap -q -z credentials # cleartext creds it can spot
tshark -r capture.pcap -q -z endpoints,ip # traffic totals per host
tshark -r capture.pcap -q -z io,stat,1 # bytes/packets per second
mkdir -p loot && tshark -r capture.pcap --export-objects http,loot # carve HTTP files
# Read, re-filter, write files
tshark -r big.pcap -Y "dns" -w dns-only.pcap # slice matching packets to a new file
sudo tshark -i eth0 -b filesize:10240 -b files:5 -w ring.pcap # ring buffer, bounded disk
Command breakdowns#
Each block below explains one situation, the exact command, and what to expect back.
Read a saved capture#
tshark -r capture.pcap
One summary line per packet. The fastest way to see what is in a file before you start filtering.
See what protocols a PCAP contains#
tshark -r capture.pcap -q -z io,phs
A protocol-hierarchy tree with packet and byte counts per layer. Start every triage here: if the traffic you want is not in the tree, it is not in the file.
Capture only web traffic to disk#
sudo tshark -i eth0 -f "tcp port 80" -w pcaps/web.pcap
-f is a BPF capture filter applied at the wire, so non-HTTP never hits disk and the file stays small. Read it back later with -r.
Filter a PCAP down to one protocol#
tshark -r capture.pcap -Y "dns"
tshark -r capture.pcap -Y "http.request or http.response"
-Y is protocol-aware (Wireshark syntax), so it understands fields that BPF cannot. Combine terms with and, or, and not.
List every DNS name that was looked up#
tshark -r capture.pcap -Y "dns.flags.response == 0" \
-T fields -e ip.src -e dns.qry.name | sort -u
Two columns: who asked, and for what. response == 0 keeps queries and drops the answers; sort -u collapses repeats.
Map HTTP requests (who asked for what)#
tshark -r capture.pcap -Y "http.request" \
-T fields -e ip.src -e http.host -e http.request.uri
Source IP, Host header, and path: a quick map of web activity in the capture.
Follow a TCP stream as readable text#
tshark -r capture.pcap -Y "http.request" -T fields -e tcp.stream | head # find the index
tshark -r capture.pcap -q -z follow,tcp,ascii,3 # reassemble index 3
Prints both sides of the conversation reassembled in order: the CLI equivalent of Wireshark’s “Follow TCP Stream.”
Pull cleartext credentials from a capture#
tshark -r capture.pcap -q -z credentials
A table of usernames and passwords TShark can spot in cleartext protocols (FTP, HTTP basic, IMAP, POP, SMTP). For FTP specifically, filter -Y "ftp.request.command == \"USER\" or ftp.request.command == \"PASS\"".
Carve files out of an HTTP session#
mkdir -p loot && tshark -r capture.pcap --export-objects http,loot
Reassembles and writes every file transferred over HTTP into loot/: images, downloads, exfiltrated documents. The same tap works for smb, tftp, and imf.
Extract the hostnames from TLS handshakes#
tshark -r capture.pcap -Y "tls.handshake.type == 1" \
-T fields -e tls.handshake.extensions_server_name
The SNI in each Client Hello: where the encrypted sessions were headed, even though you cannot read the payloads.
Grep packet payloads for a keyword#
tshark -r capture.pcap -Y 'frame contains "password"'
frame contains matches the raw bytes of the whole frame, so it finds a string regardless of protocol. Reach for it when you do not yet know which field holds the value.
List the busiest conversations#
tshark -r capture.pcap -q -z conv,tcp
A table of TCP conversations with packet and byte totals per pair: the fast way to spot a bulk transfer or a beaconing host. Use conv,udp or endpoints,ip for other cuts.
Watch ARP and DHCP live on your subnet#
sudo tshark -i eth0 -Y "arp or dhcp"
Live view of who is joining, requesting leases, and announcing MACs: the L2/L3 chatter of a homelab segment.
Save a long capture without filling the disk#
sudo tshark -i eth0 -b filesize:10240 -b files:5 -w pcaps/ring.pcap
A ring buffer: up to 5 files of about 10 MB each (filesize is in KB), oldest overwritten. Runs indefinitely with a bounded footprint.
Slice a big PCAP into a smaller one#
tshark -r big.pcap -Y "ip.addr == TARGET_IP" -w host.pcap
-Y selects and -w writes only the matching packets to a new capture: hand a colleague the 200 packets that matter instead of a 2 GB file.
Produce CSV for a spreadsheet or script#
tshark -r capture.pcap -Y "http.request" -T fields \
-E header=y -E separator=, -e frame.time -e ip.src -e http.host
-E header=y adds a header row and -E separator=, makes it CSV instead of the default TSV.
Inspect one packet’s raw bytes#
tshark -r capture.pcap -c 1 -V -x
-V prints the full decoded field tree, -x adds a hex and ASCII dump. Verify a field name here before trusting a whole -e column.
Reading the output#
The default is one summary line per packet:
12 1.043 192.168.56.10 -> 192.168.56.1 DNS 74 Standard query 0x1a2b A example.com
| Column | Is | Notes |
|---|---|---|
| No. | Packet index in the file | Jump back with -Y "frame.number == 12" |
| Time | Seconds since capture start | -t ad shows absolute date/time instead |
| Source -> Destination | Addresses (IP, or MAC at L2) | -n disables name resolution, faster and numeric |
| Protocol / Length | Top decoded protocol, frame bytes | Highest layer the dissector reached |
| Info | The dissector’s one-line summary | The most useful column for scanning |
Encrypted traffic decodes only to the transport: you see TLS, not the HTTP inside, unless you supply keys (-o "tls.keylog_file:keys.log").
Troubleshooting#
| Problem | Cause | Fix |
|---|---|---|
command not found | Not installed / stale PATH | Reinstall; open a fresh shell |
dumpcap ... Permission denied | Not in the capture group | sudo usermod -aG wireshark "$USER", re-login |
tshark -D lists nothing | No permission or no interfaces | Use sudo, or fix the wireshark group |
-Y errors with a syntax message | BPF written where Wireshark syntax is expected | Put BPF in -f; use Wireshark syntax in -Y |
-Y returns nothing | Protocol absent or field name wrong | Confirm with -q -z io,phs; verify names via -V |
| Live capture drops packets | Heavy dissection on a busy link | Capture raw with -w, analyze offline |
| Only TLS visible, no HTTP inside | Traffic is encrypted | Expected without keys; read the handshake only |
| Stats buried under packet lines | Missing -q | Add -q alongside -z |
Gotchas#
-fis BPF,-Yis Wireshark syntax:-f "tcp port 80"versus-Y "http.request". Swap them and you get a syntax error or silent empty output. This is the number-one time sink.- Capture filters cannot see application fields: BPF runs before dissection, so
-f "http.host"is invalid. Capture broadly with-f "tcp port 80", then narrow with-Yon read. - Verify a field with
-Vbefore trusting a whole-ecolumn: a wrong or renamed field name yields an empty column that reads like “no data,” not an error. -wsaves raw packets;-T fieldsprints to stdout: you cannot-wa field extraction. Redirect stdout (> out.tsvor| tee) to keep column output.filesizeis in kilobytes:-b filesize:10240is about 10 MB, not 10 GB. Off by a thousand and the ring either fills or starves.- Every PCAP may hold secrets: scrub credentials, tokens, and PII out of extracted output before pasting it anywhere.
Pairs with#
tcpdump grabs raw packets on a minimal or constrained box, then you hand the PCAP to TShark for protocol-aware filtering and field extraction. Wireshark’s GUI opens the same file for visual stream-following when the terminal gets cramped; the two share dissectors and filter syntax. Pipe columns through grep, awk, sort, and uniq to tidy them, and jq to parse -T json output. When you only need to grab and rotate captures unattended, dumpcap (which ships with Wireshark) is the lean engine underneath.