iftop, nload, bmon, vnstat, nethogs Compared

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.

iftop text-mode output in a dark terminal card showing 39.7 Mb send and 751 Kb receive rates for one TCP flow on a Debian host.

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

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, ngrep or tcpdump, and check per-socket state with ss.

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, and the counters every one of these tools reads are explained in /proc/net and networking sysctls.

Read next