Traefik als Docker-Reverse-Proxy mit automatischem HTTPS
So richtest du Traefik per Docker Compose als Reverse-Proxy mit automatischem Let's-Encrypt-HTTPS ein: mit copy-paste-fertiger compose.yaml, Docker-Auto-Discovery über Container-Labels, ACME-Resolver (HTTP-01 und DNS-01), abgesichertem Dashboard, Middlewares sowie Update- und Backup-Workflow.

Traefik als Docker-Reverse-Proxy nimmt dir die lästigste Arbeit beim Self-Hosting ab: Statt für jeden neuen Dienst eine Vhost-Datei zu schreiben und Zertifikate von Hand zu verwalten, liest Traefik seine Routing-Konfiguration direkt aus den Labels deiner Container und stellt per Let's Encrypt vollautomatisch TLS-Zertifikate aus und erneuert sie. Diese Anleitung richtet sich an Mittelstands-Admins und ambitionierte Heimserver-Nutzer und zeigt dir Schritt für Schritt den Weg über Docker Compose: vom Projektverzeichnis und der vollständigen compose.yaml über den abgesicherten Dashboard-Zugang und das automatische HTTPS bis zum Veröffentlichen eigener Dienste per Labels sowie einem sauberen Update- und Backup-Workflow.
Voraussetzungen
Bevor du startest, sollten ein paar Grundlagen stehen. Traefik selbst ist extrem genügsam – ein statisches Go-Binary, das CPU-only läuft. Eine GPU brauchst du hier nicht, und auch das nvidia-container-toolkit ist für einen Reverse-Proxy ohne Bedeutung.
- Linux-Server: Debian 12 oder Ubuntu 24.04 (nativer Linux-Host) mit öffentlicher IP.
- Docker & Docker Compose: aktuelle Docker Engine mit dem Compose-Plugin (
docker compose, nicht das altedocker-compose). - RAM & Storage: Traefik begnügt sich mit rund
50–100 MBRAM im Leerlauf und braucht praktisch keinen Plattenplatz – nur ein paar Kilobyte für dieacme.json. - Keine GPU nötig: Traefik ist rein CPU-basiert. Anders als bei LLM-Serving oder Bildgenerierung gibt es hier keinen GPU-Betrieb und kein
nvidia-container-toolkit. - Domain & DNS: eine eigene Domain (z. B.
DEINE-DOMAIN.de) mit A- bzw. AAAA-Records, die auf die öffentliche IP deines Servers zeigen. Für die HTTP-01-Challenge müssen die Ports80und443von außen erreichbar sein. - Hinter CGNAT/ohne offene Ports: dann brauchst du statt HTTP-01 die DNS-01-Challenge (siehe Schritt 6) – sie ist außerdem die einzige Option für Wildcard-Zertifikate.
Schritt 1: Wie Traefik arbeitet – statische vs. dynamische Konfiguration
Traefik trennt zwei Arten von Konfiguration, und dieses Modell zu verstehen erspart dir später viel Sucherei:
- Statische Konfiguration – alles, was beim Start feststeht: Entrypoints (Ports 80/443), aktivierte Provider, ACME-Resolver. Wir setzen sie hier über den
command-Block der compose.yaml (alternativ ginge einetraefik.ymloder Env-Vars). - Dynamische Konfiguration – das eigentliche Routing: welcher Host auf welchen Container zeigt. Die liest Traefik per Docker-Provider live aus den Labels der Container. Neue Dienste werden allein durch das Setzen von Labels veröffentlicht – ganz ohne Neustart von Traefik.
Genau das ist der Unterschied zum klassischen Aufbau: Beim manuellen nginx-Reverse-Proxy mit TLS pflegst du Vhost-Dateien von Hand und erneuerst Zertifikate über certbot. Bei Traefik entfällt beides – die Auto-Discovery übernimmt das Routing, der ACME-Resolver das Zertifikatshandling.
Schritt 2: Projektverzeichnis anlegen und acme.json vorbereiten
Lege ein eigenes Verzeichnis für den Stack an. Darin landen die compose.yaml und der Ordner letsencrypt mit der Zertifikatsdatei. Diese Datei muss vor dem ersten Start existieren und die Rechte 600 haben – sonst verweigert Traefik das Laden, weil die Permissions zu offen sind.
sudo mkdir -p /srv/traefik
cd /srv/traefik
# acme.json korrekt anlegen (sonst Fehler wegen zu offener Rechte)
mkdir -p ./letsencrypt && touch ./letsencrypt/acme.json && chmod 600 ./letsencrypt/acme.json
Die acme.json enthält später die privaten Schlüssel deiner Zertifikate. Behandle sie wie ein Geheimnis: niemals in ein Git-Repository committen und regelmäßig off-site sichern (siehe Schritt 9).
Schritt 3: Die vollständige compose.yaml anlegen
Hier die komplette, lauffähige compose.yaml für den Produktivbetrieb: Traefik mit den Entrypoints web (:80) und websecure (:443), globalem HTTP→HTTPS-Redirect, ACME-Resolver per HTTP-01-Challenge und einem abgesicherten Dashboard. Als Beispieldienst veröffentlichen wir traefik/whoami rein über Labels. Passe email, die Host-Regeln und den BasicAuth-Hash an deine Umgebung an.
services:
traefik:
image: traefik:v3.7 # Minor pinnen statt :latest
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
command:
# --- Dashboard / API ---
- "--api.dashboard=true"
- "--api.insecure=false" # KEIN ungeschuetztes :8080 in Produktion
# --- Docker-Provider (Auto-Discovery) ---
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false" # nur Container mit traefik.enable=true
- "--providers.docker.network=proxy"
# --- Entrypoints ---
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# --- globaler HTTP->HTTPS Redirect ---
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
# --- ACME / Let's Encrypt (HTTP-01) ---
- "--certificatesresolvers.le.acme.email=admin@DEINE-DOMAIN.de"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
# Zum Testen entkommentieren (Staging, verbraucht kein Rate-Limit):
# - "--certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # IMMER read-only
- ./letsencrypt:/letsencrypt
networks:
- proxy
labels:
# --- Dashboard ueber Router absichern (TLS + BasicAuth) ---
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.DEINE-DOMAIN.de`)"
- "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=auth"
# $$ = escaptes $ (Pflicht in docker-compose!), Hash via htpasswd -nB
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$xxxx$$yyyy"
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.DEINE-DOMAIN.de`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=le"
# optional, wenn der Container-Port abweicht:
- "traefik.http.services.whoami.loadbalancer.server.port=80"
networks:
proxy:
name: proxy
Wichtig zum gemeinsamen Netzwerk: Traefik und alle Zieldienste müssen im selben Docker-Netzwerk liegen (hier proxy), sonst landest du bei einem gateway timeout. Mit --providers.docker.network=proxy sagst du Traefik zusätzlich, über welches Netzwerk es die Container ansprechen soll.
Den BasicAuth-Hash für das Dashboard erzeugen
Den Platzhalter admin:$$apr1$$xxxx$$yyyy ersetzt du durch einen echten Hash. Erzeuge ihn mit htpasswd (BCrypt wird empfohlen). In der compose.yaml musst du jedes Dollarzeichen verdoppeln ($$), sonst interpretiert Compose es als Variable und der Login schlägt fehl. Der folgende Befehl übernimmt das Verdoppeln gleich mit:
# BasicAuth-Hash (BCrypt) mit korrekt verdoppelten $ fuer docker-compose:
echo $(htpasswd -nB admin) | sed -e s/\\$/\\$\\$/g
Die Ausgabe (z. B. admin:$$2y$$05$$...) trägst du komplett hinter basicauth.users= ein. Hinweis: Wenn du den Wert stattdessen über eine externe .env-Datei oder per Ansible setzt, darfst du die Dollarzeichen nicht verdoppeln.
Schritt 4: Stack starten und Zertifikate prüfen
Starte den Stack im Hintergrund und kontrolliere die Logs. Achte darauf, dass die DNS-Records für traefik.DEINE-DOMAIN.de und whoami.DEINE-DOMAIN.de bereits auf deinen Server zeigen – sonst kann Let's Encrypt die HTTP-01-Challenge nicht abschließen.
# Stack starten
docker compose up -d
# Status pruefen
docker compose ps
# Logs verfolgen (mit Strg+C beenden)
docker compose logs -f traefik
Beim ersten Aufruf eines per HTTPS veröffentlichten Hosts fordert Traefik automatisch ein Zertifikat an und legt es in der acme.json ab. Rufe danach https://whoami.DEINE-DOMAIN.de auf: Du solltest ein gültiges Zertifikat und die whoami-Ausgabe sehen. Das Dashboard erreichst du unter https://traefik.DEINE-DOMAIN.de nach Eingabe der BasicAuth-Zugangsdaten.
Tipp: Teste zuerst mit dem Staging-caServer (in der compose.yaml entkommentieren). Let's Encrypt hat strenge Rate-Limits, die du bei Trial-and-Error schnell erreichst. Wenn alles läuft, kommentierst du die Staging-Zeile wieder aus, leerst die
acme.json(neu anlegen +chmod 600) und startest neu – sonst bleiben die ungültigen Staging-Zertifikate gespeichert.
Schritt 5: Einen eigenen Dienst per Labels veröffentlichen
Das ist der eigentliche Komfort von Traefik: Einen neuen Dienst veröffentlichst du nur über Labels – kein Eingriff in die Traefik-Konfiguration nötig. Häng den Service einfach ans proxy-Netzwerk und setze die folgenden Labels. Ersetze app durch einen eindeutigen Router-/Service-Namen und app.DEINE-DOMAIN.de durch deine Subdomain.
meine-app:
image: nginx:stable
container_name: meine-app
restart: unless-stopped
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.DEINE-DOMAIN.de`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=le"
# Pflicht, wenn der Container mehrere oder abweichende Ports exponiert:
- "traefik.http.services.app.loadbalancer.server.port=80"
Nach docker compose up -d erkennt Traefik den neuen Container sofort, fordert das Zertifikat an und routet https://app.DEINE-DOMAIN.de dorthin. Die vier zentralen Labels sind immer dieselben:
| Label | Funktion |
|---|---|
traefik.enable=true | Macht den Container für Traefik sichtbar (Pflicht bei exposedbydefault=false). |
...routers.NAME.rule=Host(`...`) | Welche Domain auf diesen Dienst zeigt. Backticks beachten (v3-Syntax). |
...routers.NAME.entrypoints=websecure | Lauscht auf Port 443 (HTTPS). |
...routers.NAME.tls.certresolver=le | Verknüpft mit dem ACME-Resolver – löst die automatische Zertifikatsausstellung aus. |
Middleware: HTTPS-Redirect und BasicAuth
Den globalen HTTP→HTTPS-Redirect haben wir bereits in Schritt 3 am Entrypoint gesetzt – er gilt damit für alle Dienste. Willst du einen einzelnen Dienst zusätzlich mit BasicAuth schützen (wie das Dashboard), definierst und referenzierst du eine Middleware über Labels:
- "traefik.http.routers.app.middlewares=app-auth"
- "traefik.http.middlewares.app-auth.basicauth.users=admin:$$apr1$$xxxx$$yyyy"
Schritt 6: DNS-01-Challenge für Wildcard-Zertifikate (optional)
Die HTTP-01-Challenge aus Schritt 3 verlangt, dass dein Server öffentlich auf Port 80 erreichbar ist. Hinter CGNAT, in abgeschotteten Netzen oder wenn du ein Wildcard-Zertifikat (*.DEINE-DOMAIN.de) brauchst, nutzt du stattdessen die DNS-01-Challenge. Traefik legt dabei kurzzeitig einen TXT-Record per DNS-Provider-API an. Beispiel mit Cloudflare:
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.le.acme.email=admin@DEINE-DOMAIN.de"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.dnschallenge.provider=cloudflare"
environment:
- CF_DNS_API_TOKEN=DEIN-SCOPED-CLOUDFLARE-TOKEN
Den Scoped API Token erstellst du im Cloudflare-Dashboard mit Schreibrechten auf die DNS-Zone. Alternativ unterstützt der Provider CF_API_EMAIL + CF_API_KEY. Alle Varianten akzeptieren das Suffix _FILE für Docker-Secrets, etwa CF_DNS_API_TOKEN_FILE=/run/secrets/cf_token – so taucht das Token nicht im Klartext in der compose.yaml auf.
Für ein Wildcard-Zertifikat setzt du die Domains direkt am Router:
- "traefik.http.routers.app.tls.domains[0].main=DEINE-DOMAIN.de"
- "traefik.http.routers.app.tls.domains[0].sans=*.DEINE-DOMAIN.de"
Schritt 7: Sicherheit – Dashboard, Socket und Defaults
Ein Reverse-Proxy steht per Definition exponiert. Diese vier Punkte solltest du verinnerlichen:
- Dashboard nie ungeschützt:
--api.insecure=trueöffnet das Dashboard ohne Authentifizierung auf Port 8080. Das ist nur für lokale Tests gedacht. In Produktion veröffentlichst du es – wie in Schritt 3 – über einen Router aufservice=api@internalmit TLS und BasicAuth-Middleware. - Docker-Socket read-only: Den Socket immer als
:romounten. Schon Lesezugriff verschafft tiefen Einblick; Schreibzugriff (ohne:ro) ist faktisch Host-Root. Für höhere Anforderungen lohnt ein vorgeschalteter Socket-Proxy. - exposedbydefault=false: Ohne dieses Flag würde Traefik alle Container automatisch routen. Mit
falsemusst du an jedem Dienst bewussttraefik.enable=truesetzen. - no-new-privileges & restart:
security_opt: no-new-privileges:trueundrestart: unless-stoppedsind die von den offiziellen Beispielen empfohlenen Defaults.
Schritt 8: Abgrenzung – Traefik vs. manueller nginx-Reverse-Proxy
Wann nimmst du was? Beide Ansätze haben ihre Berechtigung:
| Aspekt | Traefik (Auto-Discovery) | nginx + certbot (manuell) |
|---|---|---|
| Neuer Dienst | nur Labels setzen, kein Neustart | Vhost-Datei schreiben + reload |
| Zertifikate | vollautomatisch per ACME-Resolver | certbot manuell/per Cron |
| Konfiguration | aus Container-Labels (dynamisch) | statische Konfigdateien |
| Ideal für | viele wechselnde Docker-Dienste | wenige, statische Backends / Nicht-Docker |
Traefik spielt seine Stärke aus, wenn du viele containerisierte Dienste betreibst, die sich oft ändern. Hast du dagegen nur ein, zwei statische Backends oder Dienste außerhalb von Docker, ist der manuelle nginx-Reverse-Proxy in Kombination mit Let's Encrypt per certbot oft übersichtlicher. Die Grundlagen zum Aufbau solcher Stacks findest du in den Docker-Compose-Grundlagen.
Schritt 9: Update- und Backup-Workflow
Updates laufen mit Docker Compose in zwei Schritten: neues Image ziehen, dann Container neu erstellen. Config und Volumes bleiben dabei erhalten.
# Update einspielen
docker compose pull # neues Image (Tag z. B. traefik:v3.7) ziehen
docker compose up -d # Container neu erstellen, acme.json bleibt erhalten
docker image prune -f # alte, ungenutzte Images aufraeumen
Pinne den Image-Tag bewusst auf eine Minor-Version (traefik:v3.7) statt :latest. Der Sprung von v2 auf v3 brachte Breaking Changes (z. B. geänderte Rule-Syntax) – auch ein unkontrollierter Minor-Wechsel kann Konfigs brechen. Aus demselben Grund sind alte v2-Tutorials nicht 1:1 übertragbar; halte dich an die v3-Doku.
Beim Backup zählt vor allem eine Datei: die acme.json mit deinen Zertifikaten und privaten Schlüsseln. Sichere sie zusammen mit der compose.yaml off-site.
# Backup: nur acme.json + Compose-/Config-Dateien sichern
cp ./letsencrypt/acme.json ./backup/acme.json.$(date +%F)
Troubleshooting
- Zertifikat wird nicht ausgestellt: HTTP-01/TLS-ALPN-01 verlangen, dass der Server öffentlich auf Port 80 bzw. 443 erreichbar ist und der DNS-A/AAAA-Record auf den Host zeigt. Hinter CGNAT oder ohne offene Ports schlägt das fehl – dann auf DNS-01 (Schritt 6) umsteigen.
- Traefik lädt acme.json nicht: falsche Rechte. Die Datei muss
600sein. Anlegen mittouch ./letsencrypt/acme.json && chmod 600 ./letsencrypt/acme.json. - Login am Dashboard schlägt fehl: Dollarzeichen im BasicAuth-Hash nicht verdoppelt. In der compose.yaml muss jedes
$als$$stehen – bei externer.env/Ansible dagegen nicht. - gateway timeout / Dienst nicht erreichbar: Traefik und Zielcontainer liegen nicht im selben Docker-Netzwerk. Beide ans
proxy-Netzwerk hängen. Bei mehreren exponierten Ports zusätzlichloadbalancer.server.portexplizit setzen. - Dienst wird gar nicht geroutet: bei
exposedbydefault=falsefehlttraefik.enable=trueam Container. - Rate-Limit von Let's Encrypt erreicht: zu viel Trial-and-Error gegen die Produktion. Erst mit dem Staging-caServer testen, danach umstellen und die
acme.jsonneu anlegen.
Häufige Fragen
Brauche ich für Traefik eine GPU?
Nein. Traefik ist ein leichtgewichtiges Go-Binary und läuft rein auf der CPU mit rund 50–100 MB RAM im Leerlauf. Eine GPU oder das nvidia-container-toolkit sind hier völlig irrelevant – das ist nur für Dienste wie LLM-Serving oder Bildgenerierung Thema.
Was ist der Unterschied zwischen statischer und dynamischer Konfiguration?
Die statische Konfiguration (Entrypoints, Provider, ACME) steht beim Start fest und kommt aus dem command-Block oder einer traefik.yml. Die dynamische Konfiguration ist das Routing und kommt live aus den Container-Labels. Änderst du Labels und startest den Container neu, übernimmt Traefik das ohne eigenen Neustart.
Warum muss der Docker-Socket read-only gemountet werden?
Weil der Socket vollen Zugriff auf die Docker-Engine gibt. Schreibzugriff entspricht praktisch Root-Rechten auf dem Host. Mit :ro kann Traefik nur lesen, was es zum Erkennen der Container braucht. Für noch mehr Trennung kannst du einen Socket-Proxy vorschalten.
HTTP-01 oder DNS-01 – welche Challenge nehme ich?
HTTP-01 ist am einfachsten, wenn dein Server öffentlich auf Port 80 erreichbar ist. DNS-01 brauchst du hinter CGNAT, in abgeschotteten Netzen oder wenn du ein Wildcard-Zertifikat (*.DEINE-DOMAIN.de) willst – sie ist die einzige Option für Wildcards, erfordert aber einen unterstützten DNS-Provider mit API-Token.
Wie sichere ich das Traefik-Dashboard ab?
Setze niemals --api.insecure=true in Produktion. Veröffentliche das Dashboard stattdessen über einen Router auf service=api@internal mit TLS-Zertifikat und einer BasicAuth-Middleware – genau so wie in der compose.yaml in Schritt 3 gezeigt.
Lohnt sich Traefik oder reicht nginx?
Traefik glänzt bei vielen, oft wechselnden Docker-Diensten: neuer Dienst, neue Labels, fertig. Hast du nur wenige statische Backends oder Dienste außerhalb von Docker, ist ein manueller nginx-Reverse-Proxy mit certbot meist übersichtlicher. Beide Ansätze sind valide – es hängt von deiner Landschaft ab.
Fazit
Mit Docker Compose ist Traefik in wenigen Minuten als Reverse-Proxy mit vollautomatischem HTTPS eingerichtet. Der Kern ist eine schlanke compose.yaml mit dem gepinnten Image traefik:v3.7, den Entrypoints 80/443, dem read-only gemounteten Docker-Socket und dem ACME-Resolver. Jeden weiteren Dienst veröffentlichst du danach allein über Container-Labels – ohne Vhost-Dateien, ohne manuelles Zertifikatshandling. Achte auf die drei sicherheitskritischen Punkte: Dashboard nur über Router mit BasicAuth (kein api.insecure), Socket read-only und exposedbydefault=false. Lege die acme.json mit chmod 600 an, pinne den Image-Tag und sichere die acme.json off-site – dann hast du einen wartungsarmen, sicheren Reverse-Proxy, der mit deiner Docker-Landschaft mitwächst.
Weiterführende Anleitungen und Quellen
- Docker Compose Grundlagen: Stacks sauber aufbauen – die Basis für compose.yaml, Volumes, Netzwerke und den Lebenszyklus deiner Container.
- nginx Reverse-Proxy mit TLS einrichten – der manuelle Gegenentwurf zu Traefiks Auto-Discovery, ideal für statische Backends.
- Let's Encrypt mit certbot: TLS-Zertifikate – die Ergänzung, wenn du Zertifikate außerhalb von Traefik verwaltest.
- Home Assistant per Docker einrichten – ein typischer Dienst, den du anschließend über Traefik-Labels per HTTPS veröffentlichst.
- Weitere Anleitungen in der Kategorie Docker.
Quellen: Traefik – Setup on Docker (offizielle Doku), Traefik – ACME / Let's Encrypt Certificate Resolver (v3), Traefik – Exposing Services with Docker und Traefik Releases (GitHub).