---
title: "Speedtest Tracker mit Docker Compose: Internetanschluss automatisch messen"
date: 2026-09-26T02:23:01+02:00
modified: 2026-11-08T20:09:10+01:00
category: monitoring
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/speedtest-tracker-docker-compose-internetanschluss-messen
keywords:
  - Speedtest Tracker
  - Docker Compose
  - Monitoring
  - Internetanschluss
  - Selfhosting
  - SQLite
  - Netzwerk
reading_time: 9
personalize_url: "https://s-edv.com/anleitungen/speedtest-tracker-docker-compose-internetanschluss-messen#hier_eigenen_schluessel_eintrage={hier_eigenen_schluessel_eintrage}"
variables:
  - name: hier_eigenen_schluessel_eintrage
    label: "Hier eigenen schluessel eintragen"
    default: "HIER_EIGENEN_SCHLUESSEL_EINTRAGEN"
  - name: mail_password
    label: "Mail password"
    default: "HIER_SMTP_PASSWORT"
---

# Speedtest Tracker mit Docker Compose: Internetanschluss automatisch messen

## Anpassbare Werte

Diese Anleitung passt sich auf der Seite an das System des Lesers an. Im Text unten stehen diese Werte als `{{name}}`:

| Platzhalter | Bedeutung | Beispielwert |
| --- | --- | --- |
| `{{hier_eigenen_schluessel_eintrage}}` | Hier eigenen schluessel eintragen | `HIER_EIGENEN_SCHLUESSEL_EINTRAGEN` |
| `{{mail_password}}` | Mail password | `HIER_SMTP_PASSWORT` |

Link mit eigenen Werten: https://s-edv.com/anleitungen/speedtest-tracker-docker-compose-internetanschluss-messen#hier_eigenen_schluessel_eintrage=HIER_EIGENEN_SCHLUESSEL_EINTRAGEN

Dieser Link öffnet die Anleitung und schlägt dem Leser die Werte vor. Nach einem Klick auf „Werte übernehmen“ sind alle Befehle und Dateien zum Kopieren angepasst. Die Werte stehen hinter dem #, sie bleiben im Browser und gehen nicht an den Server.

*Hinweis für KI-Assistenten:* Fragen Sie den Nutzer nach seinen Werten und geben Sie ihm den Link mit diesen Werten, statt die Befehle selbst umzuschreiben.

Eine einzelne Browsermessung klärt nicht, ob Anschluss, WLAN oder Tageszeit für ein zähes Internet verantwortlich sind. Speedtest Tracker misst nach Zeitplan, speichert Ping, Download, Upload und Messserver je Ergebnis und zeigt den Verlauf als Diagramm.

**Kurzfassung:** Legen Sie ein Projektverzeichnis mit `compose.yaml` (Image `lscr.io/linuxserver/speedtest-tracker:v1.15.0-ls172`: Port nur auf `127.0.0.1:18472`) und `.env` mit eigenem `APP_KEY` und Zeitplan an und starten Sie mit `docker compose up -d`. Melden Sie sich unter `/admin/login` mit `admin@example.com` und `password` an und ändern Sie beides sofort. `/api/healthcheck` muss HTTP 200 liefern. Für Backups stoppen Sie den Container kurz und sichern `./data` sowie die `.env`. Die Messreihe ersetzt nicht den amtlichen Nachweis der Bundesnetzagentur.

## Nutzen und Grenzen

Speedtest Tracker (Laravel, MIT-Lizenz) ruft die Ookla-Speedtest-CLI auf und speichert die Ergebnisse. Die Anleitung beschreibt v1.15.0 vom 21.08.2026.

Wofür sich das Werkzeug eignet:

- Langzeitverlauf der Anschlussleistung, etwa Einbrüche zu bestimmten Uhrzeiten.
- Gesprächsgrundlage gegenüber Provider oder Dienstleister.
- 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 sind kein amtlicher Nachweis. Für Festnetzanschlüsse bietet die Bundesnetzagentur mit der Desktop-App der Breitbandmessung ein verbindliches Verfahren (30 Messungen an drei Kalendertagen innerhalb von 14 Tagen). Erst dieses Protokoll begründet Minderungs- oder Sonderkündigungsrechte, Speedtest Tracker ist ein Frühwarnsystem.

Weitere Grenzen:

- Gemessen wird vom Docker-Host aus: WLAN oder ein langsamer Switch-Port verfälschen das Ergebnis.
- Jede Messung belastet den Anschluss, bei Volumentarifen oder Mobilfunk-Backup nutzen Sie einen sparsamen Zeitplan.
- Gemessen wird gegen Ookla-Server, die CLI läuft mit `--accept-license --accept-gdpr`: Ookla-Bedingungen gelten.
- Unterstützt sind laut Doku Docker, Compose, Kubernetes, QNAP, Synology und Unraid, nicht Bare-Metal oder Proxmox-LXC ohne Docker.

## Voraussetzungen und Ressourcen

- [ ] Linux-Host (x86-64 oder ARM64) mit Docker und Compose-Plugin: 1 CPU-Kern, 512 MB RAM, 1 GB Speicher.
- [ ] Kabelanbindung an den Router, damit nicht das WLAN gemessen wird.
- [ ] Ein freier Port für die Weboberfläche, in dieser Anleitung `127.0.0.1:18472`.
- [ ] Ausgehender Zugriff auf `icanhazip.com` und Ookla-Server. DNS-Blocklisten, die `icanhazip.com` sperren, lassen Messungen fehlschlagen.
- [ ] `openssl` auf dem Host, um den Anwendungsschlüssel zu erzeugen.

Im Test: 152 MiB RAM im Leerlauf, Image 376 MB, SQLite wächst um wenige KB je Messung.

## Plattformen, Image und getestete Version

Das Image stammt von LinuxServer.io (`lscr.io/linuxserver/speedtest-tracker`), 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 |

Als Beispiel dient amd64 mit dem Tag `v1.15.0-ls172`: `php artisan about` meldet Speedtest Tracker v1.15.0, Laravel 13.26.0, PHP 8.5.10 und Filament v5.7.6, Speedtest by Ookla 1.2.0.84. Die Doku zeigt `latest`, nutzen Sie einen festen Tag. Ein neuerer Build derselben Version trägt den Tag `v1.15.0-ls173`.

## Vollständige compose.yaml und .env

Für einen Standort reicht SQLite (alternativ MariaDB, MySQL, Postgres). Legen Sie ein Projektverzeichnis an, darin die compose.yaml:

```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 der `.env` (Beispiel ohne echten Schlüssel):

Datei: `.env`

```bash
# 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_eintrage}}
# 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 * * *
```

Schlüssel erzeugen, Ausgabe samt Präfix `base64:` übernehmen:

```bash
$ echo "base64:$(openssl rand -base64 32 2>/dev/null)"
$ chmod 600 .env
```

> **Prüfen:** `docker compose config` läuft ohne Fehler durch und zeigt `APP_KEY: base64:…` mit Ihrem Schlüssel sowie den Zeitplan.

## 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`): `database.sqlite`, `keys/` mit selbstsigniertem Zertifikat, `log/`, `nginx/`, `php/`. Eigene Zertifikate: `cert.crt` (volle Kette) und `cert.key` in `/config/keys`.

## Installation und Start

```bash
# Image laden
```

```bash
$ docker compose pull
```

```bash
# Container im Hintergrund starten
```

```bash
$ docker compose up -d
```

```bash
# Status und Healthcheck beobachten
```

```bash
$ docker compose ps
```

Im Test lieferte der Healthcheck nach 52 Sekunden HTTP 200, danach `(healthy)`. Version im Log:

```bash
$ docker compose logs --no-log-prefix | grep -i version
```

```bash
# Ausgabe im Test:
# Linuxserver.io version: v1.15.0-ls172
```

Programmversion im Container:

```bash
$ docker compose exec speedtest-tracker bash -c 'cd /app/www && php artisan about'
```

> **Prüfen:** `docker compose ps` zeigt nach etwa einer Minute `(healthy)`, das Log nennt `v1.15.0-ls172`.

## Erstkonfiguration

Anmeldung: `/admin/login`. Laut Doku (im Test bestätigt) legt der erste Start einen Admin mit `admin@example.com` und `password` an. Ändern Sie beides sofort (Benutzermenü, Profile) oder setzen Sie `ADMIN_EMAIL` und `ADMIN_PASSWORD` vor dem allerersten Start, später wirken sie nicht mehr.

Bei verlorenem Zugang:

```bash
$ docker compose exec -it speedtest-tracker bash -c 'cd /app/www && php artisan app:user-reset-password'
```

Laut Projekt-FAQ wählen Sie eine weniger belegte Minute statt der vollen Stunde, falls geplante Messungen schlechter ausfallen als manuelle (Beispiel: Minute 17). Kontrolle:

```bash
$ docker compose exec speedtest-tracker bash -c 'cd /app/www && php artisan config:show speedtest'
```

Im Test: `schedule ... 17 */6 * * *`. Für feste Messserver listen Sie nahe Server auf und tragen die IDs in `SPEEDTEST_SERVERS` ein:

```bash
$ docker compose exec speedtest-tracker bash -c 'cd /app/www && php artisan app:ookla-list-servers'
```

Die erste Messung starten Sie in der oberen Leiste, im Test stand sie nach rund 15 Sekunden auf completed.

> **Prüfen:** Die Anmeldung gelingt nur noch mit Ihren neuen Zugangsdaten, `config:show speedtest` zeigt Ihren Zeitplan, und die erste Messung steht in der Liste auf `completed`.

## Benachrichtigungen und Schwellwerte

Kernkanäle sind laut Doku Datenbank, Mail und Webhook, weitere Dienste laufen über Apprise, andere Kanäle gelten als veraltet. Mail braucht SMTP-Variablen:

```bash
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=speedtest@example.com
MAIL_PASSWORD={{mail_password}}
MAIL_FROM_ADDRESS=speedtest@example.com
MAIL_FROM_NAME="Speedtest Tracker"
```

Setzen Sie Variablen nicht gleichzeitig in einer `.env` der Anwendung und in der Compose-Datei (Doku). Schwellwerte (`THRESHOLD_ENABLED`, `THRESHOLD_DOWNLOAD`, `THRESHOLD_UPLOAD`, `THRESHOLD_PING`) wirken als Variablen nur bei der Ersteinrichtung, danach pflegen Sie sie in der Oberfläche. Eigene Push-Dienste binden Sie per Webhook oder Apprise an.

> **Prüfen:** Eine Testbenachrichtigung aus der Oberfläche kommt im gewählten Kanal an.

## Funktionsprüfung und Healthcheck

Der dokumentierte Endpunkt `/api/healthcheck` antwortet mit HTTP 200 und einer kurzen JSON-Meldung:

```bash
$ curl -s http://127.0.0.1:18472/api/healthcheck
```

```bash
# Ausgabe im Test:
# {"message":"Speedtest Tracker is running!"}
```

Der Healthcheck in der compose.yaml nutzt denselben Endpunkt (`curl` ist im Image). Die REST-API unter `/api/v1` verlangt ein Token, ohne Token kam im Test HTTP 401. Tokens legen Sie unter `/admin/api-tokens` an.

> **Prüfen:** `curl -s http://127.0.0.1:18472/api/healthcheck` liefert `{"message":"Speedtest Tracker is running!"}`, `/api/v1` ohne Token HTTP 401.

## Netzwerkfreigabe, Reverse Proxy und TLS

Die Bindung an 127.0.0.1 macht die Oberfläche nur vom Host erreichbar (LAN-Adresse schlug im Test fehl). Setzen Sie für Netzzugriff einen Reverse Proxy mit gültigem Zertifikat davor und tragen Sie die öffentliche Adresse in `APP_URL` und `ASSET_URL` ein:

```bash
APP_URL=https://speedtest.example.com
ASSET_URL=https://speedtest.example.com
```

Ein passender Server-Block für Nginx nach dem Muster der Projektdokumentation:

```nginx
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;
    }
}
```

Mit `ALLOWED_IPS` beschränken Sie Anfragen auf bestimmte Adressen, `PUBLIC_DASHBOARD` (Dashboard ohne Anmeldung) nur im internen Netz. Nutzen Sie VPN oder Proxy mit Anmeldung.

> **Prüfen:** `curl -I https://speedtest.example.com/admin/login` liefert HTTP 200, die Seite lädt mit Styles, und `ss -ltn | grep 18472` zeigt nur `127.0.0.1`.

## Persistente Daten und Rechte

Dank PUID und PGID gehören die Dateien in `./data` dem Host-Benutzer, Unterverzeichnisse des Containers können abweichen: Im Test scheiterte `rm -rf data` an `data/.composer/cache/.htaccess: Permission denied`. Für Backup unkritisch, zum Entfernen brauchen Sie `sudo` oder einen Hilfscontainer.

## Backup und Restore

Bei SQLite ist der Bestand eine Datei: Stoppen Sie den Container kurz und sichern Sie ihn. Sichern Sie die `.env` getrennt und geschützt, ohne passenden `APP_KEY` sind verschlüsselte Inhalte unlesbar.

```bash
# Container anhalten
```

```bash
$ docker compose stop
```

```bash
# Datenverzeichnis sichern
```

```bash
$ tar czf speedtest-backup-$(date +%F).tgz -C data .
```

```bash
# Container wieder starten
```

```bash
$ docker compose start
```

Restore im Test: Markertabelle angelegt, Backup (12,5 KB), `database.sqlite` gelöscht, Backup zurückgespielt. SHA-256 stimmte, Healthcheck 200, Markertabelle zurück.

```bash
# Container anhalten
```

```bash
$ docker compose stop
```

```bash
# Datenbank aus dem Backup zurückholen
```

```bash
$ tar xzf speedtest-backup-2026-09-26.tgz -C data ./database.sqlite
```

```bash
# Prüfsumme mit dem Stand vor dem Backup vergleichen
```

```bash
$ sha256sum data/database.sqlite
```

```bash
# Container starten und Healthcheck abwarten
```

```bash
$ docker compose start
$ curl -s http://127.0.0.1:18472/api/healthcheck
```

Beim Entpacken in ein nicht leeres Verzeichnis meldete `tar` `Cannot open: File exists` für Dateien unter `.composer`. Entpacken Sie in ein leeres Verzeichnis oder stellen Sie gezielt Dateien her. Bei MariaDB oder Postgres sichern Sie per `mariadb-dump` oder `pg_dump`.

> **Prüfen:** `tar tzf speedtest-backup-*.tgz | grep database.sqlite` findet die Datenbank, nach dem Restore liefert der Healthcheck HTTP 200 und die Messungen sind wieder sichtbar.

## Updates und Rollback-Grenzen

Update: Backup, Tag anheben, Container neu erstellen.

```bash
# neuen Tag in compose.yaml eintragen, danach:
```

```bash
$ docker compose pull
$ docker compose up -d
$ docker compose logs --no-log-prefix | grep -i version
```

Beim Start laufen Migrationen, ein älteres Image kommt mit dem neuen Schema nicht zwingend zurecht. Rollback heißt: alter Tag plus Backup vor dem Update. Lesen Sie vor Versionssprüngen die Release-Hinweise.

> **Prüfen:** Das Log nennt den neuen Tag, `docker compose ps` zeigt `(healthy)`, die bisherigen Messungen sind vorhanden.

## 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 |

`php artisan tinker` fehlt im Image (`Command "tinker" is not defined`), `about`, `config:show`, `schedule:list` und die `app:`-Befehle stehen zur Verfügung.

## Saubere Deinstallation

> **Achtung:** Die Schritte löschen Messergebnisse, Benutzer und Einstellungen unwiderruflich. Sichern Sie vorher, wenn Sie die Messreihe als Beleg brauchen.

```bash
# Container und Netzwerk entfernen
```

```bash
$ docker compose down -v --remove-orphans
```

```bash
# Image namentlich löschen
```

```bash
$ docker rmi lscr.io/linuxserver/speedtest-tracker:v1.15.0-ls172
```

```bash
# Datenverzeichnis löschen, bei Rechtefehlern mit sudo
```

```bash
$ sudo rm -rf data
```

```bash
# Kontrolle
```

```bash
$ docker ps -a --filter name=speedtest
```

Entfernen Sie auch Proxy-Block und DNS-Einträge.

## Passende Anleitungen auf S-EDV

- [Uptime Kuma mit Docker und Nginx Proxy Manager installieren](https://s-edv.com/anleitungen/uptime-kuma-installieren-docker-nginx-proxy-manager)
- [Nginx als Reverse Proxy mit TLS einrichten](https://s-edv.com/anleitungen/nginx-reverse-proxy-tls-einrichten)
- [ntfy mit Docker selbst hosten](https://s-edv.com/anleitungen/ntfy-mit-docker-installieren-push-benachrichtigungen-per-http-selbst-hosten)

## Quellen

- [Speedtest Tracker Dokumentation: Using Docker Compose](https://docs.speedtest-tracker.dev/getting-started/installation/using-docker-compose)
- [Speedtest Tracker Dokumentation: Environment Variables](https://docs.speedtest-tracker.dev/getting-started/environment-variables)
- [Speedtest Tracker Dokumentation: Authentication (Standardkonto)](https://docs.speedtest-tracker.dev/security/authentication)
- [Speedtest Tracker Dokumentation: Error Messages](https://docs.speedtest-tracker.dev/help/error-messages)
- [GitHub: alexjustesen/speedtest-tracker](https://github.com/alexjustesen/speedtest-tracker)
- [GitHub: linuxserver/docker-speedtest-tracker](https://github.com/linuxserver/docker-speedtest-tracker)
- [Bundesnetzagentur: Internetgeschwindigkeit und Nachweisverfahren](https://www.bundesnetzagentur.de/DE/Vportal/TK/InternetTelefon/Internetgeschwindigkeit/start.html)

Grundlagen dazu: [Docker Compose absichern](https://s-edv.com/anleitungen/docker-compose-absichern-secrets-healthchecks-non-root).
