MySpeed is a speed test analysis software that records your internet speed over a fully configurable retention period.
- π MySpeed generates clear statistics on speed, ping, and more
- β° MySpeed automates speed tests and allows you to set the time between tests using Cron expressions
- ποΈ Add multiple servers directly to a MySpeed instance
- π©Ί Configure health checks to notify you via email, Signal, WhatsApp, or Telegram in case of errors or downtime
- π Test results can be stored for any retention period you configure - from a few days to forever
- π₯ Support for Prometheus and Grafana
- π³οΈ Choose between Ookla, LibreSpeed, Cloudflare and your own iperf3 server
- π― Measure against several targets in one round - the internet and your own LAN, side by side
- π Get alerted when a target falls below what it usually delivers, measured against its own rolling median
The native builds bind the speed test to your real network interface. Docker only does that with host networking on a Linux host β anywhere else it measures the path through Docker's network stack rather than your line.
Download a Linux binary from the releases page:
MySpeed-linux-x64β default Bun target (needs AVX2)MySpeed-linux-x64-baselineβ older x86_64 CPUs without AVX2 (SSE4.2 / Nehalem+)MySpeed-linux-arm64β aarch64
If the default binary exits immediately with Illegal instruction / SIGILL, use the
baseline build. The install script picks baseline automatically when /proc/cpuinfo
has no avx2 flag.
curl -sSL -o /tmp/myspeed-install.sh \
https://raw.githubusercontent.com/i7Gamer/MySpeed/development/scripts/install.sh
sudo bash /tmp/myspeed-install.shBuilding a Linux binary yourself (bun run build:binary:baseline) has to happen on
Linux β a container is fine. Cross-compiling from macOS or Windows embeds the host's
native addons (e.g. @resvg/resvg-js), producing a binary that starts and then fails
at runtime.
Download from the releases page:
MySpeed-windows-x64.exeβ default Bun target (needs AVX2)MySpeed-windows-x64-baseline.exeβ older x86_64 CPUs without AVX2 (SSE4.2 / Nehalem+)MySpeed-installer.msiandMySpeed-installer-baseline.msiβ the same two as an installer, which registers MySpeed as a Windows service
Nothing picks the right one for you here, so go by the symptom: the exe exits
immediately with Illegal instruction, and the MSI installs cleanly but leaves a
service that never starts. Either one means the baseline build. To check before
downloading, PowerShell 7 answers it with
[System.Runtime.Intrinsics.X86.Avx2]::IsSupported.
The two installers are one product, so running the other one switches the build and keeps your database.
The exe keeps its data in a data folder next to the directory you start it from, so
run it from the folder you want that data to live in. The MSI installs to
C:\Program Files\MySpeed and keeps its data in C:\ProgramData\MySpeed instead.
docker run -d -p 5216:5216 -v myspeed:/myspeed/data --restart=unless-stopped --name MySpeed i7gamer/myspeedOr with Compose:
services:
myspeed:
image: i7gamer/myspeed
container_name: MySpeed
restart: unless-stopped
ports:
- "5216:5216"
volumes:
- myspeed:/myspeed/data
volumes:
myspeed:On a Linux host, add --network host (Compose: network_mode: host) and drop the
port mapping:
docker run -d --network host -v myspeed:/myspeed/data --restart=unless-stopped --name MySpeed i7gamer/myspeedMySpeed binds the speed test to a specific network interface. On the default bridge
network the only interface a container can see is its own eth0, so every test is
forced through Docker's NAT and measures that path rather than your line - the faster
your connection, the more it costs you. Host networking lets MySpeed bind to the real
NIC, and the interface picker in the settings starts listing your actual interfaces.
MySpeed still listens on port 5216, now directly on the host. This has no effect on Docker Desktop for Windows and macOS, where the traffic goes through a VM either way.
Build and run it yourself β the binaries above are this, already built
Requires bun.
git clone https://github.com/i7Gamer/MySpeed.git
cd MySpeed
bun install
bun run build
bun run server/index.jsMySpeed then listens on http://localhost:5216.
MySpeed is safe to run on a trusted LAN out of the box. Putting it on a public address takes a few deliberate steps.
Read the guide β first run, password recovery, reverse proxy, environment variables, and what is protected
A fresh instance has no password. Requests from other machines are refused until one is set, so the first thing MySpeed prints on startup is a one-time setup token:
Setup token: 5f3c1e...
Enter it when the interface asks for a password, then set a real password from
the dropdown menu. A new token is issued on every restart and is never written to
disk. On a network you fully trust you can skip this with ALLOW_NO_PASSWORD=true
β on a public address, don't.
Behind Authelia, Authentik or another forward-auth proxy, sign-in can be
delegated to the proxy entirely: set TRUSTED_AUTH_HEADER and
TRUSTED_AUTH_PROXIES from the table below and the login prompt never appears.
The setup token only applies to an instance that has no password, so it is no
help once one is set β and neither is loopback access nor ALLOW_NO_PASSWORD.
A password that is set but not known is cleared from the command line:
MySpeed --reset-passwordThe Docker image ships the runtime and the server sources rather than a compiled binary, so run the entry point there instead:
docker exec <container> bun server/index.js --reset-passwordRun it from the same directory the server runs in β it resolves
data/storage.db relative to the working directory, and will say so if it finds
no configuration there. In the container that is already the working directory.
The instance is then back to the first-run state above: open on the machine it runs on, asking every other machine for a setup token, which it prints to its log as it turns the next request away. Nothing needs restarting; set a new password from the dropdown menu. Sessions already signed in stay valid until they expire β restart the server to end them.
The command says what happened in words, and exits with a code for when something else is reading:
| Code | Meaning | What to do |
|---|---|---|
0 |
The password was cleared, or there was none to clear. | Set a new one from the interface. |
111 |
The database could not be opened at all. | Check that the data directory exists and is writable by the user the server runs as. |
113 |
The database opened and holds no MySpeed configuration. | Nothing was changed. The data is elsewhere β run the command from the directory the server runs in. |
114 |
The configuration is there and the write did not go through. | The password is unchanged and you are still locked out. The path is right; check that the database is not locked by another process and that the directory is writable. |
This is the supported way to expose MySpeed. The proxy terminates TLS and, ideally, authenticates before MySpeed is reached at all:
server {
listen 443 ssl;
server_name speed.example.com;
ssl_certificate /etc/letsencrypt/live/speed.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/speed.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:5216;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Then tell MySpeed the proxy is there, or every client will look like one address and a single attacker can lock everybody out of the login throttle:
docker run -d -p 127.0.0.1:5216:5216 -e TRUST_PROXY=1 \
-v myspeed:/myspeed/data --restart=unless-stopped --name MySpeed i7gamer/myspeedBinding the published port to 127.0.0.1 keeps the container reachable only
through the proxy.
| Variable | Default | What it does |
|---|---|---|
TRUST_PROXY |
unset | Number of proxies in front (1) or a preset such as loopback. Required behind a reverse proxy so rate limiting sees real client addresses. true is read as 1: Express would otherwise take the address from a header the caller writes. |
BASE_PATH |
unset | Serve the whole application from a subdirectory - /internet_speed - for a proxy that routes on a path prefix without stripping it off. The client works the prefix out for itself, so this is the only setting involved. |
ALLOW_NO_PASSWORD |
false |
Serve an instance that has no password to anyone who can reach it. LAN only. |
TRUSTED_AUTH_HEADER |
unset | Accept a reverse proxy's sign-in instead of asking for the password: requests carrying this header β Remote-User for Authelia and Authentik β are admitted as the operator. Only works together with TRUSTED_AUTH_PROXIES, and the proxy must strip the header from incoming requests, which forward-auth middlewares do by default. |
TRUSTED_AUTH_PROXIES |
unset | The only addresses allowed to assert that header: comma-separated addresses and subnets β 172.18.0.5,10.0.0.0/8. Checked against the connecting socket, never against a forwarded header. Without it, header authentication stays off. |
FRAME_ANCESTORS |
'none' |
CSP origins allowed to embed MySpeed in an iframe, for dashboards like Homepage or Heimdall. |
HTTPS_REDIRECT |
true |
Send network callers to the HTTPS listener when data/certs holds a certificate. Set false if a proxy terminates TLS and TRUST_PROXY is not set. |
ALLOW_LOCAL_NODES |
false |
Permit remote nodes on loopback or link-local addresses. Off by default so a node URL cannot be used to probe the host. |
ALLOWED_NODE_HOSTS |
unset | Restrict remote nodes to these hosts, comma-separated, each with an optional port β 192.168.1.50,myspeed.example.net:5216,[fd00::1]. Unset permits any host outside the blocked ranges. Worth setting on an instance reachable from the internet. |
Built in: the login throttle and per-endpoint rate limits, a 100 KB request body
cap outside the two import endpoints, CSP and anti-framing headers, node URLs
blocked from reaching loopback and cloud metadata addresses, and a config export
that redacts credentials unless you add ?includeSecrets=true.
Still worth knowing: the password is held in the browser's localStorage and sent
on every request, so anyone with access to the browser profile has it. There is a
single shared password rather than per-user accounts. Secrets are stored
unencrypted in data/storage.db β back that file up as carefully as you would a
password manager export.
Distributed under the MIT license. See LICENSE for more information.




