Speedtest Tracker mit Docker Compose: Internetanschluss automatisch messen
Speedtest Tracker misst den Internetanschluss per Zeitplan mit der Ookla-CLI und speichert jede Messung in SQLite. Die Anleitung zeigt compose.yaml, .env, Erstanmeldung, Healthcheck, Backup und Restore, Updates und typische Fehler, getestet mit v1.15.0, und grenzt die Messreihe klar vom amtlichen Nachweis der Bundesnetzagentur ab.

Wenn das Internet im Büro zäh wirkt, beginnt meist dieselbe Diskussion: Liegt es am Anschluss, am WLAN oder an der Tageszeit? Eine einzelne Messung im Browser beantwortet das nicht. Speedtest Tracker misst den Anschluss stattdessen automatisch nach Zeitplan, speichert jedes Ergebnis mit Ping, Download, Upload und Messserver in einer Datenbank und zeigt den Verlauf als Diagramm. So wird aus einem Bauchgefühl eine belastbare Zeitreihe, mit der Sie gegenüber dem Provider argumentieren oder eigene Netzprobleme eingrenzen können.
Diese Anleitung zeigt den vollständigen Betrieb mit Docker Compose: compose.yaml, .env, Erstanmeldung, Zeitplan, Healthcheck, Backup und Restore, Reverse Proxy, Updates und saubere Deinstallation. Die Befehle und Ausgaben stammen aus einer isolierten Testumgebung vom 26.09.2026 mit dem Image lscr.io/linuxserver/speedtest-tracker:v1.15.0-ls172.
Nutzen und Grenzen
Speedtest Tracker ist eine Laravel-Anwendung von Alex Justesen, die im Hintergrund die offizielle Ookla-Speedtest-CLI aufruft und die JSON-Ergebnisse speichert. Das Projekt steht unter MIT-Lizenz, hatte laut GitHub-API am 26.09.2026 5992 Sterne, wurde zuletzt am 16.09.2026 aktualisiert und ist nicht archiviert. Das aktuelle Release ist v1.15.0 vom 21.08.2026.
Wofür sich das Werkzeug eignet:
- Langzeitverlauf der Anschlussleistung, etwa um Einbrüche zu bestimmten Uhrzeiten sichtbar zu machen.
- Gesprächsgrundlage gegenüber dem Provider oder dem eigenen Dienstleister, weil jede Messung Zeitpunkt, Server und Werte enthält.
- Kontrolle nach Änderungen am Router, an der Firewall oder nach einem Tarifwechsel.
- Benachrichtigung, wenn Download, Upload oder Ping unter selbst gesetzte Schwellwerte fallen.
- Auswertung per API, Prometheus oder InfluxDB, falls bereits ein Monitoring vorhanden ist.
Wichtige Abgrenzung: Die Ergebnisse von Speedtest Tracker sind kein amtlicher Nachweis einer Minderleistung. Für den Festnetzanschluss stellt die Bundesnetzagentur mit der Desktop-App der Breitbandmessung ein eigenes, verbindliches Nachweisverfahren bereit. Dort gelten feste Regeln, unter anderem 30 Messungen an drei unterschiedlichen Kalendertagen innerhalb von 14 Tagen mit Mindestabständen. Erst das dabei erzeugte Messprotokoll ist die Grundlage für Minderungs- oder Sonderkündigungsrechte gegenüber dem Anbieter. Speedtest Tracker ergänzt dieses Verfahren sinnvoll als Frühwarnsystem, ersetzt es aber nicht.
Weitere Grenzen, die Sie kennen sollten:
- Gemessen wird vom Docker-Host aus. Hängt der Host per WLAN oder an einem langsamen Switch-Port, misst das Werkzeug diese Engstelle mit.
- Jede Messung verbraucht Bandbreite und erzeugt während der Messung Last auf dem Anschluss. Bei Tarifen mit Volumenbegrenzung oder Mobilfunk-Backup ist ein sparsamer Zeitplan Pflicht.
- Gemessen wird gegen Ookla-Server. Die CLI wird laut Projektdokumentation mit
--accept-license --accept-gdpraufgerufen, damit gelten die Nutzungs- und Datenschutzbedingungen von Ookla. - Laut Projektdokumentation werden nur die dort gelisteten Installationswege unterstützt (Docker, Docker Compose, Kubernetes sowie QNAP, Synology und Unraid). Bare-Metal-Installationen oder Proxmox-LXC ohne Docker sind ausdrücklich nicht unterstützt.
Voraussetzungen und Ressourcen
- Linux-Host mit Docker Engine und dem Compose-Plugin (
docker compose). - Kabelgebundene Anbindung des Hosts an den Router, damit die Messung den Anschluss und nicht das WLAN abbildet.
- Ein freier Port für die Weboberfläche, in dieser Anleitung
127.0.0.1:18472. - Ausgehender Zugriff auf
icanhazip.com(Internetprüfung vor jeder Messung) und auf die Ookla-Server. DNS-Blocklisten, dieicanhazip.comsperren, führen zu fehlgeschlagenen Messungen. opensslauf dem Host, um den Anwendungsschlüssel zu erzeugen.
Der Ressourcenbedarf ist gering. Im Test belegte der Container im Leerlauf rund 152 MiB RAM. Das Image ist komprimiert etwa 68 MB groß und entpackt rund 376 MB. Die SQLite-Datenbank war nach der Erstinitialisierung 152 KiB groß und wächst pro Messung nur um wenige Kilobyte.
Plattformen, Image und getestete Version
Das Projekt baut kein eigenes Image. Die offizielle Dokumentation verweist auf das Image von LinuxServer.io, das in allen Compose-Beispielen als lscr.io/linuxserver/speedtest-tracker verwendet wird. LinuxServer.io veröffentlicht es für zwei Architekturen:
| Architektur | Tag | Typische Hosts |
|---|---|---|
| x86-64 | amd64-latest | Server, Mini-PCs, die meisten NAS mit Intel oder AMD |
| arm64 | arm64v8-latest | Raspberry Pi 4 und 5 mit 64-Bit-System, ARM-Server |
Getestet wurde die amd64-Variante mit dem festen Tag v1.15.0-ls172 (LinuxServer-Release vom 19.09.2026). Im Container meldete php artisan about Speedtest Tracker v1.15.0, Laravel 13.26.0, PHP 8.5.10 und Filament v5.7.6, die mitgelieferte Messsoftware ist Speedtest by Ookla 1.2.0.84. Die Dokumentation zeigt latest. Für den Betrieb empfehlen wir einen festen Versions-Tag, damit ein Update eine bewusste Entscheidung bleibt.
Vollständige compose.yaml und .env
Die Dokumentation beschreibt vier Varianten: SQLite, MariaDB, MySQL und Postgres. Für einen einzelnen Standort reicht SQLite, es braucht keinen zweiten Container und lässt sich mit einer Datei sichern. Legen Sie ein Projektverzeichnis an und darin die folgende compose.yaml:
name: speedtest
services:
speedtest-tracker:
image: lscr.io/linuxserver/speedtest-tracker:v1.15.0-ls172
container_name: speedtest-tracker
restart: unless-stopped
ports:
- "127.0.0.1:18472:80"
environment:
- PUID=${PUID}
- PGID=${PGID}
- TZ=Europe/Berlin
- APP_KEY=${APP_KEY}
- APP_URL=${APP_URL}
- DB_CONNECTION=sqlite
- SPEEDTEST_SCHEDULE=${SPEEDTEST_SCHEDULE}
- DISPLAY_TIMEZONE=Europe/Berlin
- PRUNE_RESULTS_OLDER_THAN=365
volumes:
- ./data:/config
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost/api/healthcheck || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
Die Werte stehen in einer .env im selben Verzeichnis. Das Beispiel enthält bewusst keinen echten Schlüssel:
# Benutzer und Gruppe des Hosts, ermitteln mit: id
PUID=1000
PGID=1000
# Erzeugen mit: echo "base64:$(openssl rand -base64 32)"
APP_KEY=base64:HIER_EIGENEN_SCHLUESSEL_EINTRAGEN
# Adresse, unter der die Oberfläche erreichbar ist
APP_URL=http://127.0.0.1:18472
# Minute 17 alle sechs Stunden
SPEEDTEST_SCHEDULE=17 */6 * * *
Den Schlüssel erzeugen Sie so, wie es die Dokumentation vorgibt, und übernehmen die Ausgabe inklusive Präfix base64::
echo "base64:$(openssl rand -base64 32 2>/dev/null)"
chmod 600 .env
Parameter und Dateipfade
| Variable | Pflicht | Wirkung |
|---|---|---|
PUID, PGID | ja | Benutzer und Gruppe, unter denen die Anwendung läuft. Im Test gehörten dadurch alle Dateien in ./data dem Host-Benutzer mit UID 1000. |
APP_KEY | ja | Schlüssel zum Ver- und Entschlüsseln von Sitzungen und sensiblen Daten. Ohne ihn bricht die Initialisierung ab. |
APP_URL | ja | Adresse für Links in Mails und Benachrichtigungen. Hinter einem Proxy die öffentliche HTTPS-Adresse. |
DB_CONNECTION | nein | sqlite, alternativ mariadb, mysql oder pgsql mit den zugehörigen DB_*-Variablen. |
SPEEDTEST_SCHEDULE | nein | Cron-Ausdruck für automatische Messungen. Ohne Wert misst die Anwendung nur auf Knopfdruck. |
SPEEDTEST_SERVERS | nein | Kommagetrennte Ookla-Server-IDs, aus denen zufällig gewählt wird. |
PRUNE_RESULTS_OLDER_THAN | nein | Ergebnisse älter als die angegebene Zahl an Tagen werden gelöscht. 0 behält alles. |
DISPLAY_TIMEZONE | nein | Zeitzone für die Anzeige. Die Anwendung selbst rechnet intern in UTC. |
ADMIN_EMAIL, ADMIN_PASSWORD | nein | Zugangsdaten des ersten Admins, wirken laut Dokumentation nur bei der Ersteinrichtung. |
ASSET_URL | nein | Adresse für CSS und JavaScript, hinter einem Reverse Proxy zusammen mit APP_URL setzen. |
Alles Persistente liegt in ./data, im Container /config. Nach dem ersten Start fanden sich dort unter anderem database.sqlite, das Verzeichnis keys/ mit einem automatisch erzeugten, selbstsignierten Zertifikat (cert.crt, cert.key) sowie log/, nginx/ und php/. Eigene Zertifikate müssen laut Dokumentation als cert.crt (vollständige Kette) und cert.key in /config/keys liegen.
Installation und Start
# Image laden
docker compose pull
# Container im Hintergrund starten
docker compose up -d
# Status und Healthcheck beobachten
docker compose ps
Im Test lieferte der Healthcheck nach 52 Sekunden HTTP 200, kurz darauf stand der Container auf Up About a minute (healthy). Das Startlog nennt die Image-Version:
docker compose logs --no-log-prefix | grep -i version
# Ausgabe im Test:
# Linuxserver.io version: v1.15.0-ls172
Die Programmversion prüfen Sie im Container:
docker compose exec speedtest-tracker bash -c 'cd /app/www && php artisan about'
Erstkonfiguration
Die Anmeldeseite liegt unter /admin/login. Beim ersten Start legt die Anwendung laut Dokumentation einen Admin mit admin@example.com und dem Passwort password an. Im Test stand genau dieser Benutzer mit der Rolle admin in der Datenbank, und die Passwortprüfung gegen den gespeicherten Hash bestätigte das Standardpasswort. Ändern Sie beides sofort nach der ersten Anmeldung über das Benutzermenü oben rechts und dort über Profile. Alternativ setzen Sie ADMIN_EMAIL und ADMIN_PASSWORD schon vor dem allerersten Start, spätere Änderungen dieser Variablen haben keine Wirkung mehr.
Falls der Zugang verloren geht, bringt das Image einen interaktiven Befehl zum Zurücksetzen mit:
docker compose exec -it speedtest-tracker bash -c 'cd /app/www && php artisan app:user-reset-password'
Für den Zeitplan empfiehlt die Projekt-FAQ, falls geplante Messungen schlechter ausfallen als manuelle, eine weniger belegte Minute statt der vollen Stunde zu wählen. Das Beispiel nutzt deshalb Minute 17. Ob der Wert angekommen ist, zeigt:
docker compose exec speedtest-tracker bash -c 'cd /app/www && php artisan config:show speedtest'
Im Test meldete die Ausgabe schedule ... 17 */6 * * *. Die Anwendung prüft jede Minute, ob eine geplante Messung fällig ist. Wer feste Messserver nutzen will, lässt sich die nahen Server auflisten und trägt die IDs in SPEEDTEST_SERVERS ein:
docker compose exec speedtest-tracker bash -c 'cd /app/www && php artisan app:ookla-list-servers'
Eine erste Messung starten Sie in der Oberfläche über die Schaltfläche in der oberen Leiste. Im Test wurde einmalig eine Messung gegen einen nahen Server ausgelöst, sie stand nach rund 15 Sekunden auf completed und enthielt Ping, Download, Upload, Servername sowie den Link auf das Ookla-Ergebnis.
Benachrichtigungen und Schwellwerte
Laut Dokumentation gelten Datenbank, Mail und Webhook als Kernkanäle, Apprise wird als Weg für alle weiteren Dienste ausgebaut, andere Kanäle sind als veraltet markiert. Mail benötigt SMTP-Variablen:
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=speedtest@example.com
MAIL_PASSWORD=HIER_SMTP_PASSWORT
MAIL_FROM_ADDRESS=speedtest@example.com
MAIL_FROM_NAME="Speedtest Tracker"
Die Dokumentation warnt davor, dieselben Variablen gleichzeitig in einer .env der Anwendung und in der Compose-Datei zu setzen. Schwellwerte (THRESHOLD_ENABLED, THRESHOLD_DOWNLOAD, THRESHOLD_UPLOAD, THRESHOLD_PING) wirken als Variablen nur bei der Ersteinrichtung, danach pflegen Sie sie in der Oberfläche. Wer bereits einen eigenen Push-Dienst betreibt, bindet ihn per Webhook oder Apprise an.
Funktionsprüfung und Healthcheck
Der dokumentierte Endpunkt /api/healthcheck antwortet mit HTTP 200 und einer kurzen JSON-Meldung:
curl -s http://127.0.0.1:18472/api/healthcheck
# Ausgabe im Test:
# {"message":"Speedtest Tracker is running!"}
Der Healthcheck in der compose.yaml ruft denselben Endpunkt im Container über localhost auf, curl ist im Image enthalten. Das Beispiel in der Dokumentation nutzt APP_URL und jq, die lokale Variante ist unabhängig von DNS und Proxy. Die REST-API unter /api/v1 verlangt ein Token, ein Aufruf ohne Token lieferte im Test HTTP 401. Tokens legen Sie laut Dokumentation unter /admin/api-tokens an und vergeben dort Berechtigungen wie Ergebnisse lesen oder Messung starten.
Netzwerkfreigabe, Reverse Proxy und TLS
Die Bindung an 127.0.0.1 sorgt dafür, dass die Oberfläche nur vom Host selbst erreichbar ist. Ein Aufruf über die LAN-Adresse des Hosts schlug im Test erwartungsgemäß fehl. Für den Zugriff aus dem Netz setzen Sie einen Reverse Proxy mit gültigem Zertifikat davor und tragen die öffentliche Adresse in APP_URL und ASSET_URL ein:
APP_URL=https://speedtest.example.com
ASSET_URL=https://speedtest.example.com
Ein passender Server-Block für Nginx nach dem Muster der Projektdokumentation:
server {
listen 443 ssl;
server_name speedtest.example.com;
ssl_certificate /etc/letsencrypt/live/speedtest.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/speedtest.example.com/privkey.pem;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:18472;
}
}
Zusätzlich bietet die Anwendung ALLOWED_IPS, um Anfragen auf bestimmte Adressen zu beschränken, und PUBLIC_DASHBOARD für ein Dashboard ohne Anmeldung. Letzteres sollten Sie nur im internen Netz aktivieren. Die Oberfläche gehört grundsätzlich nicht ungeschützt ins Internet, VPN oder Proxy mit vorgeschalteter Anmeldung sind die bessere Wahl.
Persistente Daten und Rechte
Dank PUID und PGID gehören die Dateien in ./data dem angegebenen Host-Benutzer. Einzelne Unterverzeichnisse, die der Container selbst anlegt, können trotzdem abweichende Rechte haben: Im Test scheiterte ein rm -rf data als normaler Benutzer an data/.composer/cache/.htaccess: Permission denied. Für Backup und Restore ist das unkritisch, weil die relevanten Daten in database.sqlite und keys/ liegen. Zum Entfernen des ganzen Verzeichnisses brauchen Sie aber sudo oder einen Hilfscontainer.
Backup und Restore
Bei SQLite besteht der Datenbestand aus einer Datei. Für ein konsistentes Backup stoppen Sie den Container kurz, damit während des Kopierens nicht geschrieben wird. Sichern Sie außerdem die .env getrennt und geschützt, denn ohne den passenden APP_KEY lassen sich verschlüsselte Inhalte nicht mehr lesen.
# Container anhalten
docker compose stop
# Datenverzeichnis sichern
tar czf speedtest-backup-$(date +%F).tgz -C data .
# Container wieder starten
docker compose start
Der Restore wurde im Test mit vorherigem Löschen belegt: In die Datenbank kam eine Markertabelle, dann folgte das Backup (12,5 KB). Anschließend wurde database.sqlite gelöscht, ls bestätigte das Fehlen der Datei. Nach dem Zurückspielen stimmte die SHA-256-Prüfsumme mit dem Stand vor dem Backup überein, der Healthcheck lieferte wieder 200 und die Markertabelle war samt Inhalt zurück.
# Container anhalten
docker compose stop
# Datenbank aus dem Backup zurückholen
tar xzf speedtest-backup-2026-09-26.tgz -C data ./database.sqlite
# Prüfsumme mit dem Stand vor dem Backup vergleichen
sha256sum data/database.sqlite
# Container starten und Healthcheck abwarten
docker compose start
curl -s http://127.0.0.1:18472/api/healthcheck
Beim Entpacken in ein nicht leeres Verzeichnis meldete tar im Test Cannot open: File exists für die Dateien unter .composer. Stellen Sie deshalb gezielt nur die benötigten Dateien wieder her oder entpacken Sie in ein leeres Verzeichnis. Wer MariaDB oder Postgres nutzt, sichert stattdessen per mariadb-dump oder pg_dump.
Updates und Rollback-Grenzen
Ein Update besteht aus drei Schritten: Backup erstellen, Tag in der compose.yaml anheben, Container neu erstellen.
# neuen Tag in compose.yaml eintragen, danach:
docker compose pull
docker compose up -d
docker compose logs --no-log-prefix | grep -i version
Beim Start führt die Anwendung Datenbankmigrationen aus. Ein Rollback nur über den alten Image-Tag ist deshalb nicht verlässlich, weil eine ältere Version mit dem migrierten Schema nicht zurechtkommen muss. Ein sauberer Rückweg besteht aus altem Tag plus dem Backup von vor dem Update. Die Release-Hinweise auf GitHub lesen Sie vor jedem Versionssprung.
Typische Fehler mit Diagnose und Lösung
| Symptom | Ursache | Lösung |
|---|---|---|
Log zeigt An application key is missing, halting init!, Port antwortet nicht, Container bleibt trotzdem Up | APP_KEY leer oder nicht in der .env | Schlüssel erzeugen, eintragen, docker compose up -d. Im Test provoziert. |
Unsupported cipher or incorrect key length | Schlüssel falsch formatiert, etwa ohne base64: | Schlüssel mit dem dokumentierten Befehl neu erzeugen |
Bind for 127.0.0.1:18472 failed: port is already allocated | Port bereits belegt | Anderen Port wählen oder belegenden Dienst finden mit ss -ltnp. Im Test provoziert. |
Failed to connected to hostname oder Failed to fetch external IP address | Kein Internet im Container oder icanhazip.com per DNS-Filter blockiert | Domain freigeben oder eigene Adressen per SPEEDTEST_INTERNET_CHECK_HOSTNAME und SPEEDTEST_EXTERNAL_IP_URL setzen |
No servers defined oder Failed to find a working test server | Eingetragene Server-ID existiert nicht mehr | Liste mit app:ookla-list-servers neu holen, IDs anpassen |
| Geplante Messungen zu falscher Uhrzeit | Anwendung rechnet in UTC | APP_TIMEZONE und DISPLAY_TIMEZONE passend setzen, bei externer Datenbank deren Zeitzone angleichen |
| Oberfläche hinter Proxy ohne Styles | ASSET_URL fehlt | APP_URL und ASSET_URL auf die HTTPS-Adresse setzen |
500 | SERVER ERROR | Fehlkonfiguration oder Fehler in der Anwendung | Vorübergehend APP_DEBUG=true setzen, Log auf production.ERROR prüfen, danach wieder entfernen |
Ein Hinweis zur Diagnose: php artisan tinker ist im Image nicht enthalten, der Aufruf endet mit Command "tinker" is not defined. Die Befehle about, config:show, schedule:list und die app:-Befehle stehen dagegen zur Verfügung.
Saubere Deinstallation
Achtung: Die folgenden Schritte löschen alle Messergebnisse, Benutzer und Einstellungen unwiderruflich. Erstellen Sie vorher ein Backup, falls Sie die Messreihe als Beleg behalten möchten.
# Container und Netzwerk entfernen
docker compose down -v --remove-orphans
# Image namentlich löschen
docker rmi lscr.io/linuxserver/speedtest-tracker:v1.15.0-ls172
# Datenverzeichnis löschen, bei Rechtefehlern mit sudo
sudo rm -rf data
# Kontrolle
docker ps -a --filter name=speedtest
Vergessen Sie nicht, einen eventuell eingerichteten Server-Block im Reverse Proxy und DNS-Einträge zu entfernen.
Testumfang
Getestet wurden am 26.09.2026 auf einem x86-64-Linux-Host mit SQLite: Start und Healthcheck mit HTTP 200, Versionsausgabe im Log, der provozierte Fehler bei fehlendem APP_KEY, der Portkonflikt, das Standardkonto in der Datenbank, die Übernahme des Zeitplans, eine einzelne manuell ausgelöste Messung sowie Backup und Restore mit vorherigem Löschen der Datenbank. Nur aus der Dokumentation stammen Browser-Anmeldung, Benachrichtigungen, Reverse Proxy mit TLS, externe Datenbanken, der Update-Weg, arm64 und der tatsächlich zeitgesteuerte Messlauf.
Passende Anleitungen auf S-EDV
- Uptime Kuma mit Docker und Nginx Proxy Manager installieren: ergänzt die Anschlussmessung um die Verfügbarkeitsüberwachung Ihrer Dienste.
- Nginx als Reverse Proxy mit TLS einrichten: für den geschützten Zugriff auf die Oberfläche aus dem Netz.
- ntfy mit Docker selbst hosten: eigener Push-Dienst als Ziel für Webhook-Benachrichtigungen bei Schwellwertverletzungen.
Quellen
- Speedtest Tracker Dokumentation: Using Docker Compose
- Speedtest Tracker Dokumentation: Environment Variables
- Speedtest Tracker Dokumentation: Authentication (Standardkonto)
- Speedtest Tracker Dokumentation: Error Messages
- GitHub: alexjustesen/speedtest-tracker (Stand 26.09.2026)
- GitHub: linuxserver/docker-speedtest-tracker
- Bundesnetzagentur: Internetgeschwindigkeit und Nachweisverfahren