> ## 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.

# iftop, nload, bmon, vnstat, nethogs Compared
- URL: https://www.pinglabz.com/linux-bandwidth-monitoring/
- Published: 2026-08-19T16:30:34.000Z
- Updated: 2026-08-19T16:30:34.000Z
- Description: Five live bandwidth monitors pointed at the same flow at the same time, with real terminal frames and a reconciliation of why they all report different numbers.
- Author: Jaime
- Tags: Linux, Monitoring, Troubleshooting, Tools

Five tools, one flow, five different numbers. That is not a bug in any of them, and understanding why they disagree is more useful than picking a favorite. This article puts `iftop`, `nload`, `bmon`, `vnstat` and `nethogs` in front of the same traffic at the same time and reads what each one actually measures.

This article is part of the [Linux networking commands guide](https://www.pinglabz.com/linux-networking-commands/). The test setup is a Debian 13 host at 10.77.0.100 bridged into a CML topology, pushing a single iperf3 TCP stream to a server at 10.77.3.10 three router hops away. Every frame and every figure below came off that host during one session.

## The short version

iftop

Breaks traffic down by:conversation (host pair, optionally port)

Data source:libpcap, it captures packets

History:None, dies with the process

Text mode:Yes, -t

Answers:who is using the bandwidth

nload

Breaks traffic down by:Nothing, whole interface only

Data source:/proc/net/dev counters

History:Session only, min/avg/max

Text mode:No, ncurses only

Answers:is the link busy right now

bmon

Breaks traffic down by:interface, plus per-interface error counters

Data source:netlink and /proc counters

History:Session only, 60-slot graph

Text mode:Yes, ascii and format

Answers:rates and drivers errors together

vnstat

Breaks traffic down by:Interface, over time

Data source:daemon polling counters into SQLite

History:Months, survives reboots

Text mode:Yes, it is native

Answers:how much did we move last month

nethogs

Breaks traffic down by:process (PID and program name)

Data source:libpcap plus /proc socket ownership

History:None

Text mode:Yes, -t

Answers:which program is doing it

ifstat

Breaks traffic down by:Interface

Data source:/proc/net/dev counters

History:None

Text mode:Yes, it is the only mode

Answers:give me numbers I can pipe

One practical note before the output. On Debian, `iftop` and `nethogs` install into `/usr/sbin`, which is not on a normal user's PATH. `command -v iftop` reports nothing and people conclude the package failed to install. It did not; run it with `sudo`, which uses root's PATH, or call it as `/usr/sbin/iftop`.

## iftop: who is talking

`iftop` is the one you want when a link is full and you need to know which conversation is filling it. Its `-t` flag drops ncurses entirely and prints plain text, which makes it scriptable and SSH friendly:

```
j@llmbits:~$ sudo iftop -i ens224 -t -n -N -P -s 10
interface: ens224
IP address is: 10.77.0.100
MAC address is: 00:0c:29:b1:cc:47
   # Host name (port/service if enabled)   last 2s   last 10s   last 40s cumulative
------------------------------------------------------------------------------------
   1 10.77.0.100:47310                 =>    39.7Mb     34.7Mb     34.7Mb     43.4MB
     10.77.3.10:5201                   <=     751Kb      663Kb      663Kb      829KB
------------------------------------------------------------------------------------
Total send rate:                             39.7Mb     34.7Mb     34.7Mb
Total receive rate:                           751Kb      663Kb      663Kb
Total send and receive rate:                 40.5Mb     35.4Mb     35.4Mb
------------------------------------------------------------------------------------
Peak rate (sent/received/total):             39.7Mb      751Kb     40.5Mb
Cumulative (sent/received/total):            43.4MB      829KB     44.2MB
```

The three rate columns are two, ten and forty second moving averages, which is genuinely useful: a burst shows up in the 2s column and has not touched the 40s column yet. `-n` skips DNS, `-N` skips port-to-service translation and `-P` shows ports. Skip `-n` on a busy box and iftop will generate a reverse lookup for every host it sees, which is its own kind of traffic problem.

Notice the asymmetry: 39.7 Mb out, 751 Kb back. That reverse trickle is pure TCP acknowledgments, roughly 2 percent of the forward rate, which is what a healthy bulk transfer looks like.

Here is the real ncurses interface, captured out of a live terminal:

```
                  1.86Gb            3.73Gb             5.59Gb            7.45Gb       9.31Gb
mqqqqqqqqqqqqqqqqqvqqqqqqqqqqqqqqqqqvqqqqqqqqqqqqqqqqqqvqqqqqqqqqqqqqqqqqvqqqqqqqqqqqqqqqqqq
10.77.0.100:41316                => 10.77.3.10:5201                  40.0Mb  36.9Mb  36.9Mb
                                 <=                                   760Kb   709Kb   709Kb

qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
TX:             cum:   36.9MB   peak:   40.0Mb              rates:   40.0Mb  36.9Mb  36.9Mb
RX:                     709KB            760Kb                        760Kb   709Kb   709Kb
TOTAL:                 37.6MB           40.7Mb                       40.7Mb  37.6Mb  37.6Mb
```

Those runs of `q` and `v` are not corruption. ncurses draws horizontal rules using the VT100 alternate character set, where `q` in the alternate set renders as a horizontal line and `v` as a tee. Scrape the terminal buffer programmatically and you get the raw characters instead of the glyphs. On your own screen they render as proper box drawing.

That frame was captured with `tmux`, which is the honest way to get an ncurses UI into a text file:

```
tmux new-session -d -s ift -x 92 -y 20 "iftop -i ens224 -n -N -P"
sleep 9
tmux capture-pane -p -t ift
tmux kill-session -t ift
```

Worth remembering any time you need to hand someone the output of a full-screen tool over a chat window.

## nload: is the link busy

`nload` does one thing: it draws incoming and outgoing throughput on one interface as a scrolling bar chart. There is no text mode, so here is the real frame:

```
Device ens224 [10.77.0.100] (1/1):
============================================================================================
Incoming:

                                                               Curr: 1.01 MBit/s
                                                               Avg: 0.97 MBit/s
                                                               Min: 0.90 MBit/s
                                                               Max: 1.02 MBit/s
                                                ###|#########  Ttl: 48.83 MByte
Outgoing:
                                                #############
                                                #############
                                                #############
                                                #############
                                                #############  Curr: 42.68 MBit/s
                                                #############  Avg: 40.62 MBit/s
                                                #############  Min: 37.71 MBit/s
                                                #############  Max: 43.04 MBit/s
                                                #############  Ttl: 1.11 GByte
```

The min, average and max are computed over a rolling window, five minutes by default, adjustable with `-a`. `-m` shows several interfaces at once but drops the graphs. `-u m` forces MBit/s units so the scale stops jumping between K and M as traffic changes, which matters if you are watching for a pattern rather than a number.

nload's value is that a human can glance at it and know whether the shape is a steady transfer, a sawtooth, or a spike. Its limitation is that it will never tell you what the traffic is.

## bmon: rates plus the error counters

`bmon` sits between nload and a full monitoring agent. The curses view gives you rates and a graph:

```
 ens224                                                                            bmon 4.0
Interfaces                     x RX bps       pps     %x TX bps       pps     %
 +ens224                       x   1.05Mb    1.97K     x  45.71Mb    1.14K
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqvqqqqqqqqqqqqqqqqqqqqqqqvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
      Kb                      (RX Bits/second)
 1069.63 ||||........................................................
  891.36 |||||||||...................................................
  713.09 |||||||||...................................................
  534.82 |||||||||...................................................
      Mb                      (TX Bits/second)
   46.41 ||.|........................................................
   38.68 |||||||||...................................................
   30.94 |||||||||...................................................
   23.21 |||||||||...................................................
    7.74 |||||||||...................................................
         1   5   10   15   20   25   30   35   40   45   50   55   60
```

The packets-per-second column is the thing nload does not give you, and it matters. 1.97K packets per second inbound against 1.14K outbound, while the byte rate runs 45 to 1 the other way, is the signature of a bulk transfer: many small ACKs coming back, fewer large segments going out.

Press `d` and bmon expands into per-interface detail. In text mode that is `-o 'ascii:diagram=details'`:

```
j@llmbits:~$ bmon -p ens224 -b -o "ascii:diagram=details;quitafter=1" -r 1
Interfaces
 ens224 (3)
  Bits                                       401.05Mb          9.19Gb
  Packets                                    431.63K         288.45K
  Abort Error                                     0               0
  Carrier Error                                   0               0
  Collisions                                      0               0
  CRC Error                                       0               0
  Dropped                                         0               0
  Errors                                          0               0
  FIFO Error                                      0               0
  Frame Error                                     0               0
```

That is the whole driver error table next to the throughput, which makes bmon a decent one-stop check when someone says a link is "flaky." Rates plus zero errors points at congestion or the far end, not the NIC. Those same counters, and where they come from, are the subject of [/proc/net and networking sysctls](https://www.pinglabz.com/proc-net-linux/).

For scripts, the `format` module emits whatever you ask for:

```
j@llmbits:~$ bmon -p ens224 -b -r 1 \
    -o "format:quitafter=3;fmt=\$(element:name) rx_bps=\$(attr:rx:bytes) tx_bps=\$(attr:tx:bytes)\n"
ens224 rx_bps=50131356 tx_bps=1148251993
ens224 rx_bps=50249280 tx_bps=1153177101
ens224 rx_bps=50365068 tx_bps=1158095097
```

Read those carefully, because the placeholder name is a trap. `attr:rx:bytes` returns the **cumulative** byte counter, not a rate, no matter what you call the variable. Between the first and second sample the transmit counter moved 4,925,108 bytes in one second, which is 39.4 Mbit/s. You have to do that subtraction yourself. The placeholder list is long; `bmon -o format:help` prints all of it.

## vnstat: the only one with a memory

Every other tool on this page starts from zero when you launch it. `vnstat` runs a daemon that polls the kernel counters and writes them to a SQLite database, so it can answer questions about last Tuesday:

```
j@llmbits:~$ vnstat -i ens224
Database updated: 2026-08-19 09:20:00

   ens224 since 2026-08-16

          rx:  4.07 GiB      tx:  6.03 GiB      total:  10.10 GiB

   monthly
                     rx      |     tx      |    total    |   avg. rate
     ------------------------+-------------+-------------+---------------
       2026-08      4.07 GiB |    6.03 GiB |   10.10 GiB |  371.87 kbit/s
     ------------------------+-------------+-------------+---------------
     estimated     23.07 GiB |   34.21 GiB |   57.27 GiB |

   daily
                     rx      |     tx      |    total    |   avg. rate
     ------------------------+-------------+-------------+---------------
     yesterday      3.61 MiB |  125.68 MiB |  129.30 MiB |   12.55 kbit/s
         today     67.94 MiB |    1.91 GiB |    1.98 GiB |  505.83 kbit/s
     ------------------------+-------------+-------------+---------------
     estimated    174.70 MiB |    4.92 GiB |    5.09 GiB |
```

Three days of lab work, 10.10 GiB in total, with projections for the month and the day. The daily rows are the useful part in production: 129 MiB yesterday against 1.98 GiB today is the kind of step change that tells you something started, and it is visible without any monitoring stack at all. On a metered link or a cloud instance with egress charges, this is the tool that tells you whether the bill is going to hurt.

It also does live sampling, which is where it becomes comparable to the others:

```
j@llmbits:~$ vnstat -i ens224 -tr 8
Sampling ens224 (8 seconds average)...
23457 packets sampled in 8 seconds
Traffic average for ens224

      rx       980.05 kbit/s          1834 packets/s
      tx        41.94 Mbit/s          1097 packets/s
```

The important caveat with vnstat is that the daemon must have been running to have data. Install it today and it knows nothing about yesterday. Install it on every host as a matter of routine and in six months it will answer a question you have not thought to ask yet.

## nethogs: which process

This is the one that closes the loop. Every other tool tells you traffic exists; nethogs tells you which program is generating it, by matching sockets to PIDs through `/proc`:

```
j@llmbits:~$ sudo nethogs -t -d 3 -c 3 ens224
Ethernet link detected

Refreshing:
unknown TCP/0/0	0	0

Refreshing:
iperf3/15152/0	2968.17	71.2242
unknown TCP/0/0	0	0

Refreshing:
iperf3/15152/0	4246.18	100.989
unknown TCP/0/0	0	0
```

The columns are program/PID/UID, then KB/s sent and KB/s received. `iperf3` with PID 15152 owned by UID 0 was sending 4,246 KB/s, which is 34 Mbit/s.

Two honest observations. First, the value climbs across refreshes (2,968 then 4,246) because nethogs reports a running average that needs a few cycles to converge; the first sample is always low. Second, the `unknown TCP` row is where nethogs puts traffic whose socket it cannot attribute, usually because the connection closed before it looked. On a busy server that row can be substantial, so treat nethogs as a strong hint rather than an audit.

## ifstat: numbers you can pipe

```
j@llmbits:~$ ifstat -i ens224 1 5
      ens224
 KB/s in  KB/s out
  129.03   5513.65
  120.35   5266.27
  126.43   5514.85
  131.35   5556.78
  131.58   5569.88
```

No graph, no colors, no curses. Five one-second samples, two columns, done. When you want to feed a number into a script or a monitoring check, this is the least friction path.

## Why the numbers disagree

Here is everything the tools reported for the same single TCP flow, alongside what iperf3 said about itself:

iperf3 (the application)

40.9 Mbit/s

TCP payload only. Headers and retransmitted bytes are excluded by definition.

nload

42.68 Mbit/s

Interface counters. Every byte that left the NIC, headers and retransmits included.

ifstat

44.1 Mbit/s

5,513 KB/s, same counters as nload, sampled over a different second.

bmon

44.78 Mbit/s

Also interface counters, with packets per second alongside.

vnstat -tr

41.94 Mbit/s

Packet sampling over 8 seconds, so short bursts get averaged away.

iftop

39.7 Mbit/s

Captured packets counted at IP layer, per conversation. Excludes Ethernet framing.

nethogs

34.7 Mbit/s

4,332 KB/s, still converging. A rolling average that had not settled.

The spread is roughly 35 to 45 Mbit/s for one flow. Three things explain almost all of it.

**Layer.** iperf3 counts application payload. The interface counters count the full Ethernet frame. With a 1448-byte payload in a 1514-byte frame, framing overhead is about 4.4 percent. Apply that to iperf3's 40.9 Mbit/s and you get 42.7 Mbit/s, which is nload's 42.68 to two decimal places. The tools are not disagreeing at all; they are measuring different things correctly.

**Retransmissions.** That same transfer retransmitted 1,547 segments. Those bytes crossed the wire and were counted by every interface-level tool, and were excluded from iperf3's goodput figure because they were duplicates. On a lossy path this gap widens fast, and a growing gap between application throughput and interface throughput is one of the cleanest loss indicators you can get without a packet capture.

**Averaging window.** iftop's 2s, 10s and 40s columns disagree with each other on the same screen. vnstat averaged over 8 seconds. nethogs uses a rolling mean that needs several cycles. If you compare a number from one tool against a number from another without matching windows, you are comparing noise.

One more piece of honesty about this particular lab: the ceiling around 40 Mbit/s is the virtual router, not the tools and not the host. The router carrying the flow reported `txload 255/255` on its outbound interface, which is a saturated link. These figures are perfectly good for comparing what the tools measure. They are not throughput benchmarks, and none of them should be quoted as such.

## Which one to install

A reasonable default set for a Linux box you operate: **vnstat** everywhere, because it is the only one that answers questions about the past and it costs nothing to run. **iftop** and **nethogs** on anything you might have to debug interactively, because "which conversation" and "which process" are the two questions you will actually be asked. **nload** or **bmon** if you like a visual, with bmon winning if you also want error counters. **ifstat** when a script needs a number.

And when the answer to "how much" turns into "why," you have left this article's territory. Go and look at the packets with [tshark](https://www.pinglabz.com/tshark-linux/), [ngrep](https://www.pinglabz.com/ngrep-linux/) or [tcpdump](https://www.pinglabz.com/tcpdump-for-network-engineers/), and check per-socket state with [ss](https://www.pinglabz.com/ss-command-linux/).

## Key takeaways

- On Debian, `iftop` and `nethogs` live in `/usr/sbin`, so they look uninstalled to a normal user. They are not.
- Interface-counter tools (nload, ifstat, bmon) and packet-capture tools (iftop, nethogs, vnstat sampling) measure different layers. Expect a few percent difference and do not panic about it.
- Application throughput plus framing overhead should equal interface throughput. In this test 40.9 Mbit/s of goodput plus 4.4 percent of framing came to 42.7 against a measured 42.68.
- A widening gap between application throughput and interface throughput means retransmissions. That is a free loss detector.
- Always match averaging windows before comparing two tools. iftop's own 2s and 40s columns disagree by design.
- Only vnstat remembers anything. Install it before you need it, because it cannot backfill.
- nethogs is the only one that names a process, but its first sample is always low and its `unknown TCP` bucket is real traffic it could not attribute.
- To get an ncurses UI into a text file, run it under `tmux` and use `capture-pane -p`. The stray `q` characters in the result are the VT100 alternate character set, not corruption.

Bandwidth monitors tell you how much. For what and why, the rest of the toolkit is indexed in the [complete guide to Linux networking commands](https://www.pinglabz.com/linux-networking-commands/), and the counters every one of these tools reads are explained in [/proc/net and networking sysctls](https://www.pinglabz.com/proc-net-linux/).