A self-hosted, speedtest.net-style web page that measures ping, jitter, download and upload speed between a browser and a Flask server on your local network.
| File | Purpose |
|---|---|
main.py |
Flask app (API + static UI) |
gunicorn.py |
Production wrapper (binds 0.0.0.0) |
static/ |
index.html, style.css, app.js |
requirements.txt |
Dependencies |
.env.example |
Sample configuration |
pip install -r requirements.txt
python gunicorn.py # production
python main.py # developmentOpen http://<server-lan-ip>:8080 from any device on the network. The URLs are printed on startup.
Always start with
python gunicorn.py. Do not rungunicorn main:appin this folder: the file name shadows the realgunicornpackage (the wrapper handles this). On Windows it falls back towaitress.
Copy .env.example to .env to configure. Real environment variables override the file.
| Variable | Default | Description |
|---|---|---|
HOST |
0.0.0.0 |
Bind address |
PORT |
8080 |
Port |
WORKERS |
2 |
Gunicorn workers |
THREADS |
32 |
Threads per worker |
TIMEOUT |
300 |
Worker timeout (s) |
SPEEDTEST_ISP |
Local Network |
Name shown next to the client IP |
SPEEDTEST_SERVER_NAME |
hostname | Server name shown in the UI |
SPEEDTEST_LOCATION |
LAN |
Server location shown in the UI |
MAX_DOWNLOAD_MB |
1024 |
Max size per download request |
TRUST_PROXY |
unset | Set 1 behind nginx/cloudflared to read the real client IP |
GET /api/info– client IP and server infoGET /api/ping– empty response for latencyGET /api/download?size=<bytes>– streams random dataPOST /api/upload– discards the body, returns byte count
- Multi mode uses parallel streams (default 6, configurable in Settings); Single uses one.
- The first 1.5 s of each phase is excluded to skip TCP slow start.
- For accurate results on gigabit or faster links, use a wired client and keep
WORKERS × THREADSabove the number of streams.