Repository navigation
Tests Network iperf3
YABS measures your server's network throughput by running iperf3 against a list of donated public iperf servers around the world — once sending, once receiving, on IPv4 and again on IPv6 if you have it. Each location gets ~10 seconds of traffic per direction with 8 parallel TCP streams. "busy" in the table means the far-end server couldn't be tested, not that your server is slow.
iperf3 Network Speed Tests (IPv4):
---------------------------------
Provider | Location (Link) | Send Speed | Recv Speed | Ping
----- | ----- | ---- | ---- | ----
Clouvider | London, UK (10G) | 940 Mbits/sec | 935 Mbits/sec | 8.14 ms
Eranium | Amsterdam, NL (100G) | 938 Mbits/sec | 942 Mbits/sec | 11.2 ms
Uztelecom | Tashkent, UZ (10G) | busy | 312 Mbits/sec | 98.7 ms
Leaseweb | Singapore, SG (10G) | 255 Mbits/sec | 289 Mbits/sec | 182 ms
Clouvider | Los Angeles, CA, US (10G) | 301 Mbits/sec | 344 Mbits/sec | 141 ms
Leaseweb | NYC, NY, US (10G) | 498 Mbits/sec | 512 Mbits/sec | 78.3 ms
Edgoo | Sao Paulo, BR (1G) | 118 Mbits/sec | 130 Mbits/sec | 196 ms
-
Send Speed = your server → the remote server (your "upload"). Recv Speed = remote → your server (your "download"). The direction is named from your server's perspective; the number itself is what iperf3 measured at the
receiverend, which is the far end on a send test and your server on a-Rreceive test. (This trips people up regularly — the maintainer has explained it on LowEndTalk since October 2019.) - Judge the nearest locations first. Distant servers are limited by latency and transit, not by your port — a 1 Gbps VPS showing 200–600 Mbit/s to another continent is normal. See Interpreting Results.
- Ping is a single ICMP echo — a one-sample latency, not a measurement campaign.
Why iperf3 and not something like speedtest.net? From the maintainer, Oct 2019: "Speedtest is a single threaded connection ... with iperf, you'll have many parallel connections providing a better representation of how your server will handle many high-bandwidth connections simultaneously." Eight parallel streams also fill fat-or-long links that a single TCP flow can't.
Stored in the IPERF_LOCS array, five fields per server: host, port range, provider name, location label (with advertised link), supported modes. Current list (yabs.sh v2026-10-31):
| Host | Ports | Provider | Location (link) | Modes |
|---|---|---|---|---|
lon.speedtest.clouvider.net |
5200–5209 | Clouvider | London, UK (10G) | IPv4+IPv6 |
iperf-ams-nl.eranium.net |
5201–5210 | Eranium | Amsterdam, NL (100G) | IPv4+IPv6 |
speedtest.uztelecom.uz |
5200–5209 | Uztelecom | Tashkent, UZ (10G) | IPv4+IPv6 |
speedtest.sin1.sg.leaseweb.net |
5201–5210 | Leaseweb | Singapore, SG (10G) | IPv4+IPv6 |
la.speedtest.clouvider.net |
5200–5209 | Clouvider | Los Angeles, CA, US (10G) | IPv4+IPv6 |
speedtest.nyc1.us.leaseweb.net |
5201–5210 | Leaseweb | NYC, NY, US (10G) | IPv4+IPv6 |
speedtest.sao1.edgoo.net |
9204–9240 | Edgoo | Sao Paulo, BR (1G) | IPv4+IPv6 |
A commented-out Telia Helsinki entry and an # AFR placeholder comment remain in the array for future fill-ins.
With -r the list shrinks to three: Clouvider London, Leaseweb Singapore, Leaseweb NYC. (Both the README and the code comment above the -r array still name older servers — the array itself is authoritative.)
With -p "host:port_range:name:location:modes[,...]" the default list is replaced entirely. See Command-Line Flags for the exact syntax and the comma/colon pitfalls.
A common use for -p is testing against your own iperf3 server — run iperf3 -s (optionally -p 5201) on a second host, then:
curl -sL yabs.sh | bash -s -- -p "myserver.example.net:5201-5201:Mine:Home Lab (1G):IPv4"This removes the shared-server variable entirely: no busy, no other users' traffic — the cleanest possible read on your own uplink to a known endpoint. Note the 9204-9240 Edgoo range vs the usual 520x ranges — port range is per-server config, which is why each list entry carries its own.
For each server that advertises the current mode, per direction:
timeout 15 iperf3 -4|-6 -c <host> -p <random-port> -P 8 # send
timeout 15 iperf3 -4|-6 -c <host> -p <random-port> -P 8 -R # receive| Flag | Value | Why |
|---|---|---|
-4 / -6
|
per pass | Forces the address family for the current table. |
-c <host> |
server host | Client mode against the public server. |
-p <port> |
random in range | Each attempt picks a fresh port (shuf -i) — different ports can land on different daemon slots. |
-P 8 |
8 streams | Parallel connections; fills links a single TCP flow can't and better models real multi-connection load. |
-R |
receive pass only | Reverses direction — server sends, client receives. |
| (duration) | default 10 s | No -t — iperf3's built-in 10-second test. |
timeout 15 |
wrapper | External kill so a wedged server can't stall the run. |
not used: -u, -b, -t, -J
|
— | No UDP tests (so no jitter/loss metrics), no rate caps, no JSON parse — output stays human-format for the table. |
flowchart TD
A[For each server in list] --> B{"supports this IP mode?"}
B -->|no| A
B -->|yes| C["send test: pick random port via shuf,<br/>iperf3 -P 8, timeout 15s, up to 3 attempts"]
C --> D{result contains 'receiver'<br/>and no 'error'?}
D -->|"yes, speed > 0.00"| E[accept speed]
D -->|"0.00 or blank"| C
D -->|"'unable to connect'"| F[give up immediately]
D -->|other error| C2[sleep 2s, retry]
C2 --> C
E --> G[sleep 1s]
F --> G
C -->|3 attempts done| G
G --> H["recv test: same but with -R"]
H --> I["latency: ping -c1 -> 'xx.x ms' or '--'"]
I --> J[print row; 'busy' where speed missing/0.00]
J --> A
What one successful attempt's raw output looks like (iperf3 client, trimmed):
Connecting to host lon.speedtest.clouvider.net, port 5201
[ 5] 0.00-10.00 sec 112 MBytes 941 Mbits/sec sender
...
[SUM] 0.00-10.04 sec 1.10 GBytes 940 Mbits/sec receiver
YABS greps for receiver (present only when the test completed), then takes field $6 (940) and $7 (Mbits/sec) from the [SUM] ... receiver line — the combined throughput of all 8 streams measured at the receiving end. error, unable to connect, a blank result, or 0.00 each trigger the retry/give-up logic.
Key details from the code:
-
3 attempts per direction (not 5 — the README is stale on this; the code says 3, and has since Jun 2022. History: the original Oct 2019 script tried up to 10 times, reduced to 5 in Feb 2021, then to 3 in Jun 2022 — enough to ride out a transient "server busy" without stalling the run). Each attempt picks a fresh random port from the server's range via
shuf -i <range> -n 1. - Duration is iperf3's default: 10 seconds per run.
-
-P 8— 8 parallel TCP streams, so the number reflects aggregate multi-connection throughput, not a single flow's ceiling. -
15 s
timeoutwraps each attempt; an unresponsive server doesn't stall the run. -
Success criteria: output must contain
receiverand noterror; the parsed speed must be non-blank and not0.00.unable to connectaborts that direction's retries immediately; other errors retry after a 2 s sleep. -
busyis printed when a direction ends with a blank/0.00result after its attempts — meaning the public server had no free test slot or refused. It says nothing about your own capacity. (Public iperf3 daemons accept one test at a time; when someone else is mid-test, yours gets refused.) -
Success requires the literal word
receiverand noerrorin the output, and the[SUM] ... receiverline's numeric field is what gets reported — exactly as printed, unit included (Mbits/sec/Gbits/sec). The same strings land in the JSONsend/recvfields. - The whole IPv4 table runs first if IPv4 connectivity was detected, then a second full table for IPv6 — servers advertising only one mode simply don't appear in the other table.
On an unthrottled gigabit port to a nearby server, expect numbers in the high hundreds of Mbit/s (~940 Mbit/s is the practical TCP ceiling of 1 Gbps after overhead); a 10G port and 10G+ far end can show ~9.4 Gbit/s. Anything lower to a nearby location invites a closer look; anything lower to a distant location is usually just physics (latency × congestion), not a fault. Detailed interpretation — including asymmetric send/recv — is in Interpreting Results.
YABS runs the entire server list once per IP version, in order — iperf3 Network Speed Tests (IPv4) then (IPv6) — but only for versions the startup connectivity probe found. The modes field on each server entry (IPv4, IPv6, IPv4|IPv6) filters it out of a pass where it can't serve you, so the two tables can have different row counts.
The ping column uses the same -4/-6 flag as the test it follows — before a Jul 2026 fix (#126) IPv6 servers could get pinged over v4, producing a misleading latency. One ping -c1 is all it is: directionally useful, statistically thin. For real latency analysis use mtr/ping separately.
Each tested server receives ~10 s of line-rate traffic in each direction, per IP version:
- Per direction:
10 s × link rate. On a 1 Gbps port:10 s × 1 Gbit/s = 10 Gbit ≈ 1.25 GB. - Per server per stack (send + recv): ≈
2 × 1.25 GB = 2.5 GBon a 1 Gbps port. - Full run:
2.5 GB × 7 servers × 2 stacks (IPv4 + IPv6) ≈ 35 GB— the practical ceiling on a 1 Gbps port. - On faster ports it scales linearly: a 10 Gbps port can theoretically pull ~125 GB in the same pattern (
10 s × 10 Gbit/s = 12.5 GB/direction), though far-end capacity usually intervenes. - Retries add more traffic; slow or "busy" servers reduce it.
Observed real-world usage matches the model: ~20–24 GB per full run was reported on LowEndSpirit in Feb 2020. On metered plans use -r (cuts locations 7→3, roughly 57% less iperf traffic — same arithmetic, 2.5 GB × 3 servers × 2 stacks ≈ 15 GB ceiling on 1G) or -i (no network tests).
Each server costs ~20 s when healthy (10 s send + 1 s pause + 10 s recv) and up to ~90 s when every attempt times out (3 × 15 s per direction plus sleeps). A full network section is typically 2–5 minutes; a pathological one (all servers unreachable, full timeout paths on both stacks) can stretch past 15.
Separately from iperf itself, each row ends with a ping -4|-6 -c1 <host> — one ICMP echo, parsed for time=. It's there because raw throughput without latency is half the story: 300 Mbit/s with 8 ms means a nearby healthy path; the same number with 250 ms is a saturated long-haul one. -- when ping is absent or the echo fails — the throughput numbers are unaffected either way.
Outbound TCP to the iperf port ranges (5200–5210, 9204–9240) must be allowed. A host firewall (e.g. ufw with a restrictive outbound policy) can make every location fail — reported as early as Oct 2019. Corporate egress filters and NAT VPSes behind shared IPv4 produce the same symptom. If all rows say busy, test one server manually:
iperf3 -c lon.speedtest.clouvider.net -p 5201 -P 8Exactly what the code does with each attempt outcome:
| Attempt result | Action |
|---|---|
output has receiver, speed parses to non-0.00
|
accept, move on |
output has receiver but speed blank or 0.00
|
retry (up to 3 total) |
output contains unable to connect
|
give up immediately — server refusing, no point retrying |
| other error output | sleep 2 s, retry (up to 3 total) |
| no output at all / timeout kill | counts as error path → retry after 2 s |
Between the send and receive phases there's a fixed sleep 1 — a courtesy pause so the server can free its test slot.
The list is donated infrastructure — providers volunteer bandwidth and slots. The maintainer's standing policy: changes should be 1:1 swaps that improve geographic/provider diversity, port-range depth, or link capacity; the list should not just grow (bandwidth costs money).
Eras of the list:
-
2019–2020: Bouygues (
bouygues.iperf.fr, the very first server in Oct 2019 — the day-one script tested only that one server), then Online.net, Severius, Worldstream, wilhelm.tel, Biznet, Hostkey, Vultr, Velocity, Airstream, and Hurricane Electric as coverage broadened. When-rwas introduced (Feb 2020), its reduced trio was Online.net France, WorldStream NL, and Hurricane Electric US — all long since swapped out. - 2021–2023: the Clouvider / Scaleway / Hybula / Uztelecom era — Clouvider London & NYC (Feb 2021), WebHorizon Singapore (Oct 2021), Hybula 40G NL (2022), Scaleway Paris, Uztelecom Tashkent for Central Asia coverage, plus steady churn as servers came and went and ping/latency column (#43) and standardised location naming (#48) landed in Nov 2022.
- Current: Clouvider (London, LA), Eranium Amsterdam, Uztelecom Tashkent, Leaseweb (Singapore, NYC), Edgoo Sao Paulo.
Parked candidate swaps are tracked in PRs #125 (Alwyzon Vienna, 100G) and #115 (Advin Kansas City, 40G), held as inputs to a future assessment rather than merged.
If you operate a public iperf3 server and want it in the list, the bar is essentially: it replaces a weaker existing entry on geography/provider diversity, port-range depth, or link capacity — not "one more server".
-
UDP, jitter, packet loss — iperf3 can measure all three (
-u), YABS doesn't. TCP goodput only. -
Single-stream throughput — everything is
-P 8. A single-flow ceiling needs a manualiperf3run. - Latency under load — the ping sample runs between tests, not during them; bufferbloat is invisible here.
- Route quality to your clients — results reflect the path to these specific donated endpoints in these cities, not your users' paths.
- Sustained transfer — 10 s bursts can sit inside ISP "boost" windows that a 10-minute transfer would blow through.
- Interpreting Results — send/recv asymmetry, geography, and "busy"
-
Command-Line Flags —
-r,-i,-psyntax -
Troubleshooting and FAQ — all-
busytables, firewall issues, bandwidth costs - System Information — how IPv4/IPv6 connectivity is detected
- History — why iperf3 (8 streams) over speedtest.net
Repository · Issues · Binary releases · Usage stats · curl -sL yabs.sh | bash · Licensed under the WTFPL
Using YABS
- Getting Started
- Command-Line Flags
- Understanding the Output
- Interpreting Results
- Benchmarks Are Snapshots
- JSON Output and Integrations
The tests
Internals
Help and project
Background