The Go backend of LibreSpeed includes a web interface and provides HTTP endpoints for measuring download speed, upload speed, and latency. This article runs the backend on Debian as a non-root user with systemd, while Caddy on the same host provides a public HTTPS entry point.
Installing the Prebuilt Binary on Debian
This guide uses LibreSpeed speedtest-go v1.1.6; the commands apply to Debian amd64 and arm64. The speedtest-backend in the release archive already includes the default frontend assets. For other architectures, see the official release page.
sudo apt update
sudo apt install curl ca-certificates
(
set -eu
version=1.1.6
arch=$(dpkg --print-architecture)
case "$arch" in
amd64|arm64) ;;
*) printf 'Select a release archive for %s\n' "$arch" >&2; exit 1 ;;
esac
archive="speedtest-go_${version}_linux_${arch}.tar.gz"
release="https://github.com/librespeed/speedtest-go/releases/download/v${version}"
workdir=$(mktemp -d)
trap 'rm -rf -- "$workdir"' EXIT
cd "$workdir"
curl -fsSL -o "$archive" "$release/$archive"
curl -fsSL -o checksums.txt "$release/checksums.txt"
awk -v file="$archive" '$2 == file || $2 == "*" file' checksums.txt > archive.sha256
sha256sum -c archive.sha256
tar -xzf "$archive"
sudo install -d -m 0755 /usr/local/bin
sudo install -m 0755 speedtest-backend /usr/local/bin/speedtest-backend
)
Configuring the systemd Service
The backend listens on the local loopback address, while Caddy terminates TLS. database_type = "none" disables recording speed test results and statistics. See the upstream TOML example and systemd unit.
sudo install -d -m 0755 /etc/speedtest-go
sudo tee /etc/speedtest-go/settings.toml >/dev/null <<'EOF'
bind_address = "127.0.0.1"
listen_port = 8989
proxyprotocol_port = 0
enable_tls = false
enable_http2 = false
database_type = "none"
EOF
sudo tee /etc/systemd/system/speedtest-go.service >/dev/null <<'EOF'
[Unit]
Description=LibreSpeed Go backend
After=network.target
[Service]
ExecStart=/usr/local/bin/speedtest-backend -c /etc/speedtest-go/settings.toml
DynamicUser=yes
Restart=on-failure
RestartSec=3s
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now speedtest-go.service
systemctl is-active speedtest-go.service
ss -ltn 'sport = :8989'
curl -fsS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8989/
You should see the service status as active, the listening address as 127.0.0.1:8989, and the request returning HTTP 200. If startup fails, run sudo journalctl -u speedtest-go.service -n 50 --no-pager to view the logs.
Serving Public HTTPS with Caddy
Replace speedtest.example.com with your public domain and point its DNS record to Caddy. When serving directly to the public with the default ACME configuration, ensure TCP ports 80 and 443 are reachable. If you want to measure the path from the client directly to the origin server, set the speed test domain to DNS only. With CDN proxying enabled, the test includes both the CDN edge and the origin-fetch path. See Caddy Automatic HTTPS and Cloudflare proxy status.
speedtest.example.com {
reverse_proxy 127.0.0.1:8989
}
If Caddy and the backend run on separate hosts, bind the backend to a private network or WireGuard address reachable by Caddy, change the upstream address of reverse_proxy, and allow only the Caddy host to access 8989/tcp in the firewall. If the public speed test site is only for your own use, apply the existing access controls to the entire site, including the upload and download endpoints.
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy
On the network you want to test, open https://speedtest.example.com/index-classic.html and run a full speed test.
Testing Speed with the CLI
On Debian 13, run sudo apt install librespeed-cli. On macOS with Homebrew installed, run brew tap librespeed/tap and brew install librespeed-cli in that order. The official CLI release page also provides prebuilt packages; choose darwin_amd64 for Intel Macs and darwin_arm64 for Apple silicon machines. See the Debian package and LibreSpeed Homebrew tap.
cat > librespeed-servers.json <<'EOF'
[
{
"id": 1,
"name": "Self-hosted LibreSpeed",
"server": "https://speedtest.example.com/",
"dlURL": "garbage",
"ulURL": "empty",
"pingURL": "empty",
"getIpURL": "getIP"
}
]
EOF
librespeed-cli --local-json ./librespeed-servers.json --server 1 --no-icmp
--no-icmp makes the latency test use the same public HTTP entry point as well. If the domain supports both IPv4 and IPv6, add -4 and -6 separately for comparison. See CLI options and custom server format.
Comparing SSH Tunnels, Direct-to-Origin Connections, and the Public Domain
With SSH local port forwarding, you can access the backend from the client, and the measured throughput is that of the SSH tunnel. Keep the following command running in another terminal.
ssh -N -o ExitOnForwardFailure=yes \
-L 18989:127.0.0.1:8989 user@debian.example.com
From the same client, issue the same download request over each path. Replace 203.0.113.10 with Caddy’s public IPv4 address. --resolve specifies the address to connect to while retaining the domain name used for TLS and HTTP routing. When the Go backend receives ckSize=16, it returns 16 MiB of data. See the backend implementation and curl manual.
host=speedtest.example.com
origin=203.0.113.10
path='/garbage?ckSize=16'
probe_download() {
curl -q --noproxy '*' --http1.1 -fsS \
--connect-timeout 5 --max-time 45 -o /dev/null \
-w 'ip=%{remote_ip} http=%{http_version} status=%{http_code} bytes=%{size_download} avg=%{speed_download} B/s\n' \
"$@"
}
probe_download "http://127.0.0.1:18989$path"
probe_download --resolve "$host:443:$origin" "https://$host$path"
probe_download "https://$host$path"
Only compare complete responses whose status code is 200 and that downloaded exactly 16777216 bytes. Multiply B/s by 8 and divide by 1,000,000 to obtain Mbps. If speeds are slow only when the public domain resolves normally, inspect the IP address in the output to determine whether traffic is going through a CDN or using a different IP stack. --noproxy '*' bypasses any explicit proxy configured in curl, but a system-level TUN may still affect routing.
In my measurements, the SSH tunnel downloaded at approximately 151 Mbps, while the direct HTTP/1.1 connection to Caddy downloaded at approximately 162 Mbps. The browser-based speed test improved after switching Chrome from HTTP/3 to HTTP/2. I later found that the earlier belief that SSH was consistently faster was due to an incorrect comparison. HTTP/3 was observed to be slower during testing, but the exact cause has not yet been identified.
When retesting in the browser, open the same /index-classic.html on the same network and inspect the download request’s Protocol and Remote Address in the DevTools Network panel. If the protocol is h3, temporarily disable QUIC in the browser, restart it, confirm that the request uses h2, and run the speed test again. Restore the browser settings afterward. Keep the same speed test endpoint, client network, and test direction for each comparison.