A self-hosted, open-source internet speed test that keeps up with modern networks.
One ~10 MB static binary. Multi-gigabit honest measurements. Beautiful on every screen.
Graphite Meter is built to measure the link, not the tool: in Chrome it sustains up to 60 Gbit/s down and 18 Gbit/s up, and the native terminal client pushes hundreds of Gbit/s.*
- Bufferbloat, measured properly — idle latency is profiled first, then the same ping loop keeps running during download and upload, so you see exactly how your latency degrades under load (loaded latency), with detailed per-stage latency plots: average, jitter, and range for idle vs. loaded, down and up.
- Honest numbers — upload throughput is what the server actually received (byte counts over an elapsed clock streamed back live), never what the browser thinks it sent. All payloads are incompressible; stalls and reconnects can't inflate a result.
- Every stage is optional — latency, download, upload, and an optional bidirectional stage that saturates both directions at once.
- Adaptive early stopping — a stage ends as soon as its result is statistically stable, so a steady fiber line finishes in seconds while a jittery link gets the full window.
- Honest wire-rate estimates — opt-in forward-direction Ethernet accounting for IP, TCP/UDP, TLS/QUIC, HTTP framing, and explicit tunnels. The browser-facing protocol is detected automatically; unobservable packet details are shown as a range instead of guessed from runtime variance, ping timeouts, or ramp-up.
- Highly configurable — durations, parallel streams, ping cadence, units (bits/bytes, SI/IEC), manual or auto gauge scaling — from the UI, no config files.
- Featherweight server — a single static Go binary (~10 MB, client embedded,
FROM scratchimage, no shell, no libc) with low CPU and memory draw even while sinking gigabits. - Native TUI client — a full interactive terminal client
(
graphite-meter-client) that speaks the same wire protocol and runs the same stages against any Graphite Meter server. - Modern, responsive UI — dark and light themes, equally at home on a phone and a desktop.
- Independent throughput and latency transports — Fetch throughput can use HTTP/1.1 clear, HTTP/1.1 TLS, HTTP/2, or HTTP/3; latency independently uses WebSocket over dedicated HTTP/1.1 clear or TLS. Both targets are verified and frozen for the run.
- Free and open source — AGPL-3.0.
Run the published image (multi-arch: amd64 + arm64):
docker run -d --name graphite-meter -p 7246:7246 ghcr.io/zr-jb/graphite-meter:latestOpen http://localhost:7246 — that's it for the default clear HTTP/1.1 deployment. The TLS
overlay adds the HTTPS UI at https://localhost:7247 alongside native H2 and H3 measurement
listeners. Open only 7246 or 7247 in a browser; 7248 and 7249 are strict test targets.
For local browser testing, including browser-specific handling of private-root HTTP/3
certificates, see Local TLS and HTTP/3 certificates.
Use the command that matches the environment:
| Purpose | Command |
|---|---|
| Local development run | just dev |
| Local production-profile run | just prod |
| Local run with H1-TLS, H2, and H3 | Local TLS command |
| Published clear-H1 deployment | docker run -d --name graphite-meter -p 7246:7246 ghcr.io/zr-jb/graphite-meter:latest |
| Published H1-TLS, H2, and H3 deployment | GM_PUBLIC_HOST=meter.example.com docker compose -f container/docker-compose.yml -f container/docker-compose.tls.yml up -d |
just prod builds the production browser profile but still runs the server from the source tree;
it is not the published deployment workflow. The TLS Compose command requires a certificate under
/etc/letsencrypt/live/${GM_CERT_NAME:-graphite-meter} in the Compose letsencrypt volume.
Use container/docker-compose.yml, or:
services:
graphite-meter:
image: ghcr.io/zr-jb/graphite-meter:latest
ports:
- "7246:7246"
restart: unless-stoppedEnable all protocol targets with container/docker-compose.tls.yml. Set GM_PUBLIC_HOST and
mount/provision a certificate in the overlay's complete /etc/letsencrypt volume (the complete
tree is required because live/ contains symlinks into archive/). H3 needs both TCP and UDP
7249 reachable; the dedicated H1-TLS target uses TCP 7247.
A ready-made unit that pulls the image and runs it as a systemd service lives at
container/quadlet/graphite-meter.container — see
container/quadlet/README.md.
Defaults need no configuration. The complete listener, TLS, public-origin, admission-limit, and deployment reference lives in docs/CONFIGURATION.md; reverse proxies have a separate deployment guide.
just goclient-build # -> go/graphite-meter-client
./go/graphite-meter-client -url https://your-server:7247 \
-throughput-origin https://your-server:7247 \
-latency-origin https://your-server:7247An interactive TUI with the same stages (latency, download, upload, bidirectional, loaded
latency), server presets, and live telemetry — ideal for headless boxes and for pushing rates a
browser can't. Throughput and latency targets are independently selected and verified; upload
progress is part of the selected throughput path. See -throughput-origin, -throughput-protocol, and -latency-origin.
- docs/DEVELOPMENT.md — toolchain,
justrecipes, build flags, building the image from source. - docs/ARCHITECTURE.md — how the server, browser client, TUI, and
cross-language
api/contract fit together, plus the project roadmap.
The short version: git clone, then just dev, then open http://localhost:7246.
* Peak figures measured with Chrome (browser client) and the native TUI client on Linux against a localhost server — a hardware-unconstrained best case that shows the tool won't be your bottleneck. Across a real network, expect results limited by your NIC, path, and browser.