SSD Nodes Learn
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-07-24

Traefik v3 Docker Compose: 5 Apps auf einer IP

Lernen Sie, wie Sie 5 Apps mit Traefik v3 und Docker Compose via Host-Rule routen. Inklusive Lösung für den acme.json Fehler beim Starten der Container.

Eine IP, fünf Apps, ein Port 443

Ihr VPS besitzt eine einzige öffentliche IPv4-Adresse und einen einzigen TCP-Port 443. Sie möchten Gitea, eine Staging-Kopie Ihrer App, ein internes Dashboard, eine Statusseite und einen Webhook-Receiver darauf betreiben – fünf Hostnames auf einem Server. Ein Reverse Proxy ist der Prozess, der die Ports :80 und :443 übernimmt, den Host Header bei jeder Anfrage liest und die Anfrage an den richtigen Container weiterleitet. Traefik übernimmt diese Aufgabe und bezieht sowie erneuert Zertifikate für jeden Hostname, ohne dass Sie certbot manuell ausführen müssen.

Der Unterschied zwischen Traefik und einem nginx server {} Block liegt in der Herkunft der Konfiguration. Bei nginx bearbeiten Sie eine Datei und laden diese neu; der Zertifikatslebenszyklus bleibt eine separate Aufgabe. Dies entspricht dem Workflow, den Sie nutzen, wenn Sie Let's Encrypt Zertifikate mit certbot auf nginx ausstellen, wobei ein Erneuerungs-Timer außerhalb des Webservers läuft. Der Docker-Provider von Traefik überwacht den Docker-Event-Stream und liest Labels von Ihren Containern aus: Starten Sie einen Container mit einem Host() Rule-Label, ist er innerhalb einer Sekunde erreichbar; stoppen Sie ihn, verschwindet die Route. Dies ist auch eine Fehlerquelle. Konfigurationen, die in Labels gespeichert sind, existieren an fünf Stellen gleichzeitig. Ein falsches Label führt zu einem stillen Fehler – der Container wird einfach nicht geroutet, und Traefik gibt keine Fehlermeldung aus.

Die vier Substantive

  • Entrypoints sind lauschende Sockets. Sie definieren zwei: web auf :80 und websecure auf :443.
  • Routers gleichen eine Anfrage (Host(...)) ab und ordnen sie einem Service zu. Zertifikate werden pro Router über tls.certresolver angefordert.
  • Services sind das Backend – ein Container und der Port, auf dem dieser innerhalb des Docker-Netzwerks lauscht.
  • Middlewares liegen zwischen Router und Service: Basic Auth, IP-Allow-Lists, Header-Rewrites oder Redirects.

Die statische Konfiguration (Entrypoints, Providers, ACME) wird über die Kommandozeile von Traefik oder in traefik.yml übergeben; Änderungen erfordern einen Neustart von Traefik. Die dynamische Konfiguration (Routers, Services, Middlewares) wird über Container-Labels bereitgestellt und per Hot-Reload geladen. Eine Verwechslung der beiden Konfigurationstypen ist die häufigste Ursache für den Fehler "mein Flag hat keine Auswirkung".

Die Compose-Datei

Ein gemeinsames Docker-Netzwerk namens proxy bildet das Rückgrat. Traefik erreicht einen Container nur, wenn beide im selben Netzwerk liegen.

name: edge

networks:
  proxy:
    name: proxy

services:
  traefik:
    image: traefik:v3.5
    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.le.acme.email=you@example.com
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
      # while you iterate, point at staging so a mistake costs nothing:
      # - --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --api.dashboard=true
      - --log.level=INFO
      - --accesslog=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
      - 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:$$apr1$$REPLACE$$THIS

  gitea:
    image: gitea/gitea:1  # major-only pin keeps this demo copy-pasteable; pin an exact release in production
    restart: unless-stopped
    volumes:
      - ./gitea:/data
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.gitea.rule=Host(`git.example.com`)
      - traefik.http.routers.gitea.entrypoints=websecure
      - traefik.http.routers.gitea.tls.certresolver=le
      - traefik.http.services.gitea.loadbalancer.server.port=3000

docker compose up -d, dann docker compose logs -f traefik. Jede weitere Anwendung ist eine Kopie des gitea-Blocks mit eigenem Router-Namen, eigenem Host() und eigenem internen Port. Eine Nextcloud-Installation in Docker mit TLS und Backups lässt sich auf die gleiche Weise integrieren: Veröffentlichen Sie keine Ports und verbinden Sie den Container mit proxy. Die Router-Labels übernehmen die Hostnamen- und Zertifikatsverwaltung.

Fünf Details sind hierbei entscheidend.

exposedByDefault=false macht einen Container für Traefik unsichtbar, bis er traefik.enable=true besitzt. Wenn Sie dieses Label weglassen, wird für jeden gestarteten Container eine Route generiert – auch für temporäre Container wie postgres.

providers.docker.network=proxy teilt Traefik mit, welches Netzwerk verwendet werden soll, wenn ein Container mit mehreren Netzwerken verbunden ist. Ohne dieses Label wählt Traefik eventuell die falsche Container-IP. Dies führt zu einem 502-Fehler, der wie ein Anwendungsfehler wirkt.

loadbalancer.server.port=3000 ist der Port innerhalb des Containers; Gitea lauscht dort auf Port 3000. Beachten Sie, dass kein App-Container einen Port veröffentlicht – das tut nur Traefik.

Die Weiterleitung am web-Entrypoint konvertiert Plaintext-Anfragen per 308-Redirect zu HTTPS. Port 80 bleibt dennoch offen: Der ACME HTTP-Challenge benötigt ihn, ebenso wie Benutzer, die nur den Hostnamen eingeben.

Das doppelte $$ im basic-auth-Hash ist ein Compose-Escaping und kein Tippfehler. Generieren Sie den Hash mit htpasswd -nbB admin 'your-password' (Paket apache2-utils) und verdoppeln Sie anschließend jedes $.

Das Zertifikat und die acme.json-Falle

tlschallenge=true wählt TLS-ALPN-01: Let's Encrypt verbindet sich über Port 443 mit Ihrem System, und Traefik beantwortet die Challenge innerhalb des TLS-Handshakes. Die Alternative ist HTTP-01 über Port 80 — ersetzen Sie die tlschallenge Zeile in der command: Liste von Traefik durch diese zwei:

      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

Beide Methoden funktionieren. Beide erfordern, dass die öffentliche DNS-Konfiguration für den Hostnamen bereits auf Ihren VPS zeigt — die Zertifizierungsstelle löst den Namen auf und verbindet sich von extern. Erstellen Sie zuerst den A (und AAAA) Record, bestätigen Sie dies mit dig +short git.example.com und starten Sie dann Traefik.

Hier ist die Falle, die Nutzer oft einen ganzen Abend kostet. Traefik speichert den ACME-Account-Key und jedes ausgestellte Zertifikat in einer einzigen acme.json. Wenn diese Datei für Gruppen oder andere Benutzer lesbar ist, gibt Traefik eine Fehlermeldung aus, die dieser hier ähnelt, und bricht ab:

error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600

Die saubere Lösung ist die oben genannte: Nutzen Sie einen Bind-Mount für das Verzeichnis, damit Traefik die Datei selbst mit den korrekten Berechtigungen erstellen kann. Wenn Sie acme.json mit touch erstellt haben, hat Ihre umask die Berechtigungen auf 644 gesetzt. Korrigieren Sie dies auf dem Host:

chmod 600 ./letsencrypt/acme.json
docker compose restart traefik

Sichern Sie dieses Verzeichnis über Ihre App-Volumes. Ein Verlust ist beherrschbar — Zertifikate können neu ausgestellt werden — aber das gleichzeitige Neuausstellen von fünf Hostnamen führt zu Rate-Limits.

Nutzen Sie die Staging-CA während der Entwicklung. Kommentieren Sie die caserver Zeile aus, stellen Sie sicher, dass alle Routen funktionieren, und kommentieren Sie sie dann wieder aus. Löschen Sie anschließend acme.json, damit die Produktions-Zertifikate frisch angefordert werden. Die Produktions-Instanz von Let's Encrypt erlaubt fünf duplizierte Zertifikate pro Woche für denselben Satz von Hostnamen und drosselt wiederholte, fehlgeschlagene Validierungen für denselben Namen. Die Staging-Instanz stellt nicht vertrauenswürdige Zertifikate aus — Ihr Browser zeigt eine Warnung an; diese Warnung ist das Signal, dass es funktioniert hat — und die Limits sind dort wesentlich großzügiger.

Das Dashboard ist eine Steuerungsoberfläche, keine Demo

Die meisten Quickstarts setzen --api.insecure=true, wodurch das Dashboard ohne Authentifizierung auf Port 8080 bereitgestellt wird. Auf einem System mit öffentlicher IP werden so die Routing-Topologie, Hostnames, Middleware-Namen und Backend-Ports an jeden Scanner übermittelt.

Die Labels des traefik-Dienstes oben stellen die Alternative dar: Das Dashboard wird wie jede andere App über einen echten Hostnamen via TLS und hinter basicauth bereitgestellt. service=api@internal verbindet den Router mit der integrierten API von Traefik. Erhöhen Sie die Sicherheit durch eine IP-Allow-List, die von links nach rechts angewendet wird. Wenn Ihre Büro-Adresse dynamisch ist, setzen Sie den Bereich auf das Subnetz, das von einem selbst gehosteten WireGuard VPN auf demselben VPS vergeben wird. So erreichen Sie das Dashboard nur über den Tunnel:

- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-auth

Der Docker-Socket hat Root-Rechte

/var/run/docker.sock ist eine API, mit der ein Container erstellt werden kann, der / vom Host mountet. Der Zugriff darauf entspricht Root-Rechten auf dem System. Traefik benötigt diesen Zugriff, um Labels auszulesen.

Behalten Sie das :ro im Mount bei, aber beachten Sie die Auswirkungen: Es macht die Socket-Datei schreibgeschützt. Es verhindert keine POST Anfragen an die Docker-API über diesen Socket. Die eigentliche Absicherung besteht darin, Traefik den Socket nicht direkt zu übergeben und stattdessen einen Filter-Proxy dazwischenzuschalten:

  dockerproxy:
    image: tecnativa/docker-socket-proxy   # pin the current tag
    restart: unless-stopped
    environment:
      CONTAINERS: 1
      NETWORKS: 1
      POST: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy

Entfernen Sie das Socket-Volume aus der Traefik-Konfiguration und verweisen Sie den Provider auf den Proxy:

--providers.docker.endpoint=tcp://dockerproxy:2375

Traefik behält den Lesezugriff auf Container und Netzwerke, verliert aber die Berechtigung, neue Ressourcen zu erstellen.

Firewall, Ports und die häufig falsche Regel

Zwei offene Ports plus SSH:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Docker-veröffentlichte Ports umgehen ufw. Docker fügt eigene iptables-Regeln ein, die vor den ufw-Chains ausgewertet werden. Ein mit ports: ["3000:3000"] gestarteter Container ist somit aus dem Internet erreichbar, selbst wenn ufw eine Deny-Regel enthält. Die Absicherung ist struktureller Natur und keine Frage der Firewall-Konfiguration: Veröffentlichen Sie Ports nur über Traefik und weisen Sie allen anderen Containern networks: [proxy] und nichts anderes zu. Falls ein Dienst zwingend den Host erreichen muss, binden Sie ihn an das Loopback-Interface — "127.0.0.1:3000:3000".

Fehlerbehebung: Tatsächliche Fehlermeldungen

404 page not found, ausgeliefert durch Traefik. Kein Router hat übereinstimmend. Wahrscheinlichkeiten nach Häufigkeit: Der Container besitzt kein traefik.enable=true (mit gesetztem exposedByDefault=false); die Host()-Regel stimmt nicht mit dem eingegebenen Namen überein; der Router-Name in einem Label unterscheidet sich vom Router-Namen in einem anderen (routers.gitea.rule und routers.gitea.entrypoints müssen identisch sein); oder der Hostname wurde in Anführungszeichen statt in Backticks gesetzt. Traefik v3 erfordert Backticks innerhalb von Matchern.

502 Bad Gateway. Ein Router hat übereinstimmend, aber das Backend war nicht erreichbar. Fast immer befindet sich der Container nicht im proxy-Netzwerk — prüfen Sie docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. Eine weitere Ursache ist ein falscher loadbalancer.server.port: Sie haben einen veröffentlichten Port angegeben oder die App lauscht auf einem anderen Port. Das Log nennt den Versuch: dial tcp 172.18.0.5:8080: connect: connection refused.

Der Browser warnt und das Zertifikat wird auf TRAEFIK DEFAULT CERT ausgestellt. Es existiert kein Zertifikat für diesen Hostnamen und Traefik hat sein selbstsigniertes Platzhalter-Zertifikat ausgeliefert. Lesen Sie die ACME-Zeilen:

unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.com

Die DNS-Einträge zeigen noch nicht auf den Server. Korrigieren Sie den Record, warten Sie die TTL ab und starten Sie Traefik neu.

Invalid response from http://git.example.com/.well-known/acme-challenge/... beim HTTP-Challenge: Port 80 ist von extern nicht über Traefik erreichbar — meistens durch eine Firewall auf Provider-Ebene vor dem VPS, nicht durch ufw.

Zertifikate werden nie ausgestellt und Ihr DNS nutzt Cloudflare mit aktiviertem orangefarbenem Wolken-Symbol. Cloudflare terminiert TLS am Edge; TLS-ALPN-01 kann darüber nicht abgeschlossen werden. Stellen Sie den Record während der Ausstellung auf DNS-only um oder wechseln Sie zum DNS-01-Challenge mit einem API-Token. DNS-01 ist zudem der einzige Challenge, der Wildcard-Zertifikate ausstellt.

Redirect loop. Eine Komponente vor Traefik terminiert bereits TLS und leitet Plaintext an :80 weiter; der Entrypoint-Redirect leitet diesen zurück auf HTTPS. Entfernen Sie einen der beiden Redirects.

Den Betrieb aufrechterhalten

Die Docker-Unit muss für den Systemstart aktiviert sein (systemctl is-enabled docker), und restart: unless-stopped stellt den Stack nach einem Neustart wieder her. Für eine explizite Steuerung bietet eine kleine systemd-Unit, die docker compose -f /srv/edge/compose.yml up -d mit RemainAfterExit=yes ausführt, systemctl status edge und Kontrolle über die Reihenfolge.

Fixieren Sie das Traefik-Tag (traefik:v3.5, niemals latest). Das Upgrade von v2 auf v3 hat die Syntax der Regeln und die Namen der Provider geändert. Ein automatisches latest lädt eine Konfiguration, die nicht mehr erkannt wird, fehlerhaft neu. Führen Sie das Upgrade gezielt durch: Lesen Sie die Migrationshinweise, erhöhen Sie das Tag, führen Sie docker compose up -d traefik aus und überwachen Sie das Log. Wenn Sie noch ein v2-Tag verwenden, erklärt der Traefik v2 zu v3 Migrationsleitfaden alle Umbenennungen, den Kompatibilitätsmodus und ein Rollback, bei dem Ihre Zertifikate erhalten bleiben.

Sichern Sie ./letsencrypt und das Datenvolume jeder App. Traefik speichert keine weiteren Zustände, die nicht aus der compose-Datei wiederhergestellt werden können.

Probleme bei Skalierung

Die erste Grenze ist nicht der Durchsatz, sondern die einzelne Instanz: Ein Traefik auf einem VPS stellt einen Single Point of Failure für fünf Apps dar, und acme.json nutzt Flat-File-Storage — zwei Traefik-Instanzen, die gleichzeitig darauf schreiben, führen zu Datenkorruption. Skalierung bedeutet, die Zertifikatsspeicherung aus einer Datei auszulagern oder TLS an einer anderen Stelle zu terminieren.

Die zweite Grenze sind lang laufende Verbindungen. Server-sent events, große Uploads und langsame Clients erreichen die Timeouts des Entrypoints; --entryPoints.websecure.transport.respondingTimeouts.readTimeout sowie die Geschwister writeTimeout und idleTimeout sind die entsprechenden Regler. WebSockets werden ohne zusätzliche Konfiguration durchgereicht.

Die dritte Grenze ist der Festplattenspeicher. --accesslog=true schreibt auf stdout, und der json-file Driver von Docker speichert diese Daten dauerhaft, sofern keine Begrenzung erfolgt. Setzen Sie logging.options.max-size für den Traefik-Service oder schreiben Sie das Access-Log in eine Datei und nutzen Sie Log-Rotation.

Keiner dieser Punkte erfordert einen Orchestrator. Er erfordert jedoch einen Server unter Ihrer Kontrolle, mit einer echten IP und den für die Welt offenen Ports 80 und 443 — ein einzelner kleiner VPS ist die gesamte Abhängigkeitsliste.

FAQ

Benötige ich noch Certbot, wenn ich Traefik verwende?

Nein. Der ACME-Resolver von Traefik fordert das Zertifikat für jeden gerouteten Hostnamen an, erneuert es und speichert alle Zertifikate in acme.json. Certbot ist weiterhin das richtige Werkzeug, wenn nginx oder ein anderer Server TLS selbst terminiert. Die gleichzeitige Ausführung beider Tools für dieselben Hostnamen führt lediglich zu einer Überschreitung der Let's Encrypt Rate-Limits.

Warum gibt mein Container über Traefik einen 404-Fehler zurück?

Ein 404-Fehler von Traefik bedeutet, dass keine Router-Konfiguration mit der Anfrage übereinstimmt. Prüfen Sie, ob der Container traefik.enable=true besitzt (erforderlich, sobald exposedByDefault=false gesetzt ist), ob der Wert von Host() mit dem eingegebenen Namen übereinstimmt und ob der Router-Name in allen Labels für diese App identisch ist. Traefik v3 erfordert Backticks innerhalb des Matchers anstelle von Anführungszeichen.

Was ist der Unterschied zwischen einem 404 und einem 502 in diesem Fall?

Ein 404 bedeutet, dass kein Routing stattgefunden hat; ein 502 bedeutet, dass ein Router die Anfrage erkannt hat, das Backend die Verbindung jedoch abgelehnt hat. Typische Ursachen für 502-Fehler sind ein Container, der nicht mit dem proxy-Netzwerk verbunden ist, oder ein loadbalancer.server.port, der auf einen veröffentlichten Port statt auf den internen Port der Anwendung zeigt. Das Access-Log gibt die exakte Adresse an, die Traefik aufgerufen hat.

Reicht ein Read-Only-Mount des Docker-Sockets aus?

Das Flag :ro macht die Socket-Datei schreibgeschützt, nicht die dahinterliegende API — POST-Anfragen werden weiterhin darüber gesendet, und der Zugriff auf die Docker-API entspricht Root-Rechten auf dem Host. Die sicherere Methode ist der oben gezeigte docker-socket-proxy-Container, der Traefik nur Lesezugriffe auf Container und Netzwerke gewährt und Schreibzugriffe vollständig blockiert.

Kann Traefik ein Wildcard-Zertifikat ausstellen?

Nur über die DNS-01-Challenge mit einem API-Token Ihres DNS-Anbieters. TLS-ALPN-01 und HTTP-01 validieren jeweils nur einen einzelnen Hostnamen und können kein Wildcard-Zertifikat erstellen. DNS-01 ist zudem die Lösung, wenn ein CDN wie Cloudflare TLS vor Ihrem VPS terminiert und die anderen beiden Challenges fehlschlagen.