Zum Inhalt springen
Serverküche
Suche

Die Suche wird geladen … (nur in der veröffentlichten Seite verfügbar).

Container Schwierigkeit: Profi

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.

· 12 Min. Lesezeit ·Dauer: ca. 60 Minuten
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:

MiddlewareWirkungNachgemessen
sec-headersHSTS, Clickjacking- und MIME-Schutz5 neue Antwort-Header
ratebegrenzt Anfragen pro IP40 parallele Anfragen → 13× 200, 27× 429
authBasicAuth vor dem Dienstohne Passwort 401, mit 200
nur-ichnur deine IP darf reinfremde 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
🍳 Empfehlung Anzeige

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.

Zu netcup →

💶 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:

YAML
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: true
Terminal
mkdir -p ~/whoami && cd ~/whoami && docker compose up -d

Jetzt die Antwort-Header ansehen:

Terminal
curl -sI https://whoami.DEINE_DOMAIN/
Ausgabe
HTTP/2 200
content-type: text/plain; charset=utf-8
date: Sat, 25 Jul 2026 07:01:27 GMT
content-length: 351

Das 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:

YAML
      # 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=()"
Terminal
docker compose up -d && sleep 5 && curl -sI https://whoami.DEINE_DOMAIN/
Ausgabe
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: DENY

Fü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 von example.de und dem Redirect auf HTTPS.
  • x-frame-options: DENY verbietet das Einbetten in fremde <iframe>s – der klassische Clickjacking-Schutz.
  • x-content-type-options: nosniff verhindert, dass der Browser den Inhaltstyp „errät" und etwa eine hochgeladene Textdatei als Skript ausführt.
  • referrer-policy verhindert, dass beim Klick auf externe Links deine vollständige interne URL mitgeschickt wird.
  • permissions-policy schaltet 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:

YAML
      - "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:

Terminal
seq 40 | xargs -P 8 -I{} curl -s -o /dev/null -w "%{http_code}\n" https://whoami.DEINE_DOMAIN/ \
  | sort | uniq -c
Ausgabe
     13 200
     27 429

Genau 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

Für eine normale Weboberfläche sind 5–20 Anfragen pro Sekunde mit Burst 20–50 realistisch. Zu knapp gesetzt, blockierst du echte Nutzer beim Laden einer Seite. Beobachte nach dem Aktivieren die Traefik-Logs auf 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:

Terminal
docker run --rm httpd:2.4-alpine htpasswd -nbB koch "DEIN_PASSWORT"
Ausgabe
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

bcrypt-Hashes enthalten $-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.
YAML
      - "traefik.http.middlewares.auth.basicauth.users=koch:$$2y$$05$$IcOtS/jq6VR4haHO2Pvtl..."

Prüfen – drei Fälle, drei Antworten:

Terminal
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/
Ausgabe
401
401
200

Der 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:

YAML
      - "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:

Ausgabe
403

Und mit der eigenen:

Ausgabe
200

Welche IP sieht Traefik überhaupt?

Traefik prüft die IP der direkten Verbindung. Steht noch etwas davor – Cloudflare, ein Load-Balancer, ein weiterer Proxy – siehst du dessen IP, und eine Allowlist mit deiner eigenen sperrt dich selbst aus. Was tatsächlich ankommt, verrät 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:

YAML
      - "traefik.http.middlewares.gehaertet.chain.middlewares=sec-headers,rate,auth"
      - "traefik.http.routers.whoami.middlewares=gehaertet"

Prüfen, dass alles gleichzeitig wirkt:

Terminal
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-transport
Ausgabe
401
strict-transport-security: max-age=31536000; includeSubDomains; preload

Ohne 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:

YAML
    command:
      - "--api.dashboard=true"
      # … die bestehenden Parameter …

Und gib Traefik Labels für sich selbst. Der Trick ist der interne Service api@internal:

YAML
    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:

Terminal
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/overview
Ausgabe
401
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

Noch besser als BasicAuth ist beim Dashboard die Kombination: 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:

YAML
    command:
      # … die bestehenden Parameter …
      - "--providers.file.directory=/dynamic"
      - "--providers.file.watch=true"
    volumes:
      # … bestehende Mounts …
      - ./dynamic:/dynamic:ro

Dann die Middlewares als YAML in ~/traefik/dynamic/middlewares.yml:

YAML
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: 1s

Traefik neu starten – danach hängst du sie an jeden Dienst mit dem Zusatz @file:

YAML
      - "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?

Traefik hängt an jeden Namen die Herkunft an: über Labels definierte heißen intern 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:

DiensttypBeispielesinnvolle Kette
Öffentlich, für alleWebsite, Blog, Status-Seitesec-headers
Öffentlich mit LoginNextcloud, Immich, Vaultwardensec-headers, rate
Nur für dich, mit LoginUptime Kuma, Grafanasec-headers, rate (+ Allowlist, wenn möglich)
Ohne eigene AnmeldungTraefik-Dashboard, Metrik-Endpunktenur-ich, rate, auth
Gefährlich bei MissbrauchDockge, Portainer, Adminernur-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