Reverse Proxies kombinieren: Eine Architektur für den Produktivbetrieb
Vier Reverse Proxies vor einer einzelnen Anwendung wirken auf den ersten Blick wie ein System, das vereinfacht werden müsste. Die meisten Anleitungen zur Web-Infrastruktur im Produktivbetrieb beschränken sich auf eine Schicht – Nginx, Caddy oder Traefik – und betrachten alles darüber hinaus als unnötige Komplexität. Tatsächlich löst jedes dieser Werkzeuge jedoch ein eigenständiges Problem. Wenn man die Arbeitsteilung versteht, wird aus der scheinbaren Redundanz ein schlüssiges Gesamtsystem.
Diese Architektur setzen wir bei mehreren Kunden-Deployments ein: Cloudflare am Netzwerkrand, Traefik als Einstiegspunkt mit Container-Erkennung, Varnish für HTTP-Caching, Nginx als Anwendungshost und die Anwendung selbst am Ende der Kette. Eine Anfrage durchläuft diese Stationen:
Cloudflare → Traefik → Varnish → Nginx → Application
Im Folgenden erläutern wir den Beitrag jeder Schicht, ihre Grenzen und die Auswirkungen auf die Leistung – einschließlich der Stellen, an denen dieser Stack zusätzlichen Aufwand verursacht, den eine einfachere Konfiguration vermeiden würde.
Was jede Schicht tatsächlich tut
Cloudflare arbeitet am Rand des öffentlichen Internets. Es fängt DDoS-Verkehr ab, wendet WAF-Regeln an, beendet TLS-Verbindungen an weltweit verteilten PoPs und speichert statische Ressourcen in Nutzernähe zwischen. Weniger gut geeignet ist es für intelligente Routing-Entscheidungen über Ihre Container oder das Zwischenspeichern von Anwendungsantworten, die eine Prüfung von Cookies erfordern. Dafür ist eine andere Schicht zuständig.
Traefik ist der Einstiegspunkt mit Container-Erkennung innerhalb Ihrer Infrastruktur. Bei einem mandantenfähigen Docker-Stack liegt sein Wert darin, dass es Container-Labels liest und entsprechend routet. Manuelle Konfigurationsdateien, die nach Container-Neustarts oder Skalierungen nicht mehr passen, entfallen. Es verwaltet TLS-Zertifikate von Let's Encrypt für interne oder Staging-Domains, entfernt oder ändert Header und setzt Middleware-Ketten wie IP-Zulassungslisten oder Basic Auth durch. Traefik speichert keine Antworten zwischen und komprimiert unter Last nicht gut. Seine Aufgabe sind Routing und Middleware, und diese erledigt es effizient.
Varnish ist der HTTP-Beschleuniger. Es sitzt zwischen Einstiegspunkt und Anwendungshost, um gezielt Leseanfragen abzufangen. Eine gut abgestimmte Varnish-Instanz kann Zehntausende Anfragen pro Sekunde aus dem Arbeitsspeicher bedienen und vollständige HTML-Seiten in unter einer Millisekunde zurückgeben. Die VCL (Varnish Configuration Language) erlaubt präzise Kontrolle darüber, was wie lange und unter welchen Bedingungen zwischengespeichert wird – deutlich feiner als Cloudflares Cache-Regeln. Das ist bei Anwendungen mit Authentifizierung wichtig, bei denen manche Antworten mit einer angepassten Hash-Berechnung benutzerspezifisch zwischengespeichert werden könnten (das folgende Beispiel tut dies nicht, sondern leitet Anfragen angemeldeter Nutzer ohne Caching weiter), oder bei Seiten, deren Cache-Gültigkeitsdauer (TTL) von Abfrageparametern abhängt.
Nginx spielt in diesem Stack die Rolle, für die es ursprünglich konzipiert wurde: Dateien ausliefern und Anfragen an einen nachgelagerten Anwendungsprozess weiterleiten. CloudPanel verwendet Nginx als verwalteten Webserver. Jeder virtuelle Host erhält damit eine automatisch generierte Konfiguration für PHP-FPM, die Auslieferung statischer Ressourcen und gzip-Komprimierung. CloudPanel pflegt diese Konfiguration; sie sollte nicht direkt bearbeitet werden. Nginx trifft hier keine Routing-Entscheidungen: Es empfängt die Anfrage, leitet sie an die Anwendung weiter und gibt die Antwort zurück.
Traefik-Konfiguration: Container-Labels
Das Traefik-Routing für diesen Stack wird vollständig über Labels in Docker Compose konfiguriert. Entscheidend beim Einsatz von Varnish hinter Traefik ist, dass Traefik an den Varnish-Container weiterleiten sollte, nicht direkt an Nginx. Hier entsteht häufig Verwirrung: Die üblichen Traefik-Beispiele zeigen eine direkte Weiterleitung zur Anwendung, und die Einbindung von Varnish in diesen Ablauf ist schlecht dokumentiert.
services:
varnish:
image: varnish:7.5
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`www.example.com`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls=true"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
- "traefik.http.services.myapp.loadbalancer.server.port=80"
- "traefik.http.middlewares.myapp-proto.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.myapp-proto.headers.customrequestheaders.X-Forwarded-Port=443"
- "traefik.http.routers.myapp.middlewares=myapp-proto"
networks:
- traefik-public
- internal
Der Header X-Forwarded-Proto ist entscheidend. Ohne ihn sieht Ihre Anwendung jede Anfrage als HTTP, weil die Kommunikation von Varnish zu Nginx im internen Docker-Netzwerk über unverschlüsseltes HTTP läuft. Bei Anwendungen, die HTTPS erzwingen, verursacht das Weiterleitungsschleifen. Traefik setzt den Header vor dem Eintritt der Anfrage ins interne Netzwerk, Varnish reicht ihn unverändert durch und Nginx leitet ihn an die Anwendung weiter. Der ergänzende Header X-Forwarded-Port verhindert dieselbe Art von Weiterleitungsschleifen bei Anwendungen, die auch den Port prüfen.
Ein zweiter Punkt: Traefiks Einstellung passHostHeader ist standardmäßig true. Varnish erhält also den ursprünglichen Host-Header. Das ist gewünscht: Varnishs VCL kann ihn für den Cache-Schlüssel verwenden, Nginx für die Auswahl des virtuellen Hosts. Wenn Sie diese Einstellung überschreiben, erhalten beide den falschen Host. Daraus können Fehler ohne klare Meldung entstehen, die schwer nachzuverfolgen sind.
Varnish VCL: Cache-Logik
Die folgende VCL-Konfiguration ist ein Ausgangspunkt für eine WordPress- oder PHP-Anwendung hinter CloudPanel, keine fertige Richtlinie für den Produktivbetrieb. Sie deckt die üblichen Fälle ab: Umgehen des Caches für angemeldete Nutzer und nicht idempotente Anfragen, längeres Caching statischer Ressourcen, Vereinheitlichung von Tracking-Parametern und eine Kulanzzeit, in der Varnish leicht veraltete Seiten ausliefert, während es sie aktualisiert. Vor dem Produktivbetrieb müssen auch die WooCommerce-Seiten für Warenkorb, Checkout und Benutzerkonto sowie das Cookie wp_woocommerce_session_ vom Caching ausgenommen werden. Da dieses vcl_backend_response mit return(deliver) endet, wird die integrierte Varnish-Logik nicht ausgeführt, die Antworten mit Set-Cookie oder Cache-Control: private/no-store vom Caching ausschließt. Ergänzen Sie daher entsprechende Prüfungen.
vcl 4.1;
backend default {
.host = "nginx"; # service name in docker-compose
.port = "80";
.connect_timeout = 5s;
.first_byte_timeout = 30s;
.between_bytes_timeout = 10s;
}
sub vcl_recv {
# Admin- und authentifizierte Anfragen durchleiten
if (req.url ~ "^/wp-(admin|login|cron|json)" ||
req.http.Cookie ~ "wordpress_logged_in_" ||
req.http.Cookie ~ "woocommerce_items_in_cart") {
return(pass);
}
# Nur GET und HEAD cachen
if (req.method != "GET" && req.method != "HEAD") {
return(pass);
}
# Tracking-Parameter aus dem Cache-Key normalisieren
if (req.url ~ "[?&](utm_source|utm_medium|utm_campaign|fbclid|gclid)=") {
set req.url = regsuball(req.url,
"&(utm_source|utm_medium|utm_campaign|fbclid|gclid)=[^&]+", "");
set req.url = regsuball(req.url,
"\?(utm_source|utm_medium|utm_campaign|fbclid|gclid)=[^&]+&", "?");
set req.url = regsub(req.url,
"\?(utm_source|utm_medium|utm_campaign|fbclid|gclid)=[^&]+$", "");
}
# Cookies für statische Assets entfernen – erlaubt Caching
if (req.url ~ "\.(css|js|png|jpg|jpeg|gif|ico|woff2?|svg|webp)(\?.*)?$") {
unset req.http.Cookie;
}
return(hash);
}
sub vcl_backend_response {
# Statische Assets für 7 Tage cachen
if (bereq.url ~ "\.(css|js|png|jpg|jpeg|gif|ico|woff2?|svg|webp)(\?.*)?$") {
set beresp.ttl = 7d;
set beresp.grace = 24h;
unset beresp.http.Set-Cookie;
return(deliver);
}
# HTML-Seiten für 5 Minuten mit 60-sekündiger Grace-Period cachen
if (beresp.status == 200 && beresp.http.Content-Type ~ "text/html") {
set beresp.ttl = 300s;
set beresp.grace = 60s;
}
# Fehlerantworten nie cachen
if (beresp.status >= 500) {
set beresp.ttl = 0s;
}
return(deliver);
}
sub vcl_deliver {
# Cache-Status für Debugging sichtbar machen
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
# Server-Identitäts-Header entfernen
unset resp.http.Via;
unset resp.http.X-Varnish;
return(deliver);
}
Die Vereinheitlichung von Tracking-Parametern in vcl_recv wird leicht übersehen, was teuer werden kann. Sofern Sie bei Cloudflare keine URL-Umschreibung konfigurieren, erreichen UTM-Werte und fbclid-Token Varnish mit der unveränderten ursprünglichen URL. Ohne Vereinheitlichung erzeugt jeder Klick auf eine Facebook-Anzeige einen eigenen Cache-Eintrag für dieselbe Seite aus Sicht der Anwendung. Im Verlauf einer Kampagne zerfällt der Varnish-Cache dadurch in Tausende ungenutzte Einträge, und der Vorteil einer hohen Cache-Trefferquote geht vollständig verloren.
Auch die Einstellung grace in vcl_backend_response verdient eine genaue Betrachtung. Läuft die TTL eines Cache-Objekts ab, wartet die nächste Anfrage normalerweise auf eine neue Antwort von Nginx, bevor Varnish etwas ausliefert. Bei einem plötzlichen Besucheranstieg – etwa nach einem Newsletter-Versand oder einer Erwähnung in sozialen Medien – treffen mehrere Anfragen gleichzeitig auf den abgelaufenen Eintrag. Ohne Kulanzzeit bündelt Varnish die Anfragen für ein cachebares Objekt weiterhin zu einem einzigen Abruf bei Nginx, doch die anderen Anfragen warten, bis dieser abgeschlossen ist. Mit einer Kulanzzeit von 60 Sekunden liefert Varnish diesen Anfragen sofort das veraltete Objekt aus, während ein einzelner Abruf im Hintergrund den Cache aktualisiert. So verarbeitet Nginx beim Ablauf eine Anfrage statt Hunderten, und die Besucher müssen nicht darauf warten.
Auswirkungen auf die Leistung
Die Leistungsbilanz dieses Stacks ist nicht einheitlich. An manchen Stellen ist er besonders leistungsfähig, an anderen verursacht er messbaren Zusatzaufwand.
Wo er gut abschneidet: Jede zwischenspeicherbare Seite aus dem Varnish-Arbeitsspeicher wird auf Traefik-Ebene in unter 2 ms zurückgegeben. Eine WordPress-Startseite, deren Generierung mit PHP 400 ms dauert, wird für jede weitere Anfrage bis zum Ablauf der TTL in 1–2 ms ausgeliefert. Der Durchsatz steigt nicht nur geringfügig, sondern um eine Größenordnung. Nginx liefert statische Ressourcen ohnehin schnell aus, und Cloudflare speichert sie zusätzlich am Netzwerkrand zwischen. Deshalb verarbeitet Nginx solche Anfragen im Produktivbetrieb selten.
Wo zusätzlicher Aufwand entsteht: Bei einem Cache-Miss durchlaufen Anfragen jetzt vier Stationen statt einer. Eine nicht zwischenspeicherbare PHP-Anfrage – eine Sitzung eines angemeldeten Nutzers, eine POST-Übermittlung oder eine WooCommerce-Warenkorbseite – passiert Traefik, Varnish und Nginx, bevor sie PHP-FPM erreicht. Jeder Übergang im Docker-Netzwerk auf demselben Host fügt etwa 0,1–0,3 ms Latenz hinzu. Für eine PHP-Antwort mit 50 ms ist das akzeptabel, für einen API-Endpunkt mit einer Zielzeit unter 5 ms nicht. Dann sollte der API-Pfad über eine separate Traefik-Router-Regel direkt zum Anwendungscontainer geleitet werden und Varnish vollständig umgehen.
Die Speichergröße ist wichtiger als die meisten Parameter: Varnish legt seinen Objektspeicher im RAM an. Das offizielle Docker-Image verwendet standardmäßig 100 MB. Bei vielen unterschiedlichen URLs füllt er sich schnell, die Verdrängungsrate steigt und die Cache-Trefferquote bleibt unabhängig von den TTL-Einstellungen niedrig. Der erste Leistungshebel nach dem Deployment ist daher, den zugewiesenen Speicher zu erhöhen – über das Start-Flag -s malloc,2G oder die Docker-Umgebungsvariable VARNISH_SIZE=2G – und das Ergebnis mit varnishstat -1 | grep hit zu überwachen.
SSL-Terminierung: Öffentliches TLS endet bei Cloudflare. Im SSL-Modus „Full (strict)“ verbindet sich Cloudflare anschließend per HTTPS mit Traefik und verwendet dabei das oben gezeigte Let’s-Encrypt-Zertifikat des Routers. Hinter Traefik laufen die Verbindungen von Traefik zu Varnish und von Varnish zu Nginx über unverschlüsseltes HTTP im internen Docker-Bridge-Netzwerk. Das ist beabsichtigt. Auf einem einzelnen Host, auf dem Sie alle Container kontrollieren, verursacht TLS für interne Container-Kommunikation zusätzlichen CPU-Aufwand bei begrenztem Sicherheitsgewinn, da der Bridge-Verkehr den Host nicht verlässt. Gegen einen kompromittierten Container oder Host würde es nicht schützen. Wenn Ihr Bedrohungsmodell verschlüsselte Kommunikation zwischen Containern verlangt, ist das eine gesonderte Architekturfrage.
Wo dieser Stack sinnvoll ist
Dies ist keine allgemeine Empfehlung. Bei einer einzelnen containerisierten Anwendung ohne Caching-Anforderungen und ohne Bindung an CloudPanel ist direktes Traefik-Routing zur Anwendung einfacher und richtig. Der Vier-Schichten-Stack rechtfertigt seine Komplexität in konkreten Szenarien: von CloudPanel verwaltete Umgebungen, in denen Nginx feststeht; CMS-basierte Websites, bei denen das Zwischenspeichern vollständiger Seiten den Durchsatz um eine Größenordnung steigert; oder Deployments mit mehreren Diensten, bei denen Traefik bereits ein Dutzend Container routet. Varnish für einen davon hinzuzufügen bedeutet dann zusätzlichen Aufwand für ein bestehendes System statt eines neuen Systems.
Die Aufgabentrennung hält die Komplexität langfristig beherrschbar. Die Traefik-Konfiguration liegt in Docker-Compose-Labels. Die Varnish-Logik liegt in VCL-Dateien, die zusammen mit dem Compose-Stack versioniert werden. CloudPanel verwaltet die Nginx-Konfiguration. Keines dieser Systeme greift in den Aufgabenbereich der anderen ein. Bei unerwartetem Verhalten ist der Diagnoseweg klar: Der Header X-Cache zeigt, ob Varnish die Antwort ausgeliefert hat. Traefiks Zugriffsprotokoll hält fest, welcher Router und welcher Backend-Dienst zuständig waren sowie welche Statuscodes das vorgelagerte System zurückgab und welche an den Client gesendet wurden. Nginx protokolliert die von PHP-FPM gemeldeten Fehler. Jede Schicht liefert ein eigenes beobachtbares Signal, und Fehler häufen sich nicht unbemerkt über mehrere Schichten hinweg.
Was wie Redundanz aussieht, ist in der Praxis eine klare Grenze zwischen Schutz am Netzwerkrand, Routing, Caching und Auslieferung durch den Anwendungshost – vier Aufgaben, die unabhängig voneinander skalieren und ausfallen. Diese Grenze rechtfertigt den zusätzlichen Konfigurationsumfang.
Verwandte Beiträge
- React-Anwendungen im Produktivbetrieb bereitstellen: Vollständiges Docker-Setup mit Traefik Reverse Proxy
- Vollständiger Leitfaden zur Fehlerbehebung bei selbst betriebenem n8n 2025: Probleme mit Ausführungsdatengröße und Webhooks mit Traefik beheben
- Einen mandantenfähigen Entwicklungsstack mit Docker aufbauen: Vollständiges Setup für skalierbare Kunden-Deployments
- CloudPanel-Leistungsoptimierung: Hetzner-Cloud-Server für blitzschnelle Website-Auslieferung optimieren