Traefik härten: Security-Header, Rate-Limit, BasicAuth & IP-Allowlist
Traefik-Middlewares in v3 richtig einsetzen: Security-Header setzen, Anfragen begrenzen, Dienste mit BasicAuth und IP-Allowlist schützen – mit curl geprüft.
Inhaltsverzeichnis
Dein Traefik verteilt Anfragen und holt Zertifikate – aber er schützt noch nichts. Vier
Middlewares ändern das: Security-Header, ein Rate-Limit, BasicAuth und eine IP-Allowlist. Wir bauen
sie einzeln auf und messen nach jedem Schritt mit curl, was sich wirklich geändert hat.
Was bauen wir?
Am Ende hast du vier wiederverwendbare Middlewares und eine Kette, die du an jeden Dienst hängen kannst:
| Middleware | Wirkung | Nachgemessen |
|---|---|---|
sec-headers | HSTS, Clickjacking- und MIME-Schutz | 5 neue Antwort-Header |
rate | begrenzt Anfragen pro IP | 40 parallele Anfragen → 13× 200, 27× 429 |
auth | BasicAuth vor dem Dienst | ohne Passwort 401, mit 200 |
nur-ich | nur deine IP darf rein | fremde IP 403, eigene 200 |
Getestet mit Traefik v3.7.8 auf Debian 13. Wichtig für Umsteiger: In Traefik v3 heißt die
Allowlist ipAllowList – den v2-Namen ipWhiteList gibt es nicht mehr, entsprechende
Konfigurationen brechen beim Upgrade.
Voraussetzungen
- Ein laufender Traefik v3 mit
proxy-Netz und funktionierendem TLS, wie im Reverse Proxy mit Traefik aufgebaut - Ein Dienst dahinter, an dem du üben kannst. Wir nehmen
traefik/whoami– ein winziger Container, der jede Anfrage samt Headern zurückgibt, also perfekt zum Prüfen - Grundverständnis von Docker Compose und Labels
VPS 1000 G12.5
4 vCore · 8 GB RAM · 128 GB SSD
ab 14,50 €/Monat
Middlewares kosten praktisch keine Ressourcen – das läuft auf dem kleinsten VPS.
💶 1 Monat gratis für netcup-Neukunden:
Einmal einlösbar, gilt für VPS 1000 G12.5. Im Warenkorb einlösen →
💶 5 € Gutschein für netcup-Neukunden: immer gültig · nicht für Domains und VPS Lite
Schritt für Schritt
1. Ausgangslage messen
Bevor du etwas härtest, halte fest, wie es vorher war. Leg einen Testdienst an –
~/whoami/compose.yaml, DEINE_DOMAIN ersetzen:
services:
whoami:
image: traefik/whoami:v1.12
restart: unless-stopped
networks: [proxy]
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.DEINE_DOMAIN`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=le"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
proxy:
external: truemkdir -p ~/whoami && cd ~/whoami && docker compose up -dJetzt die Antwort-Header ansehen:
curl -sI https://whoami.DEINE_DOMAIN/HTTP/2 200
content-type: text/plain; charset=utf-8
date: Sat, 25 Jul 2026 07:01:27 GMT
content-length: 351Das ist die ganze Wahrheit: Traefik setzt von sich aus keinen einzigen Security-Header. TLS allein sorgt für einen verschlüsselten Transport – gegen Clickjacking, MIME-Sniffing oder versehentliches HTTP hilft es nicht.
2. Security-Header setzen
Middlewares werden bei Docker-Labels an einem beliebigen Container definiert und an einem Router aktiviert. Ergänze die Labels des Testdienstes:
# aktivieren
- "traefik.http.routers.whoami.middlewares=sec-headers"
# definieren
- "traefik.http.middlewares.sec-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.sec-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.sec-headers.headers.stsPreload=true"
- "traefik.http.middlewares.sec-headers.headers.frameDeny=true"
- "traefik.http.middlewares.sec-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.sec-headers.headers.referrerPolicy=strict-origin-when-cross-origin"
- "traefik.http.middlewares.sec-headers.headers.permissionsPolicy=camera=(), microphone=(), geolocation=()"docker compose up -d && sleep 5 && curl -sI https://whoami.DEINE_DOMAIN/HTTP/2 200
content-type: text/plain; charset=utf-8
permissions-policy: camera=(), microphone=(), geolocation=()
referrer-policy: strict-origin-when-cross-origin
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-content-type-options: nosniff
x-frame-options: DENYFünf Header mehr, und jeder hat einen Zweck:
strict-transport-security(HSTS) sagt dem Browser: Sprich mit dieser Domain ein Jahr lang nur noch über HTTPS. Das schließt die Lücke zwischen dem Tippen vonexample.deund dem Redirect auf HTTPS.x-frame-options: DENYverbietet das Einbetten in fremde<iframe>s – der klassische Clickjacking-Schutz.x-content-type-options: nosniffverhindert, dass der Browser den Inhaltstyp „errät" und etwa eine hochgeladene Textdatei als Skript ausführt.referrer-policyverhindert, dass beim Klick auf externe Links deine vollständige interne URL mitgeschickt wird.permissions-policyschaltet Kamera, Mikrofon und Standort für diese Seite ab.
preload ist eine Einbahnstraße
stsPreload=true ist die Einladung, deine Domain in die HSTS-Preload-Liste der Browser
aufzunehmen (die Aufnahme selbst beantragst du separat). Ist sie dort, erzwingen Browser HTTPS für
die Domain und alle Subdomains – auch wenn dein Server längst kein Zertifikat mehr liefert.
Herauskommen dauert Monate. Setze stsIncludeSubdomains und stsPreload nur, wenn du sicher bist,
dass jede Subdomain dauerhaft HTTPS spricht. Für den Anfang reicht stsSeconds allein.Bewusst nicht in der Liste: browserXssFilter. Der Header X-XSS-Protection gilt als
veraltet, moderne Browser ignorieren ihn. Auch eine contentSecurityPolicy fehlt hier – die ist
wirkungsvoll, aber pro Anwendung verschieden und bricht Oberflächen zuverlässig, wenn man sie
pauschal setzt.
3. Rate-Limit gegen Brute-Force und Bots
Diese Middleware begrenzt, wie viele Anfragen eine IP stellen darf:
- "traefik.http.middlewares.rate.ratelimit.average=5"
- "traefik.http.middlewares.rate.ratelimit.burst=10"
- "traefik.http.middlewares.rate.ratelimit.period=1s"average=5 bei period=1s heißt: fünf Anfragen pro Sekunde im Schnitt. burst=10 erlaubt kurze
Spitzen darüber – Webseiten laden nun mal mehrere Dateien gleichzeitig.
Aktivieren und mit echter Last prüfen. Wichtig: parallel testen, sonst bist du zu langsam, um das Limit überhaupt zu treffen:
seq 40 | xargs -P 8 -I{} curl -s -o /dev/null -w "%{http_code}\n" https://whoami.DEINE_DOMAIN/ \
| sort | uniq -c 13 200
27 429Genau so soll es aussehen: Die ersten Anfragen gehen durch, danach antwortet Traefik mit
429 Too Many Requests, ohne den Dienst dahinter überhaupt zu belasten.
Sinnvolle Werte
429 – tauchen sie im Alltag auf, ist das Limit zu streng.4. BasicAuth für Dienste ohne eigene Anmeldung
Manche Werkzeuge haben keine oder eine schwache Anmeldung – ein Traefik-Dashboard, ein Metrik-Endpunkt, eine Admin-Oberfläche. BasicAuth setzt eine Hürde davor, bevor die Anfrage die Anwendung erreicht.
Zuerst einen Passwort-Hash erzeugen. Das geht ohne Extra-Paket, direkt mit Docker:
docker run --rm httpd:2.4-alpine htpasswd -nbB koch "DEIN_PASSWORT"koch:$2y$05$IcOtS/jq6VR4haHO2Pvtl...-B erzwingt bcrypt (nicht das alte MD5), -nb schreibt das Ergebnis auf die Standardausgabe.
Jedes $ muss im Compose verdoppelt werden
$-Zeichen, und Docker Compose interpretiert $ als Variable. Trage den
Hash deshalb mit doppelten Dollarzeichen ein: aus $2y$05$Ic… wird $$2y$$05$$Ic…. Vergisst du
das, verschwinden Teile des Hashes stillschweigend und die Anmeldung schlägt immer fehl –
docker compose config zeigt dir, was Compose wirklich daraus macht. - "traefik.http.middlewares.auth.basicauth.users=koch:$$2y$$05$$IcOtS/jq6VR4haHO2Pvtl..."Prüfen – drei Fälle, drei Antworten:
curl -s -o /dev/null -w "%{http_code}\n" https://whoami.DEINE_DOMAIN/
curl -s -o /dev/null -u koch:falsch -w "%{http_code}\n" https://whoami.DEINE_DOMAIN/
curl -s -o /dev/null -u koch:DEIN_PASSWORT -w "%{http_code}\n" https://whoami.DEINE_DOMAIN/401
401
200Der Browser fragt beim 401 selbst nach Zugangsdaten – Traefik schickt dazu
www-authenticate: Basic realm="traefik".
BasicAuth ist bewusst simpel: kein Logout, kein zweiter Faktor, keine Nutzerverwaltung. Für „niemand außer mir soll hier überhaupt klopfen können" ist es genau richtig; für echte Benutzerkonten über mehrere Dienste ist Single Sign-On der passende Weg.
5. IP-Allowlist: nur von zu Hause
Noch strenger: Anfragen nur aus bestimmten Netzen zulassen. In Traefik v3 heißt die Middleware
ipAllowList:
- "traefik.http.middlewares.nur-ich.ipallowlist.sourcerange=DEINE_IP/32"Deine öffentliche IP findest du mit curl -s https://ifconfig.me. Mehrere Bereiche trennst du mit
Komma, z. B. sourcerange=203.0.113.5/32,198.51.100.0/24.
Der Test zeigt beide Seiten. Mit einer fremden IP in der Liste antwortet Traefik:
403Und mit der eigenen:
200Welche IP sieht Traefik überhaupt?
traefik/whoami im Feld X-Forwarded-For.
Für solche Aufbauten gibt es ipallowlist.ipstrategy.depth bzw. excludedips – prüfe aber vorher,
wie viele Proxys wirklich davor hängen.Eine IP-Allowlist ist wirksam, aber unbequem: Am Handy im Mobilfunknetz oder im Hotel-WLAN kommst du nicht mehr rein. Die elegantere Variante für „nur ich" ist ein WireGuard-VPN – dann erlaubst du das VPN-Netz statt wechselnder öffentlicher IPs.
6. Middlewares zu einer Kette bündeln
Mehrere Middlewares an einem Router listest du kommagetrennt – die Reihenfolge ist die
Abarbeitungsreihenfolge. Damit du das nicht bei jedem Dienst wiederholst, gibt es chain:
- "traefik.http.middlewares.gehaertet.chain.middlewares=sec-headers,rate,auth"
- "traefik.http.routers.whoami.middlewares=gehaertet"Prüfen, dass alles gleichzeitig wirkt:
curl -s -o /dev/null -w "%{http_code}\n" https://whoami.DEINE_DOMAIN/
curl -sI -u koch:DEIN_PASSWORT https://whoami.DEINE_DOMAIN/ | grep -i strict-transport401
strict-transport-security: max-age=31536000; includeSubDomains; preloadOhne Zugangsdaten 401, mit Zugangsdaten kommen Antwort und Header. Für die Reihenfolge gilt
als Faustregel: billige Ablehnungen zuerst. Eine IP-Allowlist verwirft die Anfrage, ohne etwas
zu prüfen; das Rate-Limit kostet fast nichts; BasicAuth muss einen bcrypt-Hash rechnen – und der
ist absichtlich langsam. Also nur-ich, dann rate, dann auth.
7. Der wichtigste Anwendungsfall: das Traefik-Dashboard
Traefik bringt ein Dashboard mit, das alle Router, Services und Middlewares zeigt – im Alltag ausgesprochen nützlich beim Debuggen. Es ist im Standard aus, und das aus gutem Grund: Es verrät die komplette Struktur deines Servers. Genau hier zahlt sich BasicAuth aus.
Schalte im Traefik-Stack die API ein:
command:
- "--api.dashboard=true"
# … die bestehenden Parameter …Und gib Traefik Labels für sich selbst. Der Trick ist der interne Service api@internal:
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.DEINE_DOMAIN`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=le"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$2y$$05$$IcOtS/jq6VR4haHO2Pvtl..."Nach docker compose up -d prüfst du beide Einstiegspunkte – das Dashboard und die API, die
darunter liegt:
curl -s -o /dev/null -w "%{http_code}\n" https://traefik.DEINE_DOMAIN/dashboard/
curl -s -o /dev/null -u admin:DEIN_PASSWORT -w "%{http_code}\n" https://traefik.DEINE_DOMAIN/dashboard/
curl -s -o /dev/null -w "%{http_code}\n" https://traefik.DEINE_DOMAIN/api/overview
curl -s -u admin:DEIN_PASSWORT https://traefik.DEINE_DOMAIN/api/overview401
200
401
{"http":{"routers":{"total":3,"warnings":0,"errors":0},"services":{"total":5, …Wichtig ist die dritte Zeile: Die API ist genauso geschützt wie die Oberfläche. Hättest du die
Middleware nur an einen /dashboard-Pfad gehängt, wäre /api/… weiterhin offen – und damit die
gesamte Konfiguration lesbar. Weil der Router hier auf den ganzen Host greift, gilt die
Anmeldung für beides.
Tipp
nur-ich (IP-Allowlist) und
dashboard-auth als Kette. Dann muss ein Angreifer erst im richtigen Netz sein, bevor er überhaupt ein
Passwort raten darf. Für ein Dashboard, das du selten brauchst, ist das die passende Härte.8. Middlewares zentral definieren (der skalierbare Weg)
Bis hier haben wir alles über Labels definiert. Bei fünf Diensten kopierst du dieselben Zeilen fünfmal – und beim Ändern der HSTS-Dauer editierst du fünf Dateien. Besser: einmal zentral im File-Provider definieren.
Ergänze im Traefik-Stack zwei Startparameter und einen Mount:
command:
# … die bestehenden Parameter …
- "--providers.file.directory=/dynamic"
- "--providers.file.watch=true"
volumes:
# … bestehende Mounts …
- ./dynamic:/dynamic:roDann die Middlewares als YAML in ~/traefik/dynamic/middlewares.yml:
http:
middlewares:
sec-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
frameDeny: true
contentTypeNosniff: true
referrerPolicy: strict-origin-when-cross-origin
rate:
rateLimit:
average: 5
burst: 10
period: 1sTraefik neu starten – danach hängst du sie an jeden Dienst mit dem Zusatz @file:
- "traefik.http.routers.whoami.middlewares=sec-headers@file,rate@file"Nachgeprüft: Header und Rate-Limit wirken genauso wie über Labels (im Test 12× 200, 18× 429 bei
30 parallelen Anfragen). Der Gewinn: --providers.file.watch=true liest Änderungen an der Datei
ohne Neustart ein, und die HSTS-Dauer steht künftig an genau einer Stelle.
Woher kommt eine Middleware?
sec-headers@docker, aus Dateien geladene sec-headers@file. Innerhalb desselben Providers darfst
du den Zusatz weglassen – über Provider-Grenzen hinweg nicht. Genau daran scheitern die meisten
„middleware not found"-Fehler.9. Welche Middleware für welchen Dienst?
Nicht jeder Dienst braucht alles. Die Kette, die zu einem Dienst passt, hängt davon ab, wer ihn benutzen soll und was er selbst schon mitbringt:
| Diensttyp | Beispiele | sinnvolle Kette |
|---|---|---|
| Öffentlich, für alle | Website, Blog, Status-Seite | sec-headers |
| Öffentlich mit Login | Nextcloud, Immich, Vaultwarden | sec-headers, rate |
| Nur für dich, mit Login | Uptime Kuma, Grafana | sec-headers, rate (+ Allowlist, wenn möglich) |
| Ohne eigene Anmeldung | Traefik-Dashboard, Metrik-Endpunkte | nur-ich, rate, auth |
| Gefährlich bei Missbrauch | Dockge, Portainer, Adminer | nur-ich, auth – oder gar nicht öffentlich |
Zwei Regeln dahinter: Kein Rate-Limit vor Dienste, die viele parallele Anfragen brauchen –
Immich beim Handy-Backup oder ein Medienserver beim Streamen laufen sonst in 429. Und: BasicAuth
nie zusätzlich vor eine App mit eigener Anmeldung, wenn sie eine Mobil-App hat – die meisten Apps
können mit zwei übereinanderliegenden Anmeldungen nicht umgehen.
Für alles, was in der letzten Zeile steht, ist die ehrliche Antwort meist nicht „noch eine Middleware", sondern: nicht ins offene Internet stellen und über WireGuard erreichen.
Wenn es nicht funktioniert
In den Logs steht „middleware … does not exist" und der Dienst antwortet mit 404. Fast immer
fehlt der Provider-Zusatz: Eine im File-Provider definierte Middleware heißt aus Sicht der
Docker-Labels name@file. Ohne Zusatz sucht Traefik sie unter den Docker-Labels – und findet
nichts. Umgekehrt gilt dasselbe für name@docker.
BasicAuth lehnt das richtige Passwort ab. Die $-Zeichen im bcrypt-Hash wurden von Docker
Compose als Variablen interpretiert. Im Compose müssen sie verdoppelt werden ($$2y$$05$$…).
Mit docker compose config siehst du den Wert, der wirklich ankommt.
Das Rate-Limit scheint nicht zu greifen. Sequenzielle curl-Aufrufe sind zu langsam: Jeder
Prozess baut eine neue Verbindung auf, damit bleibst du unter dem Limit. Teste parallel, etwa mit
seq 40 | xargs -P 8 -I{} curl … – dann erscheinen die 429.
Nach dem Setzen der IP-Allowlist bekommst du selbst 403. Traefik sieht nicht deine IP, sondern
die des davorstehenden Proxys (Cloudflare, Load-Balancer). Prüfe mit traefik/whoami, was in
X-Forwarded-For steht, und arbeite bei mehrstufigen Aufbauten mit
ipallowlist.ipstrategy.depth.
Eine v2-Konfiguration mit ipWhiteList funktioniert nach dem Upgrade nicht mehr. Die Middleware
heißt in Traefik v3 ipAllowList; der alte Name wurde entfernt, die Regel greift damit nicht mehr.
Beim Umstieg alle Vorkommen umbenennen.
Der Browser kommt trotz korrigierter Konfiguration nicht mehr per HTTP durch. Das ist HSTS und
kein Fehler: Der Browser hat sich max-age gemerkt und erzwingt HTTPS selbst dann, wenn dein Server
es nicht mehr anbietet. Zum Testen hilft ein privates Fenster oder ein anderer Browser – und deshalb
die Warnung zu preload oben.
Dein Problem ist nicht dabei? Alle Fehlermeldungen durchsuchen →
Wartung & Backups
Nach jedem Traefik-Update gegenprüfen. Middlewares sind Konfiguration, und Konfiguration
veraltet: Zwischen v2 und v3 wurde ipWhiteList zu ipAllowList, andere Felder haben neue Namen
bekommen. Nach einem Update lohnt der kurze Durchlauf mit curl -sI – erscheinen die erwarteten
Header noch? Antwortet die Allowlist noch mit 403? Zwei Minuten, die dir stille Wirkungslosigkeit
ersparen.
Sichere das dynamic-Verzeichnis mit. Wenn du dem File-Provider folgst, steckt darin deine
gesamte Härtung. Es gehört zusammen mit compose.yaml und acme.json ins Backup – klein, aber
schmerzhaft zu rekonstruieren. Wie das automatisch läuft, steht in
Backups mit Restic.
Allowlists altern. Dynamische IP-Adressen von Privatanschlüssen ändern sich, Büro-Netze ziehen
um. Nimm dir einmal im Quartal die sourcerange-Einträge vor und wirf raus, was du nicht mehr
zuordnen kannst – eine Allowlist mit fremden Adressen ist schlechter als keine.
Ehrlich zur Reichweite dieser Maßnahmen. Security-Header schützen den Browser deiner Nutzer, nicht deinen Server. Ein Rate-Limit bremst Brute-Force, verhindert es nicht. BasicAuth ist eine Tür mit einem Schloss, kein Benutzerkonzept. Was diese vier Middlewares nicht ersetzen: einen abgesicherten SSH-Zugang, eine Firewall, automatische Updates und fail2ban bzw. CrowdSec für die Ebene darunter. Die Middlewares sind die Schicht, die vor deinen Anwendungen sitzt – nicht die einzige.
Feedback per E-Mail: feedback@serverkueche.de
Das könnte dir auch schmecken


