Systemvoraussetzungen
| Komponente | Anforderung | Hinweis |
|---|---|---|
| Traefik | v3.7 (aktuelle stabile 3.x-Reihe, konkret v3.7.11, Stand August 2026) | Docker-Image traefik:v3.7; die ältere 2.x-Linie erhält zwar noch Sicherheits-Patches, für neue Setups sollte aber v3 verwendet werden |
| Docker Engine | Aktuelle Version mit Compose-Plugin V2 | Der eigenständige docker-compose-1.x-Befehl ist veraltet; das Schlüsselwort "version:" am Kopf der Compose-Datei wird von Compose V2 ignoriert und sollte weggelassen werden |
| Host-Betriebssystem | Linux-Server mit laufendem Docker-Daemon | Traefik selbst ist ein einzelnes, schlankes Binary; der Ressourcenbedarf hängt kaum von der Zahl der Tenants ab, wohl aber der Bedarf der gehosteten Container selbst |
| DNS | Ein A- oder AAAA-Eintrag je Tenant-Subdomain auf die öffentliche IP des Hosts, alternativ ein Wildcard-Eintrag (ein Eintrag mit Sternchen, der auf einen Schlag alle Subdomains einer Domain abdeckt) | Ein Wildcard-Eintrag verlangt den Nachweis der Domaininhaberschaft über einen DNS-Eintrag (die DNS-01-Challenge) statt über eine kurzzeitig abrufbare Datei auf Port 80 (die einfachere HTTP-01-Challenge) |
| Firewall | Ports 80/tcp und 443/tcp öffentlich erreichbar | Port 80 muss bei der HTTP-01-Challenge dauerhaft offen bleiben, auch wenn Traefik HTTP-Anfragen sonst auf HTTPS umleitet |
| Speicher für Zertifikate | Verzeichnis für die Datei acme.json mit Dateirechten 600 | Traefik verweigert bei zu offenen Rechten den Start des ACME-Resolvers, des Moduls, das Zertifikate automatisiert über das ACME-Protokoll bei der Zertifizierungsstelle beantragt |
| E-Mail-Adresse | Gültige Adresse für das ACME-Konto | Let's Encrypt nutzt sie unter anderem für Warnungen vor Ablauf oder Widerruf |
Systemarchitektur
flowchart TD
Client[Browser eines Kunden] --> DNSLookup[DNS Aufloesung kunde-a Subdomain]
DNSLookup --> Traefik[Traefik Container Port 80 und 443]
Traefik --> SocketProxy[Docker Socket oder Docker Socket Proxy]
SocketProxy --> LabelScan[Traefik liest Labels aller laufenden Container]
LabelScan --> RuleMatch{Host Regel passt}
RuleMatch -->|kunde-a Domain| ContainerA[Tenant Container A Port 80]
RuleMatch -->|kunde-b Domain| ContainerB[Tenant Container B Port 3000]
RuleMatch -->|kunde-c Domain| ContainerC[Tenant Container C Port 8080]
Traefik --> ACMEResolver[ACME Resolver fuer Zertifikate]
ACMEResolver --> CertStore[acme.json Zertifikatsspeicher]
ACMEResolver --> CA[Zertifizierungsstelle]
Ein Kunde ruft seine Subdomain im Browser auf. Die DNS-Aufloesung zeigt auf die öffentliche IP des einen Docker-Hosts, nicht auf einen eigenen Server pro Kunde. Dort nimmt Traefik als Reverse Proxy, also als vorgeschaltete Vermittlungsstelle zwischen dem offenen Internet und den einzelnen Kunden-Containern, die Verbindung auf Port 80 oder 443 entgegen und terminiert TLS, die Verschlüsselung hinter dem Schlosssymbol im Browser, noch bevor die Anfrage weitergeht. Für die Entscheidung, welcher Container die Anfrage bekommt, fragt Traefik laufend die Docker-Engine nach allen Containern mit dem Label traefik.enable=true ab und liest deren Router-Regeln aus den übrigen Labels. Passt der Host-Header, das Feld in der HTTP-Anfrage, das den aufgerufenen Domainnamen trägt, zu einer Regel, leitet Traefik intern über das gemeinsame Docker-Netzwerk an genau den Container weiter, dessen Service-Label den passenden Port nennt.
Die Zertifikatsbeschaffung läuft parallel dazu, ausgelöst durch den ersten Request für einen noch unbekannten Hostnamen. Traefik prüft dann, ob für diesen Hostnamen bereits ein gültiges Zertifikat im lokalen Speicher acme.json liegt. Fehlt es, stellt der eingebaute ACME-Resolver eine Anfrage an die Zertifizierungsstelle, löst die Challenge, also den Nachweis, dass die anfragende Partei tatsächlich die Kontrolle über die Domain hat (meist über eine kurzzeitig abrufbare Datei auf Port 80), und legt das Ergebnis in acme.json ab. Ab diesem Moment bedient Traefik den Hostnamen ohne weitere Wartezeit, bis rund einen Monat vor Ablauf automatisch erneuert wird. Für eine Agentur mit vielen Kunden bedeutet das: Ein einzelner Traefik-Container ersetzt eine ganze Reihe von Handgriffen, die früher pro Kunde separat anfielen, vom Anlegen des vhost-Blocks, dem Konfigurationsabschnitt für genau eine Domain in einer klassischen nginx-Datei, über den Lauf eines separaten Zertifikats-Werkzeugs wie Certbot bis zum Neuladen von nginx per Hand.
Schritt-für-Schritt-Umsetzung
Schritt 1: Docker-Netzwerk und Grundgerüst anlegen
Alle Container, die Traefik erreichen soll, brauchen ein gemeinsames Docker-Netzwerk. Ohne dieses Netzwerk muss Traefik raten, über welche Netzwerkschnittstelle es einen Container erreicht, und genau dieses Raten ist eine der häufigsten Ursachen für spätere 502-Fehler.
networks:
proxy:
name: proxy
Schritt 2: Traefik-Service mit ACME-Resolver konfigurieren
Der Traefik-Container selbst bekommt keine Routing-Labels, sondern eine statische Konfiguration über command-Flags: welche Ports er als Entrypoints, also als Netzwerk-Eingänge für ankommenden Datenverkehr, öffnet, dass er Docker als Provider nutzt, und mit welchem ACME-Resolver er Zertifikate holt.
services:
traefik:
image: traefik:v3.7
container_name: traefik
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=proxy
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --certificatesresolvers.leresolver.acme.email=hosting@agentur-beispiel.de
- --certificatesresolvers.leresolver.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.leresolver.acme.httpchallenge=true
- --certificatesresolvers.leresolver.acme.httpchallenge.entrypoint=web
- --api.dashboard=false
- --log.level=INFO
ports:
- "80:80"
- "443:443"
networks:
- proxy
volumes:
- ./letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
- traefik.enable=false
Der Docker-Socket im letzten Volume-Eintrag, die lokale Schnittstelle, über die Programme auf demselben Host mit der Docker-Engine sprechen, gibt Traefik vollen Lesezugriff auf alle laufenden Container, technisch sogar mehr als nur lesend, denn der Zugriff auf den Socket lässt sich nicht sauber auf reine Lesevorgänge beschränken. Für einen produktiven Multi-Tenant-Host, auf dem ein kompromittierter Traefik-Container sonst Zugriff auf sämtliche anderen Kunden-Container hätte, lohnt sich stattdessen ein vorgeschalteter Docker-Socket-Proxy, der nur die für Traefik nötigen, lesenden API-Endpunkte durchlässt und den Rest blockiert.
Schritt 3: Tenant-Container mit Routing-Labels ausstatten
Jeder Kunden-Container bekommt sein eigenes Set an Labels, unabhängig davon, welches Image oder welchen internen Port er nutzt. Das folgende Beispiel zeigt drei unterschiedliche Tenants: eine statische Website, eine Node-Anwendung und eine Anwendung, die mehrere Ports gleichzeitig offenlegt.
kunde-a-website:
image: nginx:1.27-alpine
container_name: kunde-a-website
restart: unless-stopped
networks:
- proxy
volumes:
- ./sites/kunde-a:/usr/share/nginx/html:ro
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.kunde-a.rule=Host(`kunde-a.agentur-beispiel.de`)
- traefik.http.routers.kunde-a.entrypoints=websecure
- traefik.http.routers.kunde-a.tls.certresolver=leresolver
- traefik.http.services.kunde-a.loadbalancer.server.port=80
kunde-b-shop:
image: registry.agentur-beispiel.de/kunde-b/shop:2026-08-15
container_name: kunde-b-shop
restart: unless-stopped
networks:
- proxy
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.kunde-b.rule=Host(`shop.kunde-b.de`)
- traefik.http.routers.kunde-b.entrypoints=websecure
- traefik.http.routers.kunde-b.tls.certresolver=leresolver
- traefik.http.services.kunde-b.loadbalancer.server.port=3000
kunde-c-portal:
image: registry.agentur-beispiel.de/kunde-c/portal:2026-07-30
container_name: kunde-c-portal
restart: unless-stopped
networks:
- proxy
expose:
- "8080"
- "9090"
labels:
- traefik.enable=true
- traefik.docker.network=proxy
- traefik.http.routers.kunde-c.rule=Host(`portal.kunde-c.de`)
- traefik.http.routers.kunde-c.entrypoints=websecure
- traefik.http.routers.kunde-c.tls.certresolver=leresolver
- traefik.http.services.kunde-c.loadbalancer.server.port=8080
Auffällig ist kunde-c-portal: Der Container legt zwei Ports offen, 8080 für die eigentliche Anwendung und 9090 für einen Metriken-Endpunkt. Ohne das Label loadbalancer.server.port würde Traefik nach eigener Dokumentation den ersten von der Docker-Engine gemeldeten Port verwenden, und welcher Port das ist, hängt von der Deklarationsreihenfolge im Image ab, nicht von dem, was der Betreiber eigentlich meint. Das explizite Port-Label macht diese Entscheidung eindeutig und unabhängig vom Image-Innenleben.
Der Unterschied zur nginx-vhost-Welt liegt genau hier: Jede neue Zeile Konfiguration lebt direkt neben dem Container, den sie betrifft, in derselben Compose-Datei oder demselben Stack, den auch das Kunden-Team versioniert. Es gibt keine zentrale vhost-Datei mehr, die ein Infra-Ingenieur bei jedem neuen Kunden von Hand um einen weiteren Server-Block ergänzt, testet und per nginx -s reload aktiv schaltet. Bei zwei oder drei Kunden ist dieser Unterschied nebensächlich. Bei fünfzig Kunden ist er der Unterschied zwischen einem Tages- und einem Wochenaufwand für Onboarding.
Schritt 4: TLS-Ausstellung verifizieren
Nach dem Start mit docker compose up -d lohnt sich ein Blick in die Logs, bevor der Kunde die Seite selbst testet.
docker compose logs -f traefik | grep -i acme
Eine erfolgreiche Ausstellung meldet sich mit einer Zeile in der Art "Certificate obtained for domains" im Traefik-Log. Anschließend lässt sich das ausgestellte Zertifikat direkt von außen prüfen, ohne dem Browser zu vertrauen:
openssl s_client -connect kunde-a.agentur-beispiel.de:443 \
-servername kunde-a.agentur-beispiel.de </dev/null 2>/dev/null \
| openssl x509 -noout -issuer -dates
Zeigt die Ausgabe den erwarteten Issuer und ein Ablaufdatum rund 90 Tage in der Zukunft, war die Ausstellung erfolgreich. Ein zusätzlicher curl-Aufruf gegen die HTTPS-Adresse bestätigt, dass auch der dahinterliegende Tenant-Container antwortet und nicht nur das Zertifikat existiert.
Skalierung von fünf auf fünfzig Tenants
Bei fünf Tenant-Containern auf einem Host merkt man von alledem wenig. Traefik fragt die Docker-Engine in kurzen Abständen nach Änderungen ab, parst ein paar Dutzend Labels und hält die Router-Tabelle im Speicher, das kostet kaum messbare Zeit. Bei fünfzig Containern bleibt dieser Teil weiterhin unauffällig, Traefik wird in der Praxis mit deutlich mehr Routern pro Instanz betrieben, ohne dass die Label-Auswertung selbst zum Engpass wird.
Was sich tatsächlich ändert, sind drei andere Dinge. Erstens die Zertifikatslast: Maßgeblich für das Limit der Zertifizierungsstelle ist nicht die einzelne Subdomain, sondern die registrierte Domain, in der Regel der Teil des Namens direkt vor der länderspezifischen Endung, bei kunde-a.agentur-beispiel.de also agentur-beispiel.de. Solange jeder Kunde seine eigene, bei einem anderen Registrar verwaltete Domain mitbringt, zählt jede dieser Domains getrennt gegen ihr eigenes wöchentliches Limit. Bündelt eine Agentur dagegen viele Kunden unter Subdomains der eigenen Domain, etwa kunde-a.agentur-beispiel.de bis kunde-fuenfzig.agentur-beispiel.de, teilen sich alle diese Subdomains ein gemeinsames Kontingent von aktuell 50 neu ausgestellten Zertifikaten pro Woche für agentur-beispiel.de. Bei organischem Wachstum ist das kein Problem, bei einer Massenmigration vieler Bestandskunden an einem Tag kann es eng werden; in diesem Fall lohnt sich ein Wildcard-Zertifikat über eine DNS-01-Challenge, das alle Subdomains auf einmal abdeckt und pro neuem Kunden gar keine neue Zertifikatsanfrage mehr auslöst.
Zweitens die Docker-Socket-Exposition: Mit fünf vertrauenswürdigen internen Test-Containern mag der direkte Socket-Zugriff tragbar sein. Mit fünfzig Kunden-Images aus unterschiedlichen, teils fremdverwalteten Registries steigt das Risiko, dass ein kompromittierter Container über den Socket Informationen über alle anderen Tenants abgreift. Der Docker-Socket-Proxy, der in Schritt 2 als Option genannt wurde, wird an diesem Punkt vom Nice-to-have zur Grundvoraussetzung.
Drittens die Betriebsressourcen: Traefik selbst bleibt schlank, aber die fünfzig Anwendungen dahinter tun es in der Regel nicht. Auf einem einzelnen Host konkurrieren sie um CPU, Arbeitsspeicher und Festplatten-I/O, unabhängig vom Reverse Proxy. Praktisch bedeutet das, pro Kunden-Container Ressourcengrenzen (mem_limit, cpus) zu setzen, damit ein einzelner überlasteter Tenant nicht die anderneunundvierzig mit in die Knie zieht, und Logs zentral zu sammeln, weil fünfzig verstreute Container-Logs auf der Kommandozeile nicht mehr überschaubar sind. Das ist kein Traefik-spezifisches Problem, sondern gilt für jeden Single-Host-Betrieb, der über eine Handvoll Container hinauswächst; Traefik selbst verändert an dieser Dynamik nichts.
Häufige Fehlerquellen
- 502 Bad Gateway durch fehlendes oder falsches Port-Label: Legt ein Container mehrere Ports offen und fehlt das Label traefik.http.services.<name>.loadbalancer.server.port, wählt Traefik selbst einen Port aus, und dieser muss nicht der gewünschte sein. Im Log erscheint dann typischerweise eine Zeile wie '502 Bad Gateway' caused by: dial tcp 172.19.0.5:9090: connect: connection refused, weil Traefik auf einen Port zugreift, hinter dem gar kein HTTP-Dienst lauscht. Die Lösung ist, den Port-Label explizit zu setzen, wie im Beispiel von kunde-c-portal geschehen.
- 502 Bad Gateway durch mehrere Docker-Netzwerke am selben Container: Hängt ein Tenant-Container an mehreren Docker-Netzwerken, kann Traefik beim Auflösen der internen IP-Adresse ein Netzwerk wählen, zu dem der Traefik-Container selbst gar keine Route hat. Auch das endet in einem 502 mit derselben "connection refused"-Meldung im Log, wirkt aber inkonsistent, weil es je nach Neustart mal funktioniert und mal nicht. Das Label traefik.docker.network=proxy erzwingt eindeutig das richtige Netzwerk und beendet das Raten.
- Zertifikatsausstellung schlägt wegen Rate-Limit fehl: Wird für dieselbe Domainkombination innerhalb kurzer Zeit wiederholt ein neues Zertifikat angefordert, etwa weil bei jedem Testlauf ein neuer Compose-Stack unter demselben Hostnamen hochgefahren wird, blockt die Zertifizierungsstelle ab dem sechsten Versuch für exakt dieselbe Domainkombination innerhalb von sieben Tagen weitere Anfragen ab (das Kontingent füllt sich danach langsam wieder auf, etwa alle 34 Stunden um einen weiteren Versuch). Im Traefik-Log steht dann eine Zeile in der Art acme: error: 429 :: POST :: https://acme-v02.api.letsencrypt.org/acme/new-order :: urn:ietf:params:acme:error:rateLimited :: Error creating new order :: too many certificates already issued for exact set of domains. Für Tests gegen echte Hostnamen sollte deshalb immer zuerst die Staging-Umgebung der Zertifizierungsstelle verwendet werden, die eigene, nicht öffentlich vertrauenswürdige Testzertifikate ohne diese Limits ausstellt.
- Traefik startet, aber der ACME-Resolver wird stillschweigend übersprungen: Sind die Dateirechte von acme.json zu offen, etwa 644 statt 600, verweigert Traefik dem Resolver den Dienst und läuft trotzdem weiter, nur eben ohne Zertifikatsausstellung. Die Log-Zeile lautet dann sinngemäß unable to get ACME account: permissions 644 for /etc/traefik/acme.json are too open, please use 600. Ein chmod 600 auf die Datei vor dem ersten Start behebt das dauerhaft.
Häufig gestellte Fragen
Was passiert, wenn zwei Tenant-Container aus Versehen denselben Router-Namen in ihren Labels verwenden?
Router-Namen in Traefiks Docker-Provider teilen sich einen einzigen Namensraum, also einen gemeinsamen Bereich eindeutiger Bezeichner, über alle Container hinweg, unabhängig davon, in welchem Compose-Stack sie stehen. Vergeben zwei Kunden-Container also beide das Label traefik.http.routers.website.rule, weil beide Konfigurationen aus derselben Vorlage kopiert wurden, akzeptiert Traefik das nicht stillschweigend, sondern meldet einen Konfigurationsfehler in der Art "Router defined multiple times with different configurations" und verwirft die betroffene Regel. In der Praxis heißt das, Router- und Service-Namen in den Labels müssen den Kundennamen oder eine andere eindeutige Kennung enthalten, etwa kunde-a statt website, gerade weil Vorlagen für neue Tenants sonst genau diesen Fehler systematisch reproduzieren.
Lässt sich ein bestehendes nginx-Setup schrittweise auf Traefik umstellen, oder muss der Wechsel für alle Kunden auf einmal passieren?
Eine schrittweise Umstellung von nginx auf Traefik ist möglich und in der Praxis auch der übliche Weg, weil ein harter Schnitt bei laufendem Kundenbetrieb unnötiges Risiko wäre. Dazu laufen beide Reverse Proxys parallel auf demselben Host, wobei nginx zunächst weiterhin die Ports 80 und 443 belegt und Traefik testweise auf alternative Ports gebunden wird. Für jede migrierte Subdomain wird anschließend einzeln der DNS-Eintrag oder, bei gleichbleibender IP, die Portbindung umgestellt, während der jeweilige Kunden-Container von der alten vhost-Definition in ein Compose-Setup mit Labels überführt wird. Erst wenn kein vhost mehr aktiv auf nginx verweist, wird nginx entfernt und Traefik übernimmt die Ports 80 und 443 vollständig.
Was passiert mit den ausgestellten Zertifikaten, wenn der Docker-Host neu startet oder die acme.json verloren geht?
Die Datei acme.json ist der einzige Ort, an dem Traefik seine ausgestellten Zertifikate und den privaten Schlüssel des ACME-Kontos dauerhaft speichert, ein normaler Neustart des Hosts oder des Traefik-Containers ändert daran nichts, solange das Volume erhalten bleibt. Geht die Datei tatsächlich verloren, etwa weil sie versehentlich gelöscht oder nicht ins Backup einbezogen wurde, muss Traefik für jeden Hostnamen ein neues Zertifikat anfordern. Das zählt bei der Zertifizierungsstelle in aller Regel als Erneuerung für einen bereits bekannten Domainsatz und läuft dadurch nicht sofort gegen das strengere Limit für neue Domains, unterliegt aber weiterhin dem separaten, engeren Limit für mehrfach ausgestellte Zertifikate auf dieselbe exakte Domainkombination. Ein Backup von acme.json gehört deshalb in dieselbe Routine wie jedes andere produktionsrelevante Datenverzeichnis auf dem Host.