Skip to content
Go back

LibreSpeed: Hosting Your Own Speed Test

By SumGuy 10 min read
LibreSpeed: Hosting Your Own Speed Test
Contents

Your ISP’s Best Day, Bottled and Sold Back to You

Speedtest.net measures the best-case scenario between your ISP’s peering point and a CDN server that’s been carefully colocated to make everyone look good. It’s speed-testing theatre. You get a big, satisfying number, you screenshot it, you feel validated, and then you wonder why your NAS transfers crawl at 300 Mbps on a “gigabit” network.

Honestly, Ookla’s interface has more ads than a 2009 Flash game site. You click “Go,” wait through a banner ad, watch a spinner, and get a number that means roughly nothing for your actual use case.

LibreSpeed fixes this. It’s an open-source, self-hosted speed test server that measures what you actually care about: LAN throughput, VPN tunnel bandwidth, jitter, packet loss, and real ISP performance from your actual network path. No ads. No phoning home. Just numbers.

What LibreSpeed Actually Measures (and Why That’s Useful)

LibreSpeed uses HTTP to simulate upload and download. That means it respects your actual application-layer stack (TCP, your kernel’s buffer settings, your NIC driver) rather than ICMP or raw UDP blasting past all that.

Where it shines:

Deploy It: Docker Compose

LibreSpeed ships as adolfintel/speedtest. The image is small, the config is environment variables, and the whole thing is up in under a minute.

docker-compose.yml
services:
speedtest:
image: adolfintel/speedtest:latest
container_name: librespeed
restart: unless-stopped
environment:
MODE: standalone
TELEMETRY: "true"
ENABLE_ID_OBFUSCATION: "true"
PASSWORD: "changeme" # admin login for results viewer
IPINFO_APIKEY: "" # optional: ipinfo.io key for geolocation
TIME_DL_S: "15" # download test duration (seconds)
TIME_UL_S: "15" # upload test duration (seconds)
MAX_THREADS: "6" # parallel streams for 10G testing
DISTANCE: "km"
ports:
- "8080:80"
volumes:
- librespeed_db:/database
volumes:
librespeed_db:
Terminal window
docker compose up -d

Hit http://<your-host>:8080 and you’ve got a clean speed test UI with zero ads.

Environment Variables That Matter

VariableDefaultWhat it does
MODEstandalonestandalone serves everything from one container
TELEMETRYfalseEnable SQLite result logging
PASSWORD(none)Admin password for /results/stats.php
TIME_DL_S15Download test duration in seconds
TIME_UL_S15Upload test duration
MAX_THREADS6Parallel HTTP streams, increase for 10G
IPINFO_APIKEY(none)ipinfo.io API key for client geolocation

For a 10G home lab, bump MAX_THREADS to 8 or 10. A single HTTP stream will saturate at around 3 to 4 Gbps on most commodity hardware; you need multiple parallel streams to push a 10G link. Set TIME_DL_S and TIME_UL_S to at least 20 to let the test ramp up and stabilize.

Testing Your LAN

Deploy LibreSpeed on the same machine as your NAS, or on a dedicated server on the same switch. Then open the URL from any device on the LAN.

This tells you:

A 2.5GbE NAS link should deliver ~2.2 to 2.35 Gbps in practice. If you’re seeing 940 Mbps, check ethtool eth0 on the NAS and the switch port config. LibreSpeed just told you something was wrong. Without LibreSpeed, you’d still be wondering why Plex transcodes instead of direct plays.

Testing Across a VPN Tunnel

This is the use case that actually gets me excited. Put LibreSpeed on a node that’s accessible via your VPN: a Tailscale-connected server, a WireGuard peer, a Headscale node.

For Tailscale specifically, deploy LibreSpeed on any node in your tailnet, then test from another. The number you get is your actual tunnel throughput, accounting for WireGuard encryption overhead, MTU, and CPU speed on both ends.

Terminal window
# On the Tailscale node running LibreSpeed
docker compose up -d
# From another Tailscale device, open:
# http://<tailscale-ip>:8080

You’ll find that:

This is the fastest way to diagnose “why is my VPN slow” without guessing.

The CLI Version: librespeed-cli

For cron jobs, scripting, and Grafana ingestion, there’s librespeed-cli, a Go binary that speaks the LibreSpeed protocol.

Terminal window
# Install (Linux amd64)
curl -LO https://github.com/librespeed/speedtest-cli/releases/latest/download/librespeed-cli_linux_amd64.tar.gz
tar -xzf librespeed-cli_linux_amd64.tar.gz
sudo mv librespeed-cli /usr/local/bin/
# Run against your self-hosted server (server list passed as JSON via stdin)
echo '[{"id":1,"name":"home","server":"http://192.168.1.50:8080/backend/","dlURL":"garbage.php","ulURL":"empty.php","pingURL":"empty.php","getIpURL":"getIP.php"}]' \
| librespeed-cli --local-json - --server 1 --simple
# CSV output for logging
echo '[{"id":1,"name":"home","server":"http://192.168.1.50:8080/backend/","dlURL":"garbage.php","ulURL":"empty.php","pingURL":"empty.php","getIpURL":"getIP.php"}]' \
| librespeed-cli --local-json - --server 1 --csv

Heads up: --server-json/--local-json expect a server list JSON (with dlURL, ulURL, pingURL, getIpURL fields relative to server), not a direct backend URL. Pass it inline via stdin with --local-json -, or save it to a file and point --local-json at that. The --simple flag gives you clean one-line output: download, upload, ping, jitter.

For cron-based logging:

/etc/cron.d/speedtest
*/30 * * * * root /usr/local/bin/librespeed-cli \
--local-json /etc/librespeed/servers.json --server 1 \
--csv >> /var/log/speedtest.csv 2>&1

(Drop your server-list JSON at /etc/librespeed/servers.json so cron has a file to read, stdin piping is awkward in a crontab line.)

Now you have timestamped CSV data every 30 minutes. Ingest that into Prometheus via a CSV exporter, or import directly into Grafana as a CSV data source. You’ll have a “my ISP is throttling me at peak hours” dashboard before the week is out.

Telemetry: Actually Useful for Once

Enable TELEMETRY=true and the container logs every test result into a SQLite database at /database/telemetry.db. The admin interface lives at:

http://<your-host>:8080/results/stats.php

Log in with the PASSWORD you set. You’ll see a table of every test run: timestamp, IP, download, upload, ping, jitter, ISP name (if you set an IPINFO_APIKEY).

For Grafana integration, you can either:

  1. Mount the SQLite file and use Grafana’s SQLite plugin to query it directly
  2. Export CSV from the admin UI and import as a data source

The SQLite schema is simple:

SELECT timestamp, ip, ispinfo, dl, ul, ping, jitter
FROM speedtest_telemetry
ORDER BY timestamp DESC
LIMIT 100;

dl and ul are in Mbps. Plug that into a Grafana time series and you’ve got a real ISP monitoring dashboard that costs you nothing and leaks nothing to Ookla.

Reverse Proxy: HTTPS with Caddy or Traefik

Running this on your internal network? Fine without HTTPS. Exposing it to the internet or over Tailscale to multiple users? Add HTTPS.

Caddy

Caddyfile
speedtest.yourdomain.com {
reverse_proxy librespeed:80
}
docker-compose.yml (with Caddy)
services:
speedtest:
image: adolfintel/speedtest:latest
container_name: librespeed
restart: unless-stopped
environment:
MODE: standalone
TELEMETRY: "true"
PASSWORD: "changeme"
volumes:
- librespeed_db:/database
networks:
- caddy_net
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
networks:
- caddy_net
volumes:
librespeed_db:
caddy_data:
networks:
caddy_net:

Traefik Labels

If you’re already running Traefik:

docker-compose.yml (Traefik labels)
services:
speedtest:
image: adolfintel/speedtest:latest
container_name: librespeed
restart: unless-stopped
environment:
MODE: standalone
TELEMETRY: "true"
PASSWORD: "changeme"
labels:
- "traefik.enable=true"
- "traefik.http.routers.librespeed.rule=Host(`speedtest.yourdomain.com`)"
- "traefik.http.routers.librespeed.entrypoints=websecure"
- "traefik.http.routers.librespeed.tls.certresolver=letsencrypt"
- "traefik.http.services.librespeed.loadbalancer.server.port=80"
volumes:
- librespeed_db:/database
volumes:
librespeed_db:

LibreSpeed vs iperf3: Pick Your Tool

These aren’t competitors. They measure different things.

iperf3 is raw TCP/UDP throughput. It bypasses the HTTP stack, so it tells you what your hardware can theoretically push. It’s great for:

But iperf3 doesn’t measure jitter, packet loss in a web-realistic context, or anything you’d recognize as a user-facing metric.

LibreSpeed uses HTTP. It gives you:

Use iperf3 when you’re commissioning new hardware. Use LibreSpeed day-to-day. They live in the same toolbox.

Terminal window
# iperf3: raw TCP throughput (server on one machine)
iperf3 -s
# iperf3: client test (from another machine)
iperf3 -c 192.168.1.50 -t 30
# LibreSpeed CLI: user-facing metrics
librespeed-cli --local-json /etc/librespeed/servers.json --server 1

Calibrating for 10G

Default settings are tuned for typical 100M to 1G home connections. For 10G testing, you need to tune:

environment:
MAX_THREADS: "10" # More parallel HTTP streams
TIME_DL_S: "20" # Longer to let TCP ramp up
TIME_UL_S: "20"
CHUNK_SIZE: "100" # Larger chunks (MB) per request

You also need the host running LibreSpeed to have enough CPU for multi-threaded HTTP. On a 10G test, you’re pushing ~1.25 GB/s through an HTTP server. A Raspberry Pi 4 will not do this. A modest x86 box (quad-core, 8+ GB RAM) handles it fine.

Check that your NIC isn’t bottlenecked by interrupt affinity. If you’re hitting a ceiling at ~6 Gbps and your hardware should handle more, irqbalance or manual CPU pinning can help.

The Browser UX (It’s Actually Good)

Open LibreSpeed in a browser and you get a clean, single-page interface: big dial, download/upload/ping/jitter metrics, no banner ads, no cookie consent wall, no “upgrade to Pro” nag. It looks like what speedtest.net used to look like in 2015 before it became a landing page for enterprise SaaS.

The interface is responsive, works on mobile, and loads fast. It’s not exciting, which is exactly what a diagnostic tool should be.

Should You Bother?

If you’re running a home lab with a NAS, multiple VLANs, a VPN into your network, or you’re paying for gigabit and wondering if you’re actually getting it: yes, absolutely bother.

Setup takes about five minutes. The ongoing maintenance is zero. You get:

The one time you use it to diagnose a misconfigured switch port or confirm your VPN isn’t the bottleneck you thought it was, it pays for the five minutes you spent deploying it. Probably ten times over.

Speedtest.net is fine for “is the internet working.” LibreSpeed is for people who want to know why it’s working, or why it isn’t.

Run it. Point it at your infra. See what’s actually happening on your network.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
crun vs runc vs youki: OCI Runtimes Compared

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts