Empiler des reverse proxies : une architecture de production
Quatre reverse proxies devant une seule application ressemble, au premier abord, à un système qui aurait besoin d'être simplifié. La plupart des tutoriels sur l'infrastructure web en production s'arrêtent à une seule couche — Nginx, Caddy ou Traefik — et considèrent tout ce qui va au-delà comme de l'over-engineering. Mais en réalité, chacun de ces outils résout un problème distinct, et lorsqu'on comprend la division du travail, la redondance apparente se transforme en quelque chose de cohérent.
C'est l'architecture que nous utilisons pour plusieurs déploiements clients : Cloudflare en périphérie, Traefik comme point d'entrée conscient des conteneurs, Varnish pour le cache HTTP, Nginx comme hôte applicatif, et l'application elle-même en bout de chaîne. Le flux de requêtes est le suivant :
Cloudflare → Traefik → Varnish → Nginx → Application
Ce qui suit explique ce que chaque couche apporte, où se situent les frontières, et quelles sont les implications sur les performances — y compris les endroits où cette pile crée une surcharge qu'une configuration plus simple n'aurait pas.
Ce que fait réellement chaque couche
Cloudflare opère en périphérie de l'internet public. Il absorbe le trafic DDoS, applique les règles WAF, termine le TLS sur des PoPs distribués mondialement et met en cache les ressources statiques proches des utilisateurs finaux. Ce qu'il ne fait pas bien, c'est prendre des décisions de routage intelligentes concernant vos conteneurs, ou mettre en cache des réponses applicatives qui nécessitent l'inspection des cookies. C'est le travail de quelqu'un d'autre.
Traefik est le point d'entrée conscient des conteneurs à l'intérieur de votre infrastructure. Lorsque vous exploitez une stack Docker multi-tenant, la valeur de Traefik est qu'il lit les labels des conteneurs et route en conséquence — pas de fichiers de configuration manuels qui se désynchronisent quand les conteneurs redémarrent ou montent en charge. Il gère les certificats TLS via Let's Encrypt pour les domaines internes ou de staging, supprime ou réécrit les en-têtes, et applique des chaînes de middleware comme la liste blanche d'IP ou l'authentification basique. Traefik ne met pas en cache. Il ne compresse pas bien sous charge. Son rôle est le routage et le middleware, et il s'en acquitte efficacement.
Varnish est l'accélérateur HTTP. Il se situe entre le point d'entrée et l'hôte applicatif précisément pour absorber le trafic en lecture. Une instance Varnish bien configurée peut servir des dizaines de milliers de requêtes par seconde depuis la mémoire, retournant des pages HTML complètes en moins d'une milliseconde. Son VCL (Varnish Configuration Language) vous donne un contrôle précis sur ce qui est mis en cache, pendant combien de temps et dans quelles conditions — bien plus granulaire que tout ce que les règles de cache de Cloudflare offrent. Cela importe pour les applications authentifiées où certaines réponses sont cachables par utilisateur, ou pour les pages qui doivent avoir des TTL différents selon les paramètres de requête.
Nginx, dans cette pile, joue le rôle pour lequel il a été originellement conçu : servir des fichiers et proxyfier vers un processus applicatif en amont. CloudPanel utilise Nginx comme son serveur web géré, ce qui signifie que chaque vhost dispose d'une configuration auto-générée qui gère PHP-FPM, le service des ressources statiques et la compression gzip. Cette configuration est maintenue par CloudPanel et ne devrait pas être modifiée directement. Nginx ici n'est pas une couche de prise de décision — il reçoit une requête, la proxyfie vers l'application et retourne la réponse.
Configuration Traefik : labels de conteneur
Le routage Traefik pour cette pile est configuré entièrement via des labels Docker Compose. L'intuition clé lorsqu'on place Varnish derrière Traefik est que Traefik doit transférer vers le conteneur Varnish, et non directement vers Nginx. C'est là que la plupart des gens se perdent : les exemples Traefik standards montrent un pattern direct vers l'app, et greffer Varnish dans ce flux n'est pas bien documenté.
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
L'en-tête X-Forwarded-Proto est critique. Sans lui, votre application voit chaque requête comme HTTP — parce que la communication Varnish-vers-Nginx s'effectue en HTTP simple sur le réseau Docker interne — ce qui provoque des boucles de redirection dans toute application imposant HTTPS. Traefik estampille l'en-tête avant que la requête n'entre dans le réseau interne, Varnish le passe intact, et Nginx le transmet à l'application. L'en-tête complémentaire X-Forwarded-Port évite la même classe de boucle de redirection dans les applications qui inspectent également le port.
Une deuxième considération : le paramètre passHostHeader de Traefik est vrai par défaut, ce qui signifie que Varnish reçoit l'en-tête Host original. C'est ce que vous souhaitez — le VCL de Varnish peut l'utiliser pour le keying du cache, et Nginx l'utilise pour la sélection du vhost. Si vous le remplacez, Varnish et Nginx recevront tous deux le mauvais host et échoueront silencieusement de manières difficiles à tracer.
VCL Varnish : logique de cache
Le VCL ci-dessous est un point de départ fonctionnel pour une application WordPress ou PHP derrière CloudPanel. Il couvre les cas courants : contournement pour les utilisateurs connectés et les requêtes non idempotentes, mise en cache étendue pour les ressources statiques, normalisation des paramètres de tracking, et une période de grâce pour éviter les stampedes de cache.
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 {
# Pass admin and authenticated requests
if (req.url ~ "^/wp-(admin|login|cron|json)" ||
req.http.Cookie ~ "wordpress_logged_in_" ||
req.http.Cookie ~ "woocommerce_items_in_cart") {
return(pass);
}
# Only cache GET and HEAD
if (req.method \!= "GET" && req.method \!= "HEAD") {
return(pass);
}
# Normalise tracking parameters out of the cache key
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)=[^&]+$", "");
}
# Remove cookies for static assets — allows caching
if (req.url ~ ".(css|js|png|jpg|jpeg|gif|ico|woff2?|svg|webp)(?.*)?$") {
unset req.http.Cookie;
}
return(hash);
}
sub vcl_backend_response {
# Cache static assets for 7 days
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);
}
# Cache HTML pages for 5 minutes with a 60-second grace period
if (beresp.status == 200 && beresp.http.Content-Type ~ "text/html") {
set beresp.ttl = 300s;
set beresp.grace = 60s;
}
# Never cache error responses
if (beresp.status >= 500) {
set beresp.ttl = 0s;
}
return(deliver);
}
sub vcl_deliver {
# Expose cache status for debugging
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";
}
# Remove server identity headers
unset resp.http.Via;
unset resp.http.X-Varnish;
return(deliver);
}
La normalisation des paramètres de tracking dans vcl_recv est facile à négliger et coûteuse si on l'omet. Cloudflare supprime certains paramètres en périphérie, mais les valeurs UTM et les tokens fbclid arrivent chez Varnish avec l'URL originale intacte. Sans normalisation, chaque clic sur une publicité Facebook génère une entrée de cache unique pour ce qui est, du point de vue de l'application, la même page. Sur une période de campagne, cela fragmente le cache Varnish en milliers d'entrées froides et élimine entièrement le bénéfice du taux de hit.
Le paramètre grace dans vcl_backend_response est l'autre mécanisme qui mérite d'être bien compris. Lorsque le TTL d'un objet en cache expire, la prochaine requête attendrait normalement que Nginx retourne une réponse fraîche avant que Varnish ne serve quoi que ce soit. Lors d'un pic de trafic — l'envoi d'une newsletter, une mention sur les réseaux sociaux — plusieurs requêtes arrivent simultanément pour l'entrée expirée. Sans grâce, chaque requête passe vers Nginx en parallèle. Avec une période de grâce de 60 secondes, Varnish sert l'objet périmé à toutes les requêtes sauf une pendant qu'un seul fetch en arrière-plan revalide le cache. Le résultat est que Nginx gère une requête au lieu de centaines au moment de l'expiration.
Implications sur les performances
La performance de cette pile n'est pas uniforme. Il y a des endroits où elle excelle et des endroits où elle ajoute une surcharge mesurable.
Là où elle performe bien : toute page cacheable servie depuis la mémoire de Varnish retourne en moins de 2ms au niveau Traefik. Une page d'accueil WordPress qui prend 400ms à générer depuis PHP est servie en 1–2ms pour chaque requête ultérieure jusqu'à l'expiration du TTL. L'amélioration du débit n'est pas incrémentale — c'est un ordre de grandeur. Le service des ressources statiques via Nginx est rapide dans tous les cas, et Cloudflare met ces ressources en cache en périphérie de toute façon, donc Nginx les gère rarement en production.
Là où elle ajoute une surcharge : les cache miss traversent désormais quatre sauts au lieu d'un. Une requête PHP qui ne peut pas être mise en cache — une session d'utilisateur connecté, une soumission POST, une page de panier WooCommerce — passe par Traefik, Varnish et Nginx avant d'atteindre PHP-FPM. Chaque saut réseau Docker sur le même hôte ajoute environ 0,1 à 0,3ms de latence. Pour une réponse PHP de 50ms, c'est acceptable. Pour un endpoint API qui devrait répondre en moins de 5ms, ce ne l'est pas. Dans ce cas, la bonne approche est de router le chemin API directement vers le conteneur applicatif via une règle de routeur Traefik séparée, en contournant entièrement Varnish.
Le dimensionnement de la mémoire est plus important que la plupart des paramètres : Varnish alloue son magasin d'objets depuis la RAM. La configuration par défaut utilise 256 Mo. Pour un site avec de nombreuses URLs uniques, celui-ci se remplit rapidement, le taux d'éviction monte, et le taux de hit du cache reste faible quels que soient les paramètres TTL. Le premier levier de performance à actionner après le déploiement est d'augmenter l'allocation — le flag de démarrage -s malloc,2G, ou la variable d'environnement VARNISH_SIZE=2G dans Docker — et de surveiller le résultat avec varnishstat -1 | grep hit.
Terminaison SSL : TLS se termine chez Cloudflare. Dans une configuration frontée par Cloudflare, les segments Traefik-vers-Varnish et Varnish-vers-Nginx s'exécutent en HTTP simple sur le réseau de pont Docker interne. C'est intentionnel. Ajouter TLS à la communication inter-conteneurs interne sur le même hôte introduit une surcharge CPU sans amélioration significative de la sécurité — le modèle de menace pour le trafic sur le pont Docker est catégoriquement différent du trafic sur l'internet public. Si votre modèle de menace requiert une communication inter-conteneurs chiffrée, c'est une conversation architecturale différente.
Quand cette pile est pertinente
Ce n'est pas une recommandation générale. Pour une seule application conteneurisée sans exigences de cache et sans contrainte CloudPanel, Traefik routant directement vers l'app est plus simple et correct. La pile à quatre couches justifie sa complexité dans des scénarios spécifiques : les environnements gérés par CloudPanel où Nginx n'est pas négociable, les sites adossés à un CMS où la mise en cache de pages complètes procure des améliorations de débit d'un ordre de grandeur, ou les déploiements multi-services où Traefik est déjà la couche de routage pour une douzaine de conteneurs et l'ajout de Varnish pour l'un d'eux représente un coût incrémental, pas un nouveau système.
La séparation des préoccupations est ce qui rend la complexité gérable dans le temps. La configuration Traefik vit dans les labels Docker Compose. La logique Varnish vit dans des fichiers VCL versionnés aux côtés de la stack compose. La configuration Nginx est gérée par CloudPanel. Aucun de ces systèmes n'empiète sur le domaine des autres. Quand quelque chose se comporte de manière inattendue, le chemin de diagnostic est clair : l'en-tête X-Cache vous indique si Varnish a servi la réponse ; le journal d'accès de Traefik enregistre quel middleware a été exécuté et ce que l'amont a reçu ; le journal d'erreurs de Nginx enregistre ce que PHP-FPM a signalé. Chaque couche produit son propre signal observable, et les défaillances ne se propagent pas silencieusement entre les couches.
Ce qui ressemble à de la redondance est, en pratique, une frontière claire entre le routage, le cache, le service applicatif et le calcul — quatre préoccupations qui évoluent indépendamment et tombent en panne indépendamment. Cette frontière vaut bien la surface de configuration supplémentaire.
Ressources connexes
- Déployer des applications React en production : configuration Docker complète avec Traefik
- Guide complet de dépannage n8n auto-hébergé 2025 : résoudre les problèmes de taille de données d'exécution et de webhooks avec Traefik
- Construire une stack de développement multi-tenant avec Docker : configuration complète pour des déploiements clients évolutifs
- Optimisation des performances CloudPanel : maximiser les performances du serveur cloud Hetzner pour une livraison de sites ultrarapide
Articles connexes
Déployer des applications React en production : configuration Docker complète avec le reverse proxy Traefik
Construire une infrastructure de données prête pour la production pour les vendeurs Amazon : présentation de tva-fetch
Proxy inverse Traefik : le guide complet d'auto-hébergement pour HTTPS et l'automatisation SSL