iperf3 measures the maximum throughput between two hosts by sending as much TCP or UDP traffic as possible from a client to a server and reporting the rate, retransmissions, jitter and packet loss. It is the standard tool to check what a link really delivers, to compare private and public network paths, or to tell whether a slow transfer is caused by the network or by something else. In this tutorial you will install iperf3 on two Ubuntu 24.04 servers and run TCP, UDP, multi-stream and bidirectional tests, save results as JSON and use them to track down common bottlenecks.
Prerequisites
To follow this guide you need:
- Two servers running Ubuntu 24.04 LTS, for example two CubePath VPS instances, each with a non-root user with
sudoprivileges. One acts as the iperf3 server and the other as the client. - The IP address of the server that the client can reach. In this guide it is
your_server_ip, and the client's address isyour_client_ip. - TCP and UDP port 5201 reachable on the server from the client.
Test the path you care about. If your servers talk to each other over a private network, use the server's private IP; if you want to measure Internet performance, use the public IP.
Step 1 - Installing iperf3 on both servers
Install the package on both machines:
sudo apt update
sudo apt install iperf3
During installation, a dialog asks whether iperf3 should start automatically as a daemon. Choose No. You will start the server only while you test, which avoids leaving an open service that anyone could use to flood your bandwidth.
Check the version on each machine:
iperf3 --version
iperf 3.16 (cJSON 1.7.15)
Linux server1 6.8.0-45-generic #45-Ubuntu SMP PREEMPT_DYNAMIC ... x86_64
Optional features available: CPU affinity setting, IPv6 flow label, SCTP, TCP congestion algorithm setting, sendfile / zerocopy, socket pacing, authentication, bind to device, support IPv4 don't fragment, POSIX threads
Step 2 - Opening the port on the server
If UFW is active on the server, allow port 5201 only from the client, for both TCP and UDP:
sudo ufw allow from your_client_ip to any port 5201 proto tcp
sudo ufw allow from your_client_ip to any port 5201 proto udp
Verify the rules:
sudo ufw status
To Action From
-- ------ ----
5201/tcp ALLOW your_client_ip
5201/udp ALLOW your_client_ip
Step 3 - Starting the iperf3 server
On the server, start iperf3 in server mode. It listens on port 5201 and waits for clients:
iperf3 -s
-----------------------------------------------------------
Server listening on 5201 (test #1)
-----------------------------------------------------------
Leave this terminal open. The server handles one test at a time and prints the results of each test as it runs. If you only need a single test, iperf3 -s -1 exits after serving one client. Press Ctrl+C to stop the server.
From the client, confirm the port is reachable before running a test:
nc -zv your_server_ip 5201
Connection to your_server_ip 5201 port [tcp/*] succeeded!
Step 4 - Running a basic TCP test
On the client, run a 10-second TCP test. By default the client sends data to the server, so this measures upload from client to server:
iperf3 -c your_server_ip
Connecting to host your_server_ip, port 5201
[ 5] local 10.0.0.20 port 43216 connected to 10.0.0.10 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 1.09 GBytes 9.35 Gbits/sec 0 3.02 MBytes
[ 5] 1.00-2.00 sec 1.10 GBytes 9.41 Gbits/sec 0 3.02 MBytes
...
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 10.9 GBytes 9.38 Gbits/sec 12 sender
[ 5] 0.00-10.00 sec 10.9 GBytes 9.37 Gbits/sec receiver
How to read the result:
- Bitrate on the
receiverline is the throughput that actually arrived. This is the number to report. - Retr is the number of TCP retransmissions. A few are normal; hundreds or thousands mean packet loss somewhere on the path.
- Cwnd is the TCP congestion window. If it stays small, the connection is limited by loss or latency rather than by link capacity.
For more stable numbers, run longer and ignore the first seconds, while TCP is still ramping up. -t sets the duration and -O omits the first N seconds from the totals:
iperf3 -c your_server_ip -t 30 -O 3
Step 5 - Testing the other direction and both at once
Links are often asymmetric, and a problem may only show in one direction. -R (reverse) makes the server send and the client receive, which measures download to the client without swapping roles:
iperf3 -c your_server_ip -R -t 30
--bidir sends in both directions at the same time and reports each direction separately, which is useful for replication or backup traffic that flows both ways:
iperf3 -c your_server_ip --bidir -t 30
[ ID][Role] Interval Transfer Bitrate Retr
[ 5][TX-C] 0.00-30.00 sec 32.1 GBytes 9.19 Gbits/sec 48 sender
[ 5][TX-C] 0.00-30.00 sec 32.1 GBytes 9.19 Gbits/sec receiver
[ 7][RX-C] 0.00-30.00 sec 31.8 GBytes 9.11 Gbits/sec 30 sender
[ 7][RX-C] 0.00-30.00 sec 31.8 GBytes 9.11 Gbits/sec receiver
TX-C is traffic sent by the client and RX-C traffic received by it.
Step 6 - Using parallel streams
A single TCP stream is limited by one CPU core and by how fast TCP can grow its window over the path's latency. On fast or long-distance links, several streams in parallel usually get closer to the real capacity. Run 4 streams with -P:
iperf3 -c your_server_ip -P 4 -t 30
...
[SUM] 0.00-30.00 sec 34.6 GBytes 9.91 Gbits/sec 103 sender
[SUM] 0.00-30.00 sec 34.6 GBytes 9.90 Gbits/sec receiver
Look at the [SUM] lines for the total. Compare with the single-stream result:
- If one stream is much slower than four, the limit is per-connection (window size, latency or a single CPU core), not the link. Applications that use one connection, such as
scp, will see the lower figure. - If one and four streams give the same result, you are measuring the capacity of the link or of a rate limit on it.
Step 7 - Measuring UDP loss and jitter
UDP tests do not adapt to congestion like TCP; the client sends at the rate you set with -b and the server reports how many datagrams were lost and how much their arrival time varied (jitter). This is what matters for VoIP, video and games. Without -b, iperf3 sends UDP at only 1 Mbit/s, so always set it:
iperf3 -c your_server_ip -u -b 500M -t 30
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[ 5] 0.00-30.00 sec 1.75 GBytes 500 Mbits/sec 0.000 ms 0/1294903 (0%) sender
[ 5] 0.00-30.00 sec 1.75 GBytes 499 Mbits/sec 0.021 ms 452/1294903 (0.035%) receiver
Read the receiver line: here 0.035% of datagrams were lost and jitter was 0.021 ms. Increase -b step by step (for example 500M, 1G, 2G) until loss starts to climb; that rate is roughly the usable UDP capacity of the path. Loss above about 1% or jitter above a few milliseconds will be noticeable in real-time applications.
UDP tests at high rates can also be limited by the CPU of either server rather than the network. If loss appears while one CPU core is at 100% in top, the result says more about the machine than about the link.
Step 8 - Saving results as JSON
For comparisons over time or between servers, save results in JSON with -J and extract the figures with jq:
sudo apt install jq
iperf3 -c your_server_ip -t 30 -O 3 -J > tcp-$(date +%F-%H%M).json
Extract the received throughput in Gbit/s and the total retransmissions:
jq '.end.sum_received.bits_per_second / 1e9, .end.sum_sent.retransmits' tcp-*.json
9.372811
12
For a UDP test, the summary is under .end.sum:
iperf3 -c your_server_ip -u -b 500M -t 30 -J > udp.json
jq '.end.sum | {mbps: (.bits_per_second / 1e6), jitter_ms, lost_percent}' udp.json
{
"mbps": 499.87,
"jitter_ms": 0.021,
"lost_percent": 0.035
}
Step 9 - Diagnosing a slow result
When the numbers are lower than expected, change one variable at a time:
-
Check the path. Run
ping -c 20 your_server_ipto see latency and loss. High latency limits single-stream TCP; any ping loss will show up as TCP retransmissions. -
Compare 1 and several streams (Step 6). A big difference points to per-connection limits, not the link.
-
Test both directions (Step 5). If only one direction is slow, look at that direction's path or at shaping on one side.
-
Check the MTU. A path MTU smaller than the interface MTU causes fragmentation or dropped packets. With a 1500-byte MTU, this must succeed without fragmentation:
ping -c 3 -M do -s 1472 your_server_ipIf it reports
message too longor times out, lower the size until it works to find the real path MTU. -
Check CPU usage with
topon both servers during the test. Ifiperf3sits at 100% of one core, the test is CPU bound; use-Pto spread the load, or-Z(zero copy) on the client to reduce CPU use. -
Check interface counters for errors and drops:
ip -s link showGrowing
errorsordroppedcounters under load point to the interface, driver or host rather than the network.
Troubleshooting
iperf3: error - unable to connect to server: Connection refused. The server is not running, or it is listening on another port. Start iperf3 -s on the server and check the port with sudo ss -ltnp | grep 5201.
unable to connect to server: Connection timed out. A firewall drops the traffic. Check UFW on the server (Step 2) and any cloud or network firewall in between.
iperf3: error - the server is busy running a test. try again later. The server handles one test at a time. Wait for the current test to finish, or start a second server on another port with iperf3 -s -p 5202 and connect with -p 5202.
The UDP test shows 100% loss. TCP port 5201 is open but UDP is blocked. Allow 5201/udp on the server.
Conclusion
You can now measure TCP throughput in each direction, see how parallel streams change the result, quantify UDP loss and jitter, and store results as JSON to compare over time. As next steps, run the same tests between each pair of servers that exchange heavy traffic and keep the results as a baseline, compare private and public network paths, and combine the network figures with disk and CPU benchmarks from sysbench to find the real limit of a workload.
