iperf3 Explained: Measuring Network Throughput Properly
An online speed test tells you about your internet provider. It tells you nothing about the link between two machines in your own building, which is usually the thing being complained about.
Install and run
You need it on both ends. It is the same binary in either role.
sudo apt install iperf3 # Debian and Ubuntu
sudo dnf install iperf3 # Fedora
sudo pacman -S iperf3 # Arch
# server, on the far end
iperf3 -s
# client
iperf3 -c 192.168.1.50
Default port is 5201, TCP, ten seconds, one stream, client to server.
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 1.09 GBytes 938 Mbits/sec 0 sender
[ 5] 0.00-10.00 sec 1.09 GBytes 936 Mbits/sec receiver
938 Mbit/s on gigabit ethernet is a correct result. Framing overhead accounts for the rest; you will not see 1000.
Read the receiver line. The sender figure counts bytes handed to the kernel, which can briefly exceed what actually crossed the wire.
The flags you will use
iperf3 -c 192.168.1.50 -t 30 -P 4 -i 1
| Flag | Effect |
|---|---|
-t 30 | Duration in seconds |
-P 4 | Parallel streams |
-i 1 | Report every second |
-R | Reverse: server sends to client |
--bidir | Both directions at once |
-u | UDP instead of TCP |
-b 100M | Target bitrate, required for UDP |
-p 5202 | Port |
-4 / -6 | Force IPv4 or IPv6 |
-O 2 | Omit the first 2 seconds |
-J | JSON output |
-O 2 is underused. TCP slow start means the first second or two is always below the achievable rate, and on a short test that drags the average down.
-t 10 is too short for anything real. Thirty seconds catches thermal effects, buffer exhaustion and background traffic that ten seconds misses.
Parallel streams, and why the default understates
# one stream on a 10 gigabit link
iperf3 -c 10.0.0.5
# 3.21 Gbits/sec
# four streams, same link
iperf3 -c 10.0.0.5 -P 4
# 9.41 Gbits/sec
Two reasons a single stream falls short.
The bandwidth delay product. A TCP connection in flight is limited by window size divided by round trip time. On a fast link with any meaningful latency, one stream cannot fill it regardless of how good the hardware is.
iperf3 is single threaded. At multi-gigabit rates, one CPU core handling one stream becomes the limit, not the network. This is a known design characteristic, and it is why some people still use iperf2 for 40 and 100 gigabit work.
Use -P 4 or -P 8 when testing anything above a gigabit. If parallel streams give a much higher total, the link was never the problem and the single-stream figure was measuring a connection, not a network.
Both directions
# server to client
iperf3 -c 192.168.1.50 -R
# simultaneously, both ways
iperf3 -c 192.168.1.50 --bidir
Always test both. Asymmetry is expected on consumer internet connections and is a finding on a local network that should be symmetric. Duplex mismatches, one-sided offload problems and a failing cable all show up as a large gap in one direction.
# check for a duplex mismatch while you are there
sudo ethtool eth0 | grep -E 'Speed|Duplex'
cat /sys/class/net/eth0/speed
A gigabit port negotiating 100 Mbit half duplex is almost always a cable or a port, and it explains a lot of “the network is slow” reports on its own.
UDP, for loss and jitter
# push 100 Mbit and see what arrives
iperf3 -c 192.168.1.50 -u -b 100M -t 30
# find where loss begins
iperf3 -c 192.168.1.50 -u -b 900M -t 30
[ ID] Interval Transfer Bitrate Jitter Lost/Total
[ 5] 0.00-30.00 s 340 MBytes 95.1 Mbit/s 0.085 ms 12/247890 (0.005%)
-b is mandatory in practice. Without a target rate, iperf3 defaults to 1 Mbit/s for UDP, which measures nothing. Naming a rate and checking what arrives is the actual test.
Three numbers to read:
Loss above a fraction of a percent on a wired LAN means something is wrong. On wireless, a few percent is ordinary.
Jitter is variation in arrival timing. Under a millisecond is good; tens of milliseconds ruin voice and video regardless of how much bandwidth exists.
Achieved rate versus target. If you asked for 900 Mbit and got 400 with heavy loss, you have found the saturation point.
UDP is the right tool when the complaint is “calls break up” rather than “downloads are slow”. Those are different problems and TCP throughput answers only the second.
Bandwidth is not the problem as often as people think
# latency first
ping -c 100 192.168.1.50
# latency under load, which is the real question
ping -c 100 192.168.1.50 &
iperf3 -c 192.168.1.50 -t 20
That combination is the most useful test in this article. If ping times are 1 ms idle and 300 ms while iperf3 runs, you have bufferbloat: oversized queues filling under load and delaying everything interactive. The link has plenty of bandwidth and feels terrible.
The fix is queue management rather than more capacity:
# what queueing discipline is in use
tc qdisc show dev eth0
# fq_codel is a reasonable default
sudo tc qdisc replace dev eth0 root fq_codel
Our traffic shaping guide covers doing this properly, and on a router cake with a configured rate is the usual answer.
Isolating where the limit is
Test in segments rather than end to end.
laptop ── switch ── router ── modem ── internet
# 1. laptop to another machine on the same switch
iperf3 -c 192.168.1.20 -P 4
# 2. laptop to the router
iperf3 -c 192.168.1.1 -P 4
# 3. router to the internet, from a machine behind it
iperf3 -c ping.online.net -P 4
Each hop that holds up rules out everything behind it. A common outcome is that step 2 is slow because the router’s CPU cannot forward at line rate, which no amount of switch or cable replacement will fix. Consumer routers and cheap VPS instances both do this.
Public iperf3 servers exist for step 3, and they are shared, rate-limited and variable. Fine for a rough figure, not for a measurement you intend to rely on.
Wireless
# signal and negotiated rate first
iw dev wlan0 link
iwconfig wlan0 2>/dev/null
# then throughput, both directions
iperf3 -c 192.168.1.50 -t 30
iperf3 -c 192.168.1.50 -t 30 -R
Expect roughly half the negotiated rate as real throughput. Wi-Fi is half duplex with substantial protocol overhead, so a link reporting 866 Mbit delivering 400 is normal rather than broken.
Test from the same position each time, since moving a metre changes the result more than most configuration does. Our wifi guide covers the driver side.
Running the server safely
# bind to an internal address only
iperf3 -s -B 192.168.1.50
# non-default port, one client at a time
iperf3 -s -p 5202 -1
Do not leave a server running on anything internet facing. It accepts connections from anyone who can reach the port and will saturate your link on request. -1 makes it exit after one client, which is a good habit.
For repeated testing, a systemd unit bound to an internal interface and firewalled is reasonable:
# /etc/systemd/system/iperf3.service
[Unit]
Description=iperf3 server
After=network-online.target
[Service]
ExecStart=/usr/bin/iperf3 -s -B 10.0.0.5
DynamicUser=yes
Restart=on-failure
[Install]
WantedBy=multi-user.target
DynamicUser=yes means it runs as a transient unprivileged account, which is the cheapest hardening available. Our service hardening guide covers going further.
Scripting it
# JSON, for anything automated
iperf3 -c 192.168.1.50 -t 30 -P 4 -J > result.json
jq '.end.sum_received.bits_per_second / 1e9' result.json
jq '.end.sum_sent.retransmits' result.json
The retransmit count is worth recording alongside throughput. Throughput that looks acceptable with a high retransmit count means the link is marginal and will behave badly under real mixed traffic.
What iperf3 does not tell you
Real application performance. It measures raw throughput with no application logic, no disk, no TLS. A file transfer that is slower than your iperf3 result is usually limited by disk or by the protocol, which is where fio comes in.
Anything about a single flow’s fairness or how the link behaves with mixed traffic.
Where a packet is being dropped. For that you need tcpdump, or mtr for per-hop loss along a path.
Frequently Asked Questions
Why is my iperf3 result lower than my link speed?
A single TCP stream is limited by the bandwidth delay product and by how fast one CPU core can process packets. On a 10 gigabit link a single stream frequently reaches only a few gigabits. Run parallel streams with -P to measure the link rather than one connection.
What is the difference between iperf and iperf3?
iperf3 is a rewrite, not a new version of the same code, and they are not compatible: an iperf3 client cannot talk to an iperf2 server. iperf3 is single threaded by design, which is why a very fast link can be limited by one core, and iperf2 is still occasionally preferred for multi-gigabit multi-stream work.
Should I test with TCP or UDP?
TCP for how much useful throughput you can get, which is what almost every question is about. UDP with an explicit target rate for packet loss and jitter, which is what matters for voice, video and gaming. UDP without a rate limit measures almost nothing useful.
Does iperf3 measure through my router or to it?
To whatever runs the server, so the path depends entirely on where you put it. Running the server on a machine on the far side of the link under test is the point; running it on the router measures the router, and routers with weak CPUs are often the bottleneck being blamed on the link.
Why do my two directions give different results?
Asymmetric links are normal on consumer connections, and half duplex mismatches, differing offload settings and one-sided congestion all produce asymmetry on a local network. Test both directions with -R and treat a large unexplained gap on a supposedly symmetric link as a finding rather than noise.
Is it safe to leave an iperf3 server running?
Not on anything internet facing. It accepts connections from anyone who can reach the port and will happily saturate your link on request. Bind it to an internal address, firewall the port, and stop it when the testing is done rather than leaving it enabled.