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
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.2MBThe 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.6MbThose 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 iftWorth 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 GByteThe 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 60The 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 0That 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=1158095097Read 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/sThe 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 0The 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.88No 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:
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,
iftopandnethogslive 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 TCPbucket is real traffic it could not attribute. - To get an ncurses UI into a text file, run it under
tmuxand usecapture-pane -p. The strayqcharacters 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.