Traefik v3 mit Docker auf Debian 13 — Moderner Reverse Proxy

Traefik v3 als Reverse Proxy auf Debian 13 mit Docker einrichten: Automatisches SSL, dynamische Konfiguration und Dashboard. Die moderne Alternative zu Nginx für Container-Setups.

Traefik debian

Traefik v3 als Reverse Proxy auf Debian 13 mit Docker einrichten: Automatisches SSL, dynamische Konfiguration und Dashboard. Die moderne Alternative zu Nginx für Container-Setups.

💡 Hinweis: Dieses Tutorial setzt voraus, dass du einen normalen Benutzer mit sudo-Rechten verwendest — wie im Tutorial Debian 13 Server absichern beschrieben. Außerdem solltest du Docker installiert haben.

Einleitung — Was ist Traefik?

Wenn ihr Docker-Container hostet, kennt ihr das Problem: Jeder Container will einen eigenen Port, SSL-Zertifikate müssen einzeln eingerichtet werden, und bei jedem neuen Service müsst ihr die Reverse-Proxy-Konfiguration von Hand anpassen. Klassische Lösungen wie Nginx funktionieren — aber sie sind statisch.

Traefik ist ein moderner Reverse Proxy, der speziell für Container-Umgebungen gebaut wurde. Er erkennt automatisch neue Docker-Container, holt sich Let’s-Encrypt-Zertifikate automatisch und konfiguriert das Routing automatisch — alles über Docker-Labels. Kein Reload, kein Restart, keine manuelle Config pro Service.

Was Traefik kann

  • Auto-Discovery — Erkennt neue Docker-Container und routet automatisch
  • Automatisches SSL — Let’s Encrypt out of the box, inklusive Wildcard-Zertifikaten via DNS-Challenge
  • Dynamische Konfiguration — Änderungen ohne Restart
  • Web-Dashboard — Übersicht aller Routen, Services und Middlewares
  • Middlewares — Rate-Limiting, Auth, Header-Manipulation, Redirects, IP-Whitelist
  • Multi-Provider — Docker, Kubernetes, Consul, File, HTTP — alles parallel
  • HTTP/3 & TCP/UDP-Routing — Moderne Protokolle nativ unterstützt
  • Metrics & Tracing — Prometheus, Jaeger, OpenTelemetry direkt integriert

Traefik vs. Nginx vs. Caddy

FeatureTraefik v3NginxCaddy
Auto-DiscoveryJa (Docker, K8s)NeinTeilweise
Automatisches SSLJaNur via CertbotJa
Wildcard SSL (DNS)JaNur via CertbotJa
Web-DashboardJaNeinNein
KonfigurationYAML/TOML/Labelsnginx.confCaddyfile
PerformanceSehr gutExzellentSehr gut
LernkurveMittelNiedrigNiedrig
Beste Wahl fürDocker-SetupsBare Metal, hohe LastEinfache Setups

In diesem Guide richten wir Traefik v3 von Grund auf ein — von der Installation über das Dashboard bis zum ersten abgesicherten Service inklusive HTTPS-Redirect, Middlewares und Monitoring.


Voraussetzungen

Bevor wir starten, braucht ihr:

  • Einen Debian 13 (Trixie) Server — Eingerichtet nach unserem Debian 13 Server-Guide
  • Docker & Docker Compose installiert — Wie in unserem Docker-Tutorial beschrieben
  • Eine Domain (z.B. example.com) — für SSL-Zertifikate zwingend nötig
  • Ports 80 und 443 müssen von außen erreichbar sein
  • DNS-Zugriff — A-Records für eure Subdomains
  • Eine E-Mail-Adresse für Let’s Encrypt

💡 Hinweis: Falls ihr bereits Nginx oder einen anderen Reverse Proxy auf Port 80/443 laufen habt, müsst ihr diesen vorher stoppen. Zwei Reverse Proxies auf den gleichen Ports gehen nicht.

Firewall-Ports öffnen

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

DNS vorbereiten

Erstellt für jeden Service, den ihr später hinter Traefik betreibt, einen A-Record. Für den Anfang reicht ein Wildcard:

Typ:   A
Name:  *
Wert:  EURE-SERVER-IP
TTL:   3600

Typ:   A
Name:  @
Wert:  EURE-SERVER-IP
TTL:   3600

Wartet ein paar Minuten und prüft die Auflösung:

dig +short traefik.example.com
dig +short whoami.example.com

Traefik installieren

Wir installieren Traefik mit Docker Compose und einer ausgelagerten Konfigurationsdatei. Das ist der sauberste Weg und macht Updates trivial.

Schritt 1: Verzeichnisstruktur anlegen

sudo mkdir -p /opt/traefik/{config,certs,logs}
cd /opt/traefik
sudo touch certs/acme.json
sudo chmod 600 certs/acme.json

Die acme.json speichert eure SSL-Zertifikate. Sie muss chmod 600 haben — sonst weigert sich Traefik zu starten.

Schritt 2: Docker-Netzwerk erstellen

Damit Traefik mit anderen Containern reden kann, brauchen wir ein gemeinsames Docker-Netzwerk:

sudo docker network create traefik-public

Alle Services, die Traefik routen soll, müssen später in diesem Netzwerk sein.

Schritt 3: Statische Konfiguration (traefik.yml)

sudo nano /opt/traefik/config/traefik.yml

Inhalt:

# Globale Einstellungen
global:
  checkNewVersion: true
  sendAnonymousUsage: false

# Logs
log:
  level: INFO
  filePath: /logs/traefik.log

accessLog:
  filePath: /logs/access.log
  bufferingSize: 100

# API & Dashboard
api:
  dashboard: true
  insecure: false

# Entry Points (eure öffentlichen Ports)
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true

  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

# Provider — wo Traefik nach Konfiguration sucht
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik-public

  file:
    directory: /config
    watch: true

# SSL-Zertifikate via Let's Encrypt
certificatesResolvers:
  letsencrypt:
    acme:
      email: euer@example.com
      storage: /certs/acme.json
      httpChallenge:
        entryPoint: web

⚠️ Wichtig: Tauscht euer@example.com gegen eure echte E-Mail-Adresse. Let’s Encrypt schickt euch dort Warnungen, falls Zertifikate nicht erneuert werden können.

Schritt 4: Dynamische Konfiguration (dynamic.yml)

Diese Datei enthält Middlewares, die wir gleich für die Absicherung nutzen.

sudo nano /opt/traefik/config/dynamic.yml

Inhalt:

http:
  middlewares:
    # Sicherheits-Header für alle Services
    secure-headers:
      headers:
        frameDeny: true
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "SAMEORIGIN"
        referrerPolicy: "strict-origin-when-cross-origin"

    # Rate-Limiting (100 req/sec, burst 50)
    rate-limit:
      rateLimit:
        average: 100
        burst: 50

    # Basic Auth fürs Dashboard (Hash gleich generieren)
    dashboard-auth:
      basicAuth:
        users:
          - "HIER_KOMMT_GLEICH_DER_HASH_REIN"

# TLS-Optionen — moderne Cipher Suites only
tls:
  options:
    default:
      minVersion: VersionTLS12
      sniStrict: true
      cipherSuites:
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

Schritt 5: Basic-Auth-Hash für das Dashboard generieren

Das Dashboard sollte nicht öffentlich erreichbar sein. Wir schützen es mit Basic Auth:

sudo apt install -y apache2-utils
htpasswd -nb admin EUER_PASSWORT

Die Ausgabe sieht so aus: admin:$apr1$xyz...$abc...

Wichtig: In Docker-Compose und YAML-Files muss jedes $ zu $$ verdoppelt werden. In der dynamic.yml (die Traefik direkt liest) reicht ein $. Tauscht HIER_KOMMT_GLEICH_DER_HASH_REIN durch euren Hash:

sudo nano /opt/traefik/config/dynamic.yml

Schritt 6: docker-compose.yml erstellen

sudo nano /opt/traefik/docker-compose.yml

Inhalt:

services:
  traefik:
    image: traefik:v3.2
    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
      - ./config/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./config:/config:ro
      - ./certs:/certs
      - ./logs:/logs
    networks:
      - traefik-public
    labels:
      # Aktiviere Traefik für sich selbst
      - "traefik.enable=true"

      # Dashboard-Router
      - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.service=api@internal"

      # Middlewares: Auth + Sicherheits-Header
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth@file,secure-headers@file"

networks:
  traefik-public:
    external: true

💡 Hinweis: Tauscht traefik.example.com durch eure echte Subdomain. Der DNS-A-Record dafür muss bereits existieren, sonst kann Let’s Encrypt das Zertifikat nicht ausstellen.

Schritt 7: Traefik starten

cd /opt/traefik
sudo docker compose up -d

Der erste Start dauert 10-30 Sekunden. Den Fortschritt mitlesen:

sudo docker compose logs -f

Bei Configuration loaded from file und Server now listening ist Traefik bereit.

Schritt 8: Dashboard aufrufen

Öffnet im Browser:

https://traefik.example.com

Ihr werdet nach Basic-Auth-Credentials gefragt — gebt die Daten ein, die ihr beim htpasswd-Schritt vergeben habt. Wenn das Dashboard erscheint, ist Traefik komplett einsatzbereit.

Geschafft! Traefik läuft, das Dashboard ist erreichbar, das SSL-Zertifikat wurde automatisch geholt. Jetzt kommen die ersten Services dahinter.


Ersten Service hinter Traefik betreiben

Als Beispiel deployen wir whoami — einen winzigen Container, der HTTP-Request-Details zurückgibt. Perfekt zum Testen.

Schritt 1: Compose-Datei erstellen

sudo mkdir -p /opt/whoami
sudo nano /opt/whoami/docker-compose.yml

Inhalt:

services:
  whoami:
    image: traefik/whoami
    container_name: whoami
    restart: unless-stopped
    networks:
      - traefik-public
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls=true"
      - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
      - "traefik.http.routers.whoami.middlewares=secure-headers@file"
      - "traefik.http.services.whoami.loadbalancer.server.port=80"

networks:
  traefik-public:
    external: true

Schritt 2: Starten und testen

cd /opt/whoami
sudo docker compose up -d

Im Browser:

https://whoami.example.com

Ihr seht die HTTP-Header der Anfrage — und das Schloss-Symbol bestätigt das gültige SSL-Zertifikat.

💡 Tipp: Das ist das Grundmuster für jeden Service hinter Traefik: Container ins traefik-public-Netzwerk hängen, Labels setzen, fertig. Keine Reverse-Proxy-Config zu schreiben, kein Reload nötig.


Wichtige Traefik-Labels verstehen

Die Labels sind das Herzstück von Traefik. Hier die wichtigsten erklärt:

LabelFunktion
traefik.enable=trueContainer überhaupt durch Traefik routen
traefik.http.routers.NAME.rule=Host(...)Welche Domain auf diesen Service zeigt
traefik.http.routers.NAME.entrypoints=websecureHTTPS-Entry-Point
traefik.http.routers.NAME.tls=trueSSL aktivieren
traefik.http.routers.NAME.tls.certresolver=letsencryptSSL-Zertifikat von Let’s Encrypt
traefik.http.services.NAME.loadbalancer.server.port=8080Auf welchem Port der Container lauscht
traefik.http.routers.NAME.middlewares=...Welche Middlewares angewendet werden

Komplexere Routing-Regeln sind möglich:

# Mehrere Domains
- "traefik.http.routers.app.rule=Host(`app.example.com`) || Host(`www.app.example.com`)"

# Nur ein bestimmter Pfad
- "traefik.http.routers.api.rule=Host(`example.com`) && PathPrefix(`/api`)"

# Bestimmter Header
- "traefik.http.routers.beta.rule=Host(`example.com`) && Headers(`X-Beta`, `true`)"

Wildcard-SSL via DNS-Challenge

Die HTTP-Challenge (Standard) braucht für jede Subdomain ein eigenes Zertifikat. Bei vielen Services ist das umständlich. Mit der DNS-Challenge holt ihr ein Wildcard-Zertifikat (*.example.com) — gilt für alle Subdomains gleichzeitig.

Voraussetzung: Ihr nutzt einen unterstützten DNS-Provider mit API. Wir zeigen das Beispiel für Cloudflare — eine vollständige Liste findet ihr in der Traefik-Dokumentation.

Schritt 1: Cloudflare-API-Token erstellen

Im Cloudflare-Dashboard:

  1. My Profile → API Tokens → Create Token
  2. Template: Edit zone DNS
  3. Permissions: Zone — DNS — Edit, Zone — Zone — Read
  4. Zone Resources: Eure Domain
  5. Token erstellen und sicher kopieren

Schritt 2: Token in Traefik eintragen

Erweitert die docker-compose.yml:

services:
  traefik:
    # ... vorhandene Config ...
    environment:
      - CF_DNS_API_TOKEN=EUER_CLOUDFLARE_TOKEN

Schritt 3: traefik.yml anpassen

Ergänzt einen zweiten Resolver für die DNS-Challenge:

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

  cloudflare:
    acme:
      email: euer@example.com
      storage: /certs/acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

Schritt 4: Wildcard-Zertifikat anfordern

In den Labels eines Services:

labels:
  - "traefik.http.routers.app.tls.certresolver=cloudflare"
  - "traefik.http.routers.app.tls.domains[0].main=example.com"
  - "traefik.http.routers.app.tls.domains[0].sans=*.example.com"

Traefik holt jetzt ein einzelnes Zertifikat, das für alle Subdomains gilt — und erneuert es automatisch.

💡 Tipp: Wildcard-Zertifikate sind perfekt für Homelabs mit vielen Services, sparen Rate-Limit-Anfragen bei Let’s Encrypt und ermöglichen Subdomains für Services, die von außen gar nicht erreichbar sein sollen.


Middlewares — Services absichern

Middlewares sind Filter, die Anfragen vor dem Service verarbeiten. Hier die wichtigsten Patterns:

IP-Whitelist

Nur bestimmte IPs dürfen zugreifen — perfekt für Admin-Panels.

In dynamic.yml:

http:
  middlewares:
    admin-only:
      ipAllowList:
        sourceRange:
          - "192.168.1.0/24"
          - "10.0.0.0/8"
          - "EURE_HEIM_IP/32"

In den Labels:

- "traefik.http.routers.admin.middlewares=admin-only@file,secure-headers@file"

Basic Auth

Wie schon beim Dashboard genutzt — funktioniert für jeden Service:

http:
  middlewares:
    portainer-auth:
      basicAuth:
        users:
          - "admin:$apr1$xyz...$abc..."

Rate-Limiting pro Client-IP

http:
  middlewares:
    api-rate-limit:
      rateLimit:
        average: 50
        period: 1s
        burst: 20
        sourceCriterion:
          ipStrategy:
            depth: 1

Authentik Forward-Auth

Wenn ihr unser Authentik-Tutorial befolgt habt, könnt ihr Authentik als Forward-Auth-Middleware nutzen:

http:
  middlewares:
    authentik:
      forwardAuth:
        address: http://authentik-server:9000/outpost.goauthentik.io/auth/traefik
        trustForwardHeader: true
        authResponseHeaders:
          - X-authentik-username
          - X-authentik-groups
          - X-authentik-email
          - X-authentik-name
          - X-authentik-uid

In den Labels:

- "traefik.http.routers.app.middlewares=authentik@file,secure-headers@file"

Jetzt fragt Traefik bei jedem Request Authentik, ob der User eingeloggt ist.


Logs & Monitoring

Traefik schreibt zwei Log-Dateien:

  • traefik.log — Server-Logs (Errors, Config-Changes, ACME-Events)
  • access.log — Jeder eingehende Request

Logs ansehen

# Live mitlesen
sudo tail -f /opt/traefik/logs/traefik.log
sudo tail -f /opt/traefik/logs/access.log

# Im Container
sudo docker compose logs -f traefik

Access-Log als JSON

Für Auswertung mit Tools wie Grafana Loki ist JSON praktischer als das Standard-Format:

accessLog:
  filePath: /logs/access.log
  format: json
  fields:
    headers:
      defaultMode: keep
      names:
        Authorization: drop

Prometheus-Metrics

Traefik kann Metriken im Prometheus-Format exportieren:

metrics:
  prometheus:
    addEntryPointsLabels: true
    addRoutersLabels: true
    addServicesLabels: true

Die Metrics liegen dann unter http://traefik:8080/metrics — perfekt für ein späteres Grafana-Setup.


Backups

Traefik selbst hat keinen Datenbank-State, aber zwei Dinge müssen gesichert werden:

Was muss gesichert werden?

  • /opt/traefik/config/ — Statische und dynamische Konfiguration
  • /opt/traefik/certs/acme.jsonEure SSL-Zertifikate
  • /opt/traefik/docker-compose.yml

Automatisches Backup-Script

sudo nano /usr/local/bin/traefik-backup.sh

Inhalt:

#!/bin/bash
set -e

BACKUP_DIR="/var/backups/traefik"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
RETENTION_DAYS=14

mkdir -p "$BACKUP_DIR"

tar -czf "$BACKUP_DIR/traefik-$TIMESTAMP.tar.gz" \
  -C /opt/traefik config certs docker-compose.yml

find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete

echo "Backup completed: $TIMESTAMP"

Ausführbar machen und mit systemd Timer automatisieren — wie in unserem restic Backup-Tutorial beschrieben:

sudo chmod +x /usr/local/bin/traefik-backup.sh
sudo /usr/local/bin/traefik-backup.sh

⚠️ Best Practice: Die acme.json enthält eure privaten SSL-Schlüssel. Speichert Backups nur an sicheren Orten und verschlüsselt sie für Off-Site-Storage.


Updates

Traefik veröffentlicht regelmäßig Updates. Major-Versionen können Breaking Changes haben — also Release-Notes lesen.

Update durchführen

cd /opt/traefik

# Image-Tag in docker-compose.yml anpassen (z.B. von v3.2 auf v3.3)
sudo nano docker-compose.yml

# Backup vor dem Update!
sudo /usr/local/bin/traefik-backup.sh

# Update
sudo docker compose pull
sudo docker compose up -d

Während des Restarts sind eure Services kurz nicht erreichbar (5-10 Sekunden).

💡 Empfehlung: Pinnt Traefik immer auf eine Major.Minor-Version (z.B. traefik:v3.2), nicht auf latest. Major-Updates wie v2 → v3 brachten Breaking Changes in der Konfiguration.


Zusammenfassung & Checkliste

Hier eine Checkliste, damit ihr nichts vergesst:

SchrittStatus
Debian 13 Server eingerichtet
Docker installiert
Domain & DNS-Records (A & Wildcard) konfiguriert
Verzeichnisstruktur unter /opt/traefik
acme.json mit chmod 600 erstellt
Docker-Netzwerk traefik-public erstellt
traefik.yml mit Let’s Encrypt konfiguriert
dynamic.yml mit Middlewares erstellt
Basic-Auth-Hash für Dashboard generiert
Traefik-Container gestartet
Dashboard unter eurer Domain erreichbar
SSL-Zertifikat erfolgreich ausgestellt
whoami-Test-Service deployed und erreichbar
Backup-Script eingerichtet

Troubleshooting

Traefik startet nicht

cd /opt/traefik
sudo docker compose logs -f

Häufigste Ursachen:

  • acme.json hat nicht chmod 600
  • traefik.yml enthält Syntax-Fehler (YAML ist whitespace-sensitiv!)
  • Port 80/443 schon belegt → sudo ss -tlnp | grep -E '(:80|:443)'

SSL-Zertifikat wird nicht ausgestellt

  • DNS prüfen: dig +short euredomain.com — Zeigt es auf eure Server-IP?
  • Port 80 muss von außen erreichbar sein (HTTP-Challenge braucht das)
  • Let’s Encrypt Rate-Limit erreicht? → 50 Zertifikate / Domain / Woche
  • In Traefik-Logs nach acme suchen: sudo docker compose logs traefik | grep -i acme

Service erreichbar, aber kein SSL

  • Hat der Container traefik.enable=true?
  • Ist der Container im traefik-public-Netzwerk?
  • Im Dashboard prüfen: Wird der Router angezeigt? Hat er einen TLS-Eintrag?

„Gateway Timeout“ oder „Bad Gateway“

  • Stimmt der Port in loadbalancer.server.port?
  • Lauscht die App auf 0.0.0.0 (nicht nur localhost)?
  • Gleiches Docker-Netzwerk wie Traefik?

Dashboard nicht erreichbar

  • Subdomain in Host(...) korrekt?
  • DNS-A-Record für traefik.example.com existiert?
  • Basic-Auth-Hash korrekt eingetragen? (Vorsicht mit $ vs. $$)

„too many redirects“

  • Cloudflare SSL-Modus auf Full (strict) stellen, nicht auf Flexible
  • Im HTTPS-Redirect schauen: ist permanent: true aktiv?

Nächste Schritte

Traefik läuft — und jetzt? Hier sind sinnvolle nächste Schritte:

  • Wildcard-SSL via DNS-Challenge — Vertiefendes Tutorial
  • Authentik als Forward-Auth integrieren — SSO für alle Services hinter Traefik
  • Crowdsec-Bouncer — Brute-Force und Bot-Schutz auf Layer 7
  • Prometheus + Grafana — Visualisierung aller Traefik-Metrics
  • Multi-Server-Setup — Mehrere Traefik-Instanzen mit gemeinsamem Cluster-Storage
  • TCP/UDP-Routing — Auch Datenbanken und Game-Server hinter Traefik betreiben

Traefik ist das Herzstück eines modernen Container-Setups. Die offizielle Dokumentation ist sehr umfangreich und immer aktuell.

Habt ihr Fragen oder Probleme? Schreibt es in die Kommentare — ich helfe gerne!

Kommentar hinterlassen