Repository navigation
Home
A LAN speed test that behaves like speed.cloudflare.com — download, upload, idle and loaded latency, jitter, packet loss and quality ratings — but runs entirely inside your own network and never contacts Cloudflare's edge.
Cloudflare open-sourced the measurement engine as
@cloudflare/speedtest; the site at
speed.cloudflare.com is not open source. This project supplies the rest: a
backend that satisfies the engine's endpoint contract, a front end that drives
it, a coturn relay for the packet-loss stage, and the container that serves it
all.

A finished run: bandwidth in both directions with the reported percentile marked, latency idle and under load, jitter, packet loss, and the engine's own suitability ratings.
This wiki is public-safe. No real addresses, hostnames or credentials appear anywhere in it. Placeholders are used throughout — substitute your own.
Not affiliated with, sponsored by, or endorsed by Cloudflare, Inc. Cloudflare is a trademark of Cloudflare, Inc. This project is built on their open-source
@cloudflare/speedtestengine, used under the MIT licence.
| Page | What it covers |
|---|---|
| Quick Start | From nothing to a measured link, with docker compose
|
| Deployment | Running it properly on a host: TLS, the relay, firewall, updates |
| Configuration |
config/speedtest.toml, measurement profiles, environment variables |
| TURN and Packet Loss | coturn setup, credentials, verifying a relay candidate |
| Page | What it covers |
|---|---|
| Reading the Results | What each figure means, the distribution view, and why raw throughput is a separate number |
| History and Metrics | Stored runs, trends, comparing two runs, retention, the Prometheus endpoint |
| Client Identity | Who a run belongs to: reverse DNS, friendly names, trusted proxies, and the addresses that cannot be recovered at all |
| Troubleshooting | Symptoms and their causes, especially the silent ones |
| Page | What it covers |
|---|---|
| Engine Contract |
Start here before touching the backend. The @cloudflare/speedtest request/response contract as verified from source, the config keys that keep traffic local, and the two engine behaviours that shape the whole design |
| Architecture | How the pieces fit: request flow, what runs where, why each choice |
| HTTP API | Every route the backend serves, and what it answers with |
| Development | Local setup, build, run, regenerating these screenshots |
| Testing | The tiers, what each gates, how to run them |
| Release History | What shipped in each version |
-
Backend — Rust/Axum. Serves
/__downand/__upplus the built front end. Download payloads are refcounted slices of one pre-allocated buffer, so there is no per-request allocation in the data path. - Front end — TypeScript/Vite, no framework. Drives the engine, renders live results and quality ratings, dark by default.
- coturn — on the same host, for the packet-loss stage. The engine measures loss by relaying a UDP burst from the browser back to itself.
- Config — one TOML file with named measurement profiles. The engine's own defaults are tuned for internet paths and are actively wrong on a LAN; see Configuration.
-
logAimApiUrldefaults to a Cloudflare endpoint and the engine POSTs every completed result to it. It must benull. Pinned server-side, re-checked client-side, and asserted by tests at two tiers. -
server-timingmust be spelledcfRequestDuration;dur=N. Any other spelling — including the obviousdur=N— is silently ignored, and latency quietly falls back to a fixed estimate. -
loadedRequestMinDurationdefaults to 250 ms, above the duration of almost any LAN transfer. Leave it alone and loaded latency is never measured, which in turn removes every quality rating with no error shown.
New here? Quick Start. Changing the backend? Engine Contract first.
Getting it running
Using it
Working on it