Traefik Reverse Proxy

1. Einführung

Traefik ist ein moderner, cloud-nativer Reverse Proxy und Load Balancer, der speziell für dynamische Infrastrukturen entwickelt wurde. Im Gegensatz zu klassischen Lösungen wie Nginx oder HAProxy erkennt Traefik neue Dienste automatisch und konfiguriert sich selbst – ohne dass ein Neustart oder eine manuelle Konfigurationsänderung notwendig ist.

Der wesentliche Unterschied zu Nginx als Reverse Proxy liegt im Konfigurationsmodell. Nginx setzt auf eine statische, dateibasierte Konfiguration: Jede neue Anwendung erfordert einen neuen Serverblock in einer Konfigurationsdatei sowie einen Reload des Prozesses. Traefik hingegen liest seine Routing-Regeln direkt aus der laufenden Infrastruktur – etwa aus Docker-Labels, Kubernetes-Ingress-Ressourcen oder Consul-Einträgen – und passt sein Routing in Echtzeit an.

Traefik unterscheidet zwischen statischer Konfiguration und dynamischer Konfiguration. Die statische Konfiguration wird beim Start geladen und definiert grundlegende Parameter wie EntryPoints, Provider und TLS-Resolver. Die dynamische Konfiguration hingegen wird laufend aktualisiert und enthält die eigentlichen Routing-Regeln, Middlewares und Service-Definitionen. Diese Trennung erlaubt es, Dienste hinzuzufügen oder zu entfernen, ohne Traefik neu zu starten.

Der cloud-native Ansatz von Traefik macht ihn besonders attraktiv für Umgebungen mit vielen Containern, Microservices oder häufig wechselnden Deployments. Traefik unterstützt nativ Docker, Docker Swarm, Kubernetes, Nomad, Consul und weitere Provider. Auch die automatische TLS-Zertifikatsverwaltung über Let's Encrypt ist tief in Traefik integriert und benötigt keine zusätzlichen Tools wie certbot.

2. Architektur

Das Routing-Modell von Traefik basiert auf vier zentralen Konzepten: EntryPoints, Routers, Middlewares und Services. Zusammen bilden sie eine klare Pipeline, durch die jeder eingehende Request fließt.

2.1 EntryPoints

EntryPoints sind die Netzwerk-Einstiegspunkte von Traefik – also die Ports, auf denen Traefik lauscht. Typischerweise werden Port 80 für HTTP und Port 443 für HTTPS konfiguriert. EntryPoints können auch für TCP- oder UDP-Traffic verwendet werden, was Traefik auch für Nicht-HTTP-Protokolle (z. B. Datenbanken, Mail) nutzbar macht.

2.2 Routers

Routers nehmen eingehende Anfragen entgegen und leiten sie anhand von Regeln an einen Service weiter. Eine typische Router-Regel ist ein Host-Matcher: Host(`app.example.com`). Regeln können kombiniert werden, z. B. anhand von Pfad, Header oder HTTP-Methode. Jeder Router kann optional Middlewares einbinden und einen TLS-Resolver zugewiesen bekommen.

2.3 Middlewares

Middlewares transformieren Requests oder Responses, bevor sie den Service erreichen oder den Client verlassen. Typische Anwendungsfälle sind: Redirect von HTTP auf HTTPS, Authentifizierung, Rate-Limiting, das Hinzufügen von Security-Headern oder das Entfernen von URL-Präfixen (StripPrefix). Middlewares sind wiederverwendbar und können mehreren Routern zugewiesen werden.

2.4 Services

Services definieren das eigentliche Backend-Ziel – also die Anwendung, an die der Traffic weitergeleitet wird. Im Docker-Kontext wird das Backend automatisch aus der Container-IP und dem exponierten Port ermittelt. Traefik unterstützt Load Balancing über mehrere Backend-Instanzen, Health Checks und Sticky Sessions.

2.5 Provider

Provider sind die Quellen der dynamischen Konfiguration. Der Docker-Provider liest Labels von laufenden Containern. Der File-Provider lädt Konfiguration aus YAML- oder TOML-Dateien. Der Kubernetes-Provider nutzt Ingress-Ressourcen und Custom Resource Definitions. Mehrere Provider können gleichzeitig aktiv sein und ergänzen sich gegenseitig.

3. Statische Konfiguration

Die statische Konfiguration von Traefik wird entweder über eine Datei (traefik.yml oder traefik.toml) oder über CLI-Argumente übergeben. Sie wird einmalig beim Start eingelesen. Änderungen erfordern einen Neustart von Traefik.

Eine typische traefik.yml für einen Heimserver oder kleinen VPS sieht folgendermaßen aus:

## traefik.yml – Statische Konfiguration

api:
  dashboard: true
  insecure: false  # Dashboard nur über gesicherte Route erreichbar

log:
  level: INFO

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  file:
    filename: /etc/traefik/dynamic.yml
    watch: true

Mit exposedByDefault: false werden Docker-Container nicht automatisch über Traefik erreichbar gemacht. Jeder Container muss explizit mit dem Label traefik.enable=true aktiviert werden – eine wichtige Sicherheitsmaßnahme. Das api.dashboard-Feature aktiviert die Traefik-Weboberfläche, die unter einer gesicherten Route erreichbar gemacht werden kann.

Der file-Provider mit watch: true ermöglicht es, eine separate dynamische Konfigurationsdatei zu pflegen, die Traefik bei Änderungen automatisch neu einliest – ohne Neustart.

4. Docker-Provider

Der Docker-Provider ist das Herzstück von Traefik im Container-Betrieb. Traefik verbindet sich mit dem Docker-Daemon über den Unix-Socket und liest Labels von laufenden Containern. Aus diesen Labels generiert Traefik automatisch die entsprechenden Router, Services und Middlewares.

Die wichtigsten Labels im Überblick:

labels:
  # Traefik für diesen Container aktivieren
  - "traefik.enable=true"

  # Router-Regel: Hostname-basiertes Routing
  - "traefik.http.routers.myapp.rule=Host(`app.example.com`)"

  # EntryPoint: HTTPS verwenden
  - "traefik.http.routers.myapp.entrypoints=websecure"

  # TLS aktivieren und certResolver zuweisen
  - "traefik.http.routers.myapp.tls=true"
  - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"

  # Interner Port der Anwendung (erforderlich bei mehreren exponierten Ports)
  - "traefik.http.services.myapp.loadbalancer.server.port=8080"

  # Netzwerk explizit angeben (wichtig bei mehreren Netzwerken)
  - "traefik.docker.network=traefik-net"

Der Name myapp in den Label-Keys ist frei wählbar und dient als eindeutiger Bezeichner innerhalb der Traefik-Konfiguration. Wichtig ist Konsistenz: Alle Labels eines Routers oder Services müssen denselben Bezeichner verwenden. Traefik erkennt, welche Labels zusammengehören, und erzeugt daraus eine vollständige Routing-Konfiguration.

Der Port in loadbalancer.server.port ist der interne Port des Containers – nicht der nach außen gemappte Host-Port. Wenn ein Container nur einen Port exponiert, kann Traefik diesen automatisch ermitteln. Bei mehreren exponierten Ports muss der Port explizit angegeben werden.

Damit Traefik auf Docker-Container zugreifen kann, müssen sich Traefik und die Ziel-Container im selben Docker-Netzwerk befinden. Es empfiehlt sich, ein dediziertes externes Netzwerk zu erstellen:

docker network create traefik-net

5. TLS mit Let's Encrypt

Traefik hat eine native Integration für das ACME-Protokoll (Automatic Certificate Management Environment), über das Let's Encrypt Zertifikate ausgestellt werden. Zertifikate werden automatisch beantragt, gespeichert und vor Ablauf erneuert – ohne externen Certbot oder Cronjobs.

5.1 HTTP-01 Challenge

Die HTTP-01 Challenge ist die einfachste Methode und funktioniert, wenn Port 80 öffentlich erreichbar ist. Let's Encrypt schickt eine Anfrage an eine bekannte URL (/.well-known/acme-challenge/...) auf der zu validierenden Domain, die Traefik temporär beantwortet. Konfiguration in traefik.yml:

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

5.2 DNS-01 Challenge

Die DNS-01 Challenge ist erforderlich für Wildcard-Zertifikate (*.example.com) oder für Server, die nicht über Port 80 aus dem Internet erreichbar sind. Traefik setzt hierfür einen TXT-Record im DNS des Providers. Die Unterstützung ist abhängig vom DNS-Provider und erfordert API-Credentials. Traefik unterstützt zahlreiche DNS-Provider wie Cloudflare, Route53, Hetzner DNS und viele weitere über die integrierte lego-Bibliothek:

certificatesResolvers:
  cloudflare:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

Die Cloudflare-API-Credentials werden als Umgebungsvariablen übergeben: CF_API_EMAIL und CF_API_KEY (oder alternativ CF_DNS_API_TOKEN für scoped API Tokens). Für Wildcard-Zertifikate wird das Label traefik.http.routers.myapp.tls.domains[0].main=example.com und traefik.http.routers.myapp.tls.domains[0].sans=*.example.com gesetzt.

5.3 Interne Domains und selbstsignierte Zertifikate

Für interne Domains (z. B. app.home.lan), die nicht öffentlich erreichbar sind und für die keine Let's Encrypt-Zertifikate ausgestellt werden können, bietet Traefik die Möglichkeit, eine eigene Certificate Authority (CA) zu verwenden. Die CA-Zertifikate und -Schlüssel werden in der dynamischen Konfiguration referenziert:

tls:
  certificates:
    - certFile: /certs/local.crt
      keyFile: /certs/local.key
  stores:
    default:
      defaultCertificate:
        certFile: /certs/local.crt
        keyFile: /certs/local.key

Alternativ generiert Traefik ein selbstsigniertes Zertifikat als Fallback, wenn kein passendes Zertifikat vorhanden ist. Dies sollte in Produktionsumgebungen vermieden werden, da Browser dieses Zertifikat nicht als vertrauenswürdig akzeptieren.

6. Middlewares

Middlewares sind eine der mächtigsten Funktionen von Traefik. Sie erlauben es, Requests und Responses zu transformieren, bevor sie den Service erreichen oder zurückgesendet werden. Middlewares werden in der dynamischen Konfiguration definiert und können Routern per Label oder in der dynamic.yml zugewiesen werden.

6.1 HTTP-zu-HTTPS-Redirect

Der einfachste Weg ist die direkte Redirect-Konfiguration am EntryPoint (wie in Abschnitt 3 gezeigt). Alternativ lässt sich ein Redirect auch als eigenständige Middleware definieren und gezielt zuweisen:

http:
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https
        permanent: true

6.2 Security Headers und HSTS

Security Headers verbessern die Sicherheit der Anwendung erheblich. Mit der Headers-Middleware können HTTP-Response-Header gesetzt werden. HSTS (HTTP Strict Transport Security) weist Browser an, die Domain ausschließlich über HTTPS zu laden:

http:
  middlewares:
    security-headers:
      headers:
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        stsPreload: true
        forceSTSHeader: true
        contentTypeNosniff: true
        browserXssFilter: true
        referrerPolicy: "strict-origin-when-cross-origin"
        permissionsPolicy: "camera=(), microphone=(), geolocation=()"
        customFrameOptionsValue: "SAMEORIGIN"

6.3 BasicAuth

BasicAuth schützt einen Dienst mit Benutzername und Passwort. Das Passwort muss als bcrypt-Hash gespeichert werden. Generierung mit htpasswd aus dem Apache-Paket oder alternativ mit dem Docker-Image httpd:

# Passwort-Hash generieren (Apache htpasswd)
htpasswd -nb admin sicherespasswort

# Alternativ ohne lokale Installation via Docker
docker run --rm httpd htpasswd -nb admin sicherespasswort

# Ergebnis beispielhaft:
# admin:$apr1$xyz123.../hashedvalue
http:
  middlewares:
    auth:
      basicAuth:
        users:
          - "admin:$apr1$xyz123.../hashedvalue"

6.4 RateLimit

Die RateLimit-Middleware begrenzt die Anzahl der Anfragen pro Zeiteinheit und schützt so vor Brute-Force-Angriffen und übermäßiger Last. Der Parameter average definiert die erlaubte Durchschnittsrate pro Sekunde, burst erlaubt kurzzeitige Spitzen:

http:
  middlewares:
    rate-limit:
      rateLimit:
        average: 100
        burst: 50

6.5 StripPrefix

StripPrefix entfernt einen URL-Präfix, bevor die Anfrage an den Backend-Service weitergeleitet wird. Nützlich, wenn eine Anwendung unter einem Pfad erreichbar sein soll, intern aber am Root-Pfad läuft:

http:
  middlewares:
    strip-api:
      stripPrefix:
        prefixes:
          - "/api"

6.6 Middlewares per Label zuweisen

Definierte Middlewares werden Routern über Labels zugewiesen. Mehrere Middlewares können kommagetrennt angegeben werden. Das Suffix gibt an, aus welchem Provider die Middleware stammt – @file für den File-Provider, @docker für Docker-Labels:

labels:
  - "traefik.http.routers.myapp.middlewares=security-headers@file,rate-limit@file"

7. Praxisbeispiel

Das folgende vollständige Praxisbeispiel zeigt ein typisches Heimserver- oder VPS-Setup mit Traefik als Reverse Proxy, Portainer zur Container-Verwaltung und Vaultwarden (ein selbstgehosteter Bitwarden-kompatibler Passwort-Manager) – alle mit automatischem TLS via Let's Encrypt.

7.1 Verzeichnisstruktur

traefik-stack/
├── docker-compose.yml
├── traefik/
│   ├── traefik.yml
│   └── dynamic.yml
└── letsencrypt/
    └── acme.json       # wird automatisch befüllt, Berechtigungen: 600

7.2 traefik/traefik.yml

api:
  dashboard: true
  insecure: false

log:
  level: INFO

accessLog: {}

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik-net
  file:
    filename: /etc/traefik/dynamic.yml
    watch: true

7.3 traefik/dynamic.yml

http:
  middlewares:
    security-headers:
      headers:
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        stsPreload: true
        forceSTSHeader: true
        contentTypeNosniff: true
        browserXssFilter: true
        referrerPolicy: "strict-origin-when-cross-origin"
        customFrameOptionsValue: "SAMEORIGIN"

    auth-dashboard:
      basicAuth:
        users:
          - "admin:$apr1$CHANGEME$hashedpassword"

7.4 docker-compose.yml

version: "3.8"

networks:
  traefik-net:
    external: true

services:

  traefik:
    image: traefik:v3.1
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
      - "traefik.http.routers.traefik-dashboard.tls=true"
      - "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik-dashboard.service=api@internal"
      - "traefik.http.routers.traefik-dashboard.middlewares=auth-dashboard@file,security-headers@file"

  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - portainer_data:/data
    networks:
      - traefik-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=Host(`portainer.example.com`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls=true"
      - "traefik.http.routers.portainer.tls.certresolver=letsencrypt"
      - "traefik.http.routers.portainer.middlewares=security-headers@file"
      - "traefik.http.services.portainer.loadbalancer.server.port=9000"

  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - DOMAIN=https://vault.example.com
      - SIGNUPS_ALLOWED=false
      - ADMIN_TOKEN=CHANGEME_SEHR_SICHER
    volumes:
      - vaultwarden_data:/data
    networks:
      - traefik-net
    labels:
      - "traefik.enable=true"
      # Haupt-Router für Vaultwarden Web-UI
      - "traefik.http.routers.vaultwarden.rule=Host(`vault.example.com`)"
      - "traefik.http.routers.vaultwarden.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden.tls=true"
      - "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
      - "traefik.http.routers.vaultwarden.middlewares=security-headers@file"
      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
      # Separater Router für WebSocket-Notifications (Port 3012)
      - "traefik.http.routers.vaultwarden-ws.rule=Host(`vault.example.com`) && Path(`/notifications/hub`)"
      - "traefik.http.routers.vaultwarden-ws.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden-ws.tls=true"
      - "traefik.http.routers.vaultwarden-ws.tls.certresolver=letsencrypt"
      - "traefik.http.services.vaultwarden-ws.loadbalancer.server.port=3012"

volumes:
  portainer_data:
  vaultwarden_data:

7.5 Deployment

Vor dem ersten Start muss das externe Docker-Netzwerk erstellt und die Berechtigungen der acme.json-Datei korrekt gesetzt werden. Let's Encrypt verweigert das Speichern von Zertifikaten, wenn die Datei für andere Benutzer lesbar ist:

# Externes Netzwerk erstellen
docker network create traefik-net

# acme.json mit korrekten Berechtigungen erstellen
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json

# Stack starten
docker compose up -d

# Logs beobachten
docker compose logs -f traefik

Nach dem Start beobachtet Traefik alle Container im traefik-net-Netzwerk, liest deren Labels und richtet das Routing automatisch ein. Sobald ein DNS-Eintrag für traefik.example.com, portainer.example.com und vault.example.com auf die Server-IP zeigt und die Ports 80 und 443 erreichbar sind, beantragt Traefik automatisch TLS-Zertifikate über Let's Encrypt.

Das Traefik-Dashboard unter https://traefik.example.com zeigt nach dem Login (BasicAuth) eine Übersicht aller aktiven Routers, Services und Middlewares in Echtzeit. Neue Container mit entsprechenden Labels werden sofort ohne Neustart von Traefik registriert – das ist der entscheidende Vorteil gegenüber statisch konfigurierten Reverse Proxies wie Nginx.

Für Produktionsumgebungen empfiehlt es sich zudem, den Docker-Socket nicht direkt in den Traefik-Container zu mounten, sondern einen Socket-Proxy (z. B. tecnativa/docker-socket-proxy) dazwischenzuschalten. Dieser gewährt Traefik nur Lesezugriff auf die Docker-API und reduziert die Angriffsfläche erheblich, falls der Traefik-Container kompromittiert wird.