> ## Content Index
> Fetch the complete content index at: https://www.pinglabz.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# tshark: Wireshark in the Terminal
- URL: https://www.pinglabz.com/tshark-linux/
- Published: 2026-08-19T16:30:32.000Z
- Updated: 2026-08-19T16:30:32.000Z
- Description: Wireshark’s full dissector library driven from an SSH session: capture filters against display filters, custom field output, ring buffers and the statistics taps that make triage fast.
- Author: Jaime
- Tags: Packet Analysis, Linux, Troubleshooting, Labs

You already know how to run `tcpdump`. You already know Wireshark. `tshark` is the awkward middle child that most engineers skip, and skipping it costs you real time, because it is the only one of the three that will dissect HTTP, decode a TLS handshake, follow a TCP stream and hand you a throughput histogram without ever leaving the SSH session you are already in.

This article is part of the [Linux networking commands guide](https://www.pinglabz.com/linux-networking-commands/). Everything below was captured on a Debian 13 host bridged into a CML topology: the host sits at 10.77.0.100, an nginx server (WEB1) sits at 10.77.2.10 two router hops away, and an iperf3 server (SRV1) sits at 10.77.3.10 three hops away. Every byte of output is from that lab.

## tshark against tcpdump against Wireshark

They are not competitors so much as three points on a curve, and the honest answer to "which one" is usually "whichever is already installed."

tcpdump

Everywhere, including on appliances and minimal containers. Fast, tiny, and it understands the packet headers. It does not dissect application protocols beyond a summary line.

Reach for it when: you need a capture *right now* on a box you do not control.

tshark

Wireshark's full dissector library, driven from a terminal. Same display filters, same statistics engine, same file formats. Heavier and slower than tcpdump.

Reach for it when: you need to *understand* the traffic, not just record it.

Wireshark GUI

The best packet analysis tool there is, and useless over an SSH session to a jump host in another datacenter.

Reach for it when: you can get the pcap onto your laptop.

The workflow that actually happens in practice is a blend: capture with `tcpdump -w` or `tshark -w` on the remote host, triage with `tshark` in place to decide whether the file is worth moving, then pull the interesting file down for Wireshark. If you have not read it yet, [tcpdump for network engineers](https://www.pinglabz.com/tcpdump-for-network-engineers/) covers the capture side in depth, and the capture filter syntax there is exactly the syntax tshark uses for `-f`.

## Picking an interface with -D

tshark needs to be told what to listen to, and `-D` tells you what is available:

```
j@llmbits:~$ sudo tshark -D
1. ens192
2. ens224
3. any
4. lo (Loopback)
5. bluetooth-monitor
6. nflog
7. nfqueue
8. dbus-system
9. dbus-session
10. ciscodump (Cisco remote capture)
11. dpauxmon (DisplayPort AUX channel monitor capture)
12. randpkt (Random packet generator)
13. sdjournal (systemd Journal Export)
14. sshdump (SSH remote capture)
15. udpdump (UDP Listener remote capture)
16. wifidump (Wi-Fi remote capture)
```

Note what is in that list beyond the obvious NICs. `any` captures across every interface at once, which is the fastest way to answer "is this packet leaving the box at all." `nflog` and `nfqueue` let you capture packets that netfilter logged or queued, so you can see exactly what a firewall rule matched. And `ciscodump` and `sshdump` are extcap plugins that SSH into a remote device and stream a capture back, which means tshark can pull an EPC capture off a Cisco box without you ever copying a file.

## The one concept that matters: capture filters against display filters

This trips up everyone who comes to tshark from tcpdump, because tshark has both and they use completely different syntax.

`-f` is a **capture filter**. It is BPF, the same language tcpdump uses, and it is applied in the kernel before the packet is ever copied to userspace. Packets it rejects are never seen, never counted and never written to your file.

`-Y` is a **display filter**. It is Wireshark's own language, it can reach into any field of any dissected protocol, and it runs after tshark has fully dissected the packet. Packets it rejects were still captured.

Here is that difference made visible. This run asked for six packets on port 80 with a display filter that only shows SYNs and HTTP requests, while curl fetched a page from WEB1:

```
j@llmbits:~$ sudo tshark -i ens224 -c 6 -f "tcp port 80" -Y "tcp.flags.syn==1 || http.request"
Capturing on 'ens224'
3 packets captured
    1 0.000000000  10.77.0.100 → 10.77.2.10   TCP 74 51464 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM TSval=1259260457 TSecr=0 WS=128
    2 0.004854831   10.77.2.10 → 10.77.0.100  TCP 74 80 → 51464 [SYN, ACK] Seq=0 Ack=1 Win=65160 Len=0 MSS=1460 SACK_PERM TSval=2214885239 TSecr=1259260457 WS=2048
    4 0.005135906  10.77.0.100 → 10.77.2.10   HTTP 140 GET / HTTP/1.1
```

Look at the frame numbers: 1, 2, 4\. Frame 3 was captured, dissected and then hidden, because it was the bare ACK that completes the handshake and it matched neither half of the display filter. The gap in the numbering is the display filter's fingerprint. If you ever want to know whether a packet is missing because it was not on the wire or because your filter ate it, that gap is the answer.

The practical rule: **use a capture filter to control what hits the disk, and a display filter to control what hits your eyes.** On a busy link, a capture filter is the difference between a 200 MB file and a 40 GB file. A display filter cannot save you that, because the packet was already captured before `-Y` ran.

### The -c gotcha that will waste an afternoon

`-c` counts *captured* packets, not displayed ones. Combine a small `-c` with a selective `-Y` and tshark will happily exit having shown you nothing at all:

```
j@llmbits:~$ sudo tshark -i ens224 -c 3 -f "tcp port 80" -Y "http.request"
Capturing on 'ens224'
0 packets captured

j@llmbits:~$ sudo tshark -i ens224 -c 3 -f "tcp port 80"
Capturing on 'ens224'
3 packets captured
    1 0.000000000  10.77.0.100 → 10.77.2.10   TCP 74 52908 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM TSval=2248582813 TSecr=0 WS=128
    2 0.004532634   10.77.2.10 → 10.77.0.100  TCP 74 80 → 52908 [SYN, ACK] Seq=0 Ack=1 Win=65160 Len=0 MSS=1460 SACK_PERM TSval=2214910466 TSecr=2248582813 WS=2048
    3 0.004615864  10.77.0.100 → 10.77.2.10   TCP 66 52908 → 80 [ACK] Seq=1 Ack=1 Win=64256 Len=0 TSval=2248582818 TSecr=2214910466
```

The three packets `-c 3` consumed were the handshake. The GET was the fourth packet and tshark had already exited. If you want N matching packets, either tighten the capture filter so only interesting packets are captured, or drop `-c` and use `-a duration:N` instead.

## Turning packets into columns with -T fields

This is the feature that makes tshark worth installing even if you never use it interactively. `-T fields` plus a list of `-e` field names produces tab-separated output that you can pipe into awk, sort, or a spreadsheet:

```
j@llmbits:~$ sudo tshark -i ens224 -c 8 -f "tcp port 80" \
    -T fields -e frame.number -e ip.src -e ip.dst -e tcp.srcport \
    -e tcp.dstport -e tcp.flags.str -e tcp.len -E header=y
frame.number	ip.src	ip.dst	tcp.srcport	tcp.dstport	tcp.flags.str	tcp.len
1	10.77.0.100	10.77.2.10	51468	80	··········S·	0
2	10.77.2.10	10.77.0.100	80	51468	·······A··S·	0
3	10.77.0.100	10.77.2.10	51468	80	·······A····	0
4	10.77.0.100	10.77.2.10	51468	80	·······AP···	74
5	10.77.2.10	10.77.0.100	80	51468	·······A····	0
6	10.77.2.10	10.77.0.100	80	51468	·······AP···	238
7	10.77.0.100	10.77.2.10	51468	80	·······A····	0
8	10.77.2.10	10.77.0.100	80	51468	·······AP···	896
```

That is a complete HTTP transaction in eight lines: SYN, SYN-ACK, ACK, the 74-byte GET, the ACK for it, then a 238-byte response header followed by an 896-byte body. The `tcp.flags.str` field renders the flags positionally, which reads oddly at first but is very easy to grep.

Any field Wireshark can display, you can extract. `-e http.host`, `-e dns.qry.name`, `-e tls.handshake.extensions_server_name`, `-e ip.ttl`. To find a field name, run `tshark -G fields` and grep, or click the field in the Wireshark GUI once and read the status bar.

## Writing and reading files

`-w` writes a pcapng, `-r` reads one back. Here is a nine second capture taken while iperf3 pushed traffic to SRV1:

```
j@llmbits:~$ sudo tshark -i ens224 -f "host 10.77.3.10" -w iperf.pcapng -a duration:9
Capturing on 'ens224'
11670 packets captured

j@llmbits:~$ ls -lh iperf.pcapng
-rw------- 1 root root 18M Aug 19 09:12 iperf.pcapng
```

### The permission error that makes no sense

The first time I ran that, in a directory owned by my own user, tshark failed:

```
tshark: The file to which the capture would be saved ("iperf.pcapng") could not be
opened: Permission denied.
0 packets captured
```

It was running as root at the time, in a directory root could obviously write. The reason is that tshark does not capture packets itself: it forks `dumpcap`, and dumpcap drops privileges before it opens the output file. The child that creates the file is not the user you think it is. The fix is either to write somewhere the unprivileged capture user can create files, or to run dumpcap directly and let tshark read the result afterward. This is worth knowing before you lose a capture window to it.

### Ring buffers for captures that run for days

If you are chasing something intermittent, you cannot leave `-w` running for a week. `-b` gives you a rotating set of files:

```
j@llmbits:~$ sudo tshark -i ens224 -f "host 10.77.3.10" -w ring.pcapng \
    -b filesize:200 -b files:3 -a duration:8
Capturing on 'ens224'
9681 packets captured

j@llmbits:~$ ls -l ring*.pcapng
-rw------- 1 root root 200044 Aug 19 09:12 ring_00070_20260819091246.pcapng
-rw------- 1 root root 200692 Aug 19 09:12 ring_00071_20260819091246.pcapng
-rw------- 1 root root  44968 Aug 19 09:12 ring_00072_20260819091246.pcapng
```

Read the file numbers. tshark wrote seventy two files during those eight seconds and kept three. `filesize` is in kilobytes, so 200 means a 200 KB file, which under an iperf3 load rotates roughly ten times a second. In production you would use something like `-b filesize:100000 -b files:20`, giving a 2 GB rolling window. The point is that disk usage is bounded and the newest data always survives.

## The statistics engine

This is where tshark leaves tcpdump behind entirely. `-z` runs one of Wireshark's statistics taps and prints the result when the capture ends. Pair it with `-q` to suppress the per-packet lines.

### Who is talking to whom

```
j@llmbits:~$ tshark -r iperf.pcapng -q -z conv,tcp
================================================================================
TCP Conversations
Filter:<No Filter>
                                                           |       <-      | |       ->      | |     Total     |    Relative    |   Duration   |
                                                           | Frames  Bytes | | Frames  Bytes | | Frames  Bytes |      Start     |              |
10.77.0.100:56962          <-> 10.77.3.10:5201               6195 413 kB       5445 17 MB       11640 17 MB         0.016673143         5.0273
10.77.0.100:56960          <-> 10.77.3.10:5201                 14 1,269 bytes      16 1,561 bytes      30 2,830 bytes     0.000000000         5.0472
================================================================================
```

Two conversations, not one. Port 56960 carried 30 packets and about 2.8 KB: that is iperf3's control connection, which negotiates the test and reports results. Port 56962 carried 17 MB: that is the actual data stream. If you have ever wondered why an iperf3 test opens two sockets, there it is on the wire.

### Throughput over time

```
j@llmbits:~$ tshark -r iperf.pcapng -q -z io,stat,1

===============================
| IO Statistics               |
|                             |
| Duration: 5. 47156 secs     |
| Interval: 1 secs            |
|                             |
| Col 1: Frames and bytes     |
|-----------------------------|
|          |1                 |
| Interval | Frames |  Bytes  |
|-----------------------------|
|  0 <> 1  |   1729 | 2473887 |
|  1 <> 2  |   2079 | 3054518 |
|  2 <> 3  |   2424 | 3529040 |
|  3 <> 4  |   2572 | 3942920 |
|  4 <> 5  |   2729 | 4393538 |
|  5 <> Dur|    137 |  170316 |
===============================
```

That is TCP slow start rendered as a table: 2.47 MB in the first second climbing to 4.39 MB in the fifth as the congestion window opens. You can add a filter to any column, so `-z io,stat,1,"tcp.analysis.retransmission"` gives you a per-second retransmission count, which is exactly the graph you want when someone says "the network was slow around 3pm."

### What went wrong, summarized

```
j@llmbits:~$ tshark -r iperf.pcapng -q -z expert
Warns (20)
=============
   Frequency      Group           Protocol  Summary
          20   Sequence                TCP  Connection reset (RST)

Notes (2060)
=============
   Frequency      Group           Protocol  Summary
        1583   Sequence                TCP  Partial Acknowledgement of a segment
          29   Sequence                TCP  Duplicate ACK (#1)
          29   Sequence                TCP  Duplicate ACK (#2)
          42   Sequence                TCP  This frame is a (suspected) fast retransmission
          52   Sequence                TCP  This frame is a (suspected) retransmission
          29   Sequence                TCP  Duplicate ACK (#3)
          28   Sequence                TCP  Duplicate ACK (#4)
```

The expert info tap is the single highest-value thing in tshark for triage. It reads a capture you know nothing about and tells you what Wireshark thinks is abnormal, in one screen.

## A worked example: corroborating a retransmit count

Here is why this matters in practice. iperf3 reported its own retransmission count for the transfer:

```
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-5.00   sec  16.1 MBytes  27.0 Mbits/sec   53            sender
[  5]   0.00-5.01   sec  15.9 MBytes  26.6 Mbits/sec                  receiver
```

Fifty three retransmits, according to the kernel's own counters on the sending host. Now ask the wire:

```
j@llmbits:~$ tshark -r iperf.pcapng -Y "tcp.analysis.retransmission" | wc -l
52
```

Fifty two. Those two numbers come from completely independent sources: one is `tcpi_total_retrans` read out of the kernel by iperf3, the other is Wireshark's sequence-number analysis of packets captured off the NIC. They agree to within one packet, and the one packet is easily explained by the capture starting a fraction of a second after the transfer did.

That agreement is the whole point. When a host-level counter and an independent packet capture tell the same story, you can stop arguing about whether the tool is lying and start fixing the path. When they *disagree* badly, that itself is the finding: it usually means offloads are hiding the real segmentation from your capture, which is covered in [ethtool: link speed, duplex, offloads and NIC stats](https://www.pinglabz.com/ethtool-linux/).

## Following a stream and reading a full dissection

`-z follow,tcp,ascii,0` reassembles stream 0 and prints it as text, which is the terminal equivalent of Wireshark's Follow TCP Stream:

```
j@llmbits:~$ tshark -r http.pcapng -q -z follow,tcp,ascii,0
Follow: tcp,ascii
Filter: tcp.stream eq 0
Node 0: 10.77.0.100:54370
Node 1: 10.77.2.10:80
74
GET / HTTP/1.1
Host: 10.77.2.10
User-Agent: curl/8.14.1
Accept: */*

	238
HTTP/1.1 200 OK
Server: nginx/1.29.8
Date: Wed, 19 Aug 2026 15:54:41 GMT
Content-Type: text/html
Content-Length: 896
Last-Modified: Tue, 07 Apr 2026 11:37:12 GMT
Connection: keep-alive
ETag: "69d4ec68-380"
Accept-Ranges: bytes

	896
```

The numbers before each block are byte counts, and the indentation marks direction. When you need the opposite, maximum detail on a single packet rather than a whole conversation, `-V` prints the complete dissection tree:

```
j@llmbits:~$ tshark -r http.pcapng -Y "http.request" -V
Frame 4: 140 bytes on wire (1120 bits), 140 bytes captured (1120 bits) on interface ens224, id 0
    Arrival Time: Aug 19, 2026 08:54:41.169864491 PDT
    [Protocols in frame: eth:ethertype:ip:tcp:http]
Ethernet II, Src: VMware_b1:cc:47 (00:0c:29:b1:cc:47), Dst: aa:bb:cc:00:04:00 (aa:bb:cc:00:04:00)
    Destination: aa:bb:cc:00:04:00 (aa:bb:cc:00:04:00)
        .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default)
Internet Protocol Version 4, Src: 10.77.0.100, Dst: 10.77.2.10
    Total Length: 126
    Identification: 0x08d2 (2258)
    010. .... = Flags: 0x2, Don't fragment
    Time to Live: 64
    Protocol: TCP (6)
```

Two details in that output are worth pausing on. The destination MAC is `aa:bb:cc:00:04:00` and tshark flags the locally administered bit, because that is R1's interface and IOL allocates MACs from a locally administered pool rather than a vendor OUI. And the source resolves to `VMware_b1:cc:47`, because tshark looked up the OUI. If you would rather see raw values and skip every name lookup, `-n` disables all of them.

## What tshark is bad at

Two honest limitations, because pretending otherwise leads people to the wrong tool.

**It is slow.** Full dissection costs CPU per packet. On a genuinely busy interface, tshark will drop packets that tcpdump would have caught, because tcpdump does almost nothing per packet. The correct pattern on a hot link is `tcpdump -w` to capture and `tshark -r` to analyze. Do not dissect at line rate if you can avoid it.

**It cannot decrypt what it does not have keys for.** tshark will tell you a TLS session exists, which cipher suite was negotiated and what server name the client asked for, and then it stops. Without the session keys it cannot show you the HTTP inside. That boundary, and what leaks anyway, is the subject of the companion article on [ngrep: grep for network traffic](https://www.pinglabz.com/ngrep-linux/).

## Key takeaways

- `-f` is a kernel-side capture filter in BPF syntax; `-Y` is a userspace display filter in Wireshark syntax. Only `-f` saves you disk and CPU.
- Gaps in the frame numbering are the display filter's signature. Frames 1, 2, 4 means frame 3 was captured and hidden, not missing from the wire.
- `-c` counts captured frames, not displayed ones. Pair a tight `-Y` with `-a duration:N` instead.
- `-T fields -e ...` turns packets into tab-separated columns, which is the fastest path from a capture to a report.
- `-b filesize:N -b files:M` bounds a long-running capture to a fixed amount of disk.
- `-z expert` is the best thirty seconds you can spend on a capture you did not take yourself. `-z conv,tcp` and `-z io,stat,1` answer "who" and "when."
- tshark writing a file can fail with "Permission denied" even as root, because dumpcap drops privileges before creating it.
- Capture with tcpdump on busy links, analyze with tshark afterward. Full dissection at line rate drops packets.

tshark is the analysis half of a pair. The capture half is [tcpdump](https://www.pinglabz.com/tcpdump-for-network-engineers/), the pattern-matching half is [ngrep](https://www.pinglabz.com/ngrep-linux/), and the question of how much traffic is even moving belongs to [the live bandwidth monitors](https://www.pinglabz.com/linux-bandwidth-monitoring/). All of them, along with the rest of the toolkit, are indexed in the [complete guide to Linux networking commands](https://www.pinglabz.com/linux-networking-commands/).