Table des matières :
- Pré-requis et topologie Docker réaliste (TLS, reverse-proxy, Varnish, backend)
- Cache HTTP et PrestaShop : ce qui est “cacheable” en pratique (et ce qui ne l’est pas)
- Docker Compose : câblage Varnish ↔ Nginx ↔ PHP-FPM (et pièges de ports)
- VCL PrestaShop : règles strictes, normalisation, TTL et gestion des cookies
- Valider X-Cache et diagnostiquer HIT/MISS sans se mentir
- Invalidation : purge/ban, et pourquoi PrestaShop ne vous aidera pas (sans module)
- Observabilité Varnish : hit ratio, varnishlog, limites systèmes et runbook incident
Pré-requis et topologie Docker réaliste (TLS, reverse-proxy, Varnish, backend)
Ce qui suit est pensé pour PrestaShop 9.1 (stack Symfony 6.4) avec PHP 8.3 (ou 8.2) et Varnish 7.6 LTS. Le point important : PrestaShop ne fournit pas un jeu d’en-têtes HTTP « cache-friendly » suffisant pour un cache partagé (shared cache) sans règles côté proxy. En particulier, la présence de cookies et de pages semi-dynamiques (panier, compte, prix selon groupe/zone, devises) impose une stratégie VCL stricte, sinon vous cachez des pages personnalisées et vous vous exposez à des fuites de contenu.
En Docker, l’architecture la plus robuste est : reverse proxy TLS (Traefik/Nginx) → Varnish (HTTP) → Nginx (static + fastcgi) → PHP-FPM → MariaDB/Redis. Mettre Varnish directement en frontal TLS est faisable (Hitch, nginx+proxy), mais dans 90 % des stacks Docker, on préfère laisser le proxy edge terminer TLS et transmettre en HTTP interne.
Deux points « réseau + HTTP » reviennent systématiquement sur PrestaShop en reverse-proxy (donc à valider avant même d’optimiser le cache) :
- Chaîne de headers de proxy : votre edge doit transmettre correctement
Host,X-Forwarded-Proto(https),X-Forwarded-For(IP client) et idéalementX-Forwarded-Host. SiX-Forwarded-Protoest absent ou incohérent, PrestaShop peut générer des URLs en HTTP, des redirections en boucle, ou des assets mixtes. - Proxies de confiance côté Symfony/PrestaShop : en environnement conteneurisé, l’IP source vue par PHP est souvent celle du proxy. Il faut donc configurer correctement les proxies de confiance (selon votre manière de déployer PrestaShop 9) pour que le schéma (http/https) et l’IP client remontent correctement. Ce point n’augmente pas le hit ratio, mais évite une catégorie d’effets de bord difficiles à diagnostiquer (redirections, génération de liens, GeoIP, protections anti-fraude).
Prérequis non négociables avant de toucher à Varnish :
- Vous avez déjà une baseline de perf (TTFB, hit ratio attendu, pages à exclure). Si ce n’est pas le cas, posez une méthode reproductible (voir Audit performance PrestaShop : méthode en 6 étapes reproductibles et TTFB PrestaShop : réduire le Time To First Byte sous 200 ms).
- Vous savez diagnostiquer côté navigateur et côté HTTP (voir DevTools : méthodes professionnelles pour mesurer et optimiser les performances web).
- Vous acceptez que le cœur PrestaShop ne “pense” pas en cache HTTP partagé : on travaille donc principalement au niveau VCL (et éventuellement via modules/hook si vous devez purger finement).
Checklist rapide (très concrète) avant Varnish en prod :
- Le back-office reste accessible et stable derrière vos proxys (pas de redirections bizarres).
- La boutique sert des réponses cohérentes en
https(URLs canoniques, pas de mixed content). - Vous avez identifié vos pages « strictement publiques » (home, catégories, produit sans personnalisation) et vos pages « strictement privées » (compte, panier, tunnel).
- Vous savez comment tester en HTTP brut (cf. section X-Cache), sinon vous naviguez à l’aveugle.
Cache HTTP et PrestaShop : ce qui est “cacheable” en pratique (et ce qui ne l’est pas)
Varnish est un cache HTTP ; il décide de stocker/réutiliser des réponses à partir de la clé de cache (URL + Host + Vary + normalisations) et des directives HTTP (Cache-Control, Expires, Set-Cookie…). En e-commerce, la difficulté n’est pas “mettre un cache”, c’est ne pas casser la personnalisation. Le risque classique sur PrestaShop : servir une page “mon compte” ou un panier à un autre utilisateur parce qu’un cookie ou un header a été ignoré.
Le signal le plus robuste est souvent… le cookie. En front-office, PrestaShop pose un cookie de session (souvent du type PrestaShop-<hash>) et d’autres cookies (langue, devise, consentement, tracking). Tant qu’un utilisateur est identifié, a un panier, ou a un contexte spécifique (groupe, remise), la page est potentiellement personnalisée. La conséquence : la plupart des pages peuvent être cacheables uniquement en mode « visiteur anonyme sans cookies applicatifs » ou si vous implémentez un cache par fragments (ESI).
Pour cadrer la décision « cache / pas cache », ce tableau vous évite beaucoup d’erreurs (et il est facile à adapter à votre catalogue) :
| Type de page PrestaShop | Exemple d’URL | Risque de personnalisation | Stratégie Varnish la plus sûre |
|---|---|---|---|
| Home | / |
moyen (modules, blocs dynamiques) | Cache possible sans cookie PS ; TTL modéré |
| Catégorie | /categorie/... |
moyen (tri, facettes, prix) | Cache possible sur pages publiques ; normaliser querystring |
| Produit | /produit/... |
moyen à élevé (prix par groupe, stock, personnalisations) | Cache prudent ; TTL modéré ; ESI si blocs dynamiques |
| CMS | /content/... |
faible à moyen | Bon candidat au cache |
| Recherche | /recherche?... |
élevé (beaucoup de variantes, faible réutilisation) | Souvent PASS ou TTL très court + querysort |
| Compte | /mon-compte |
très élevé | PASS |
| Panier / checkout | /panier, /commande |
très élevé | PASS |
| API / webservices / endpoints module | variable | variable | En général PASS tant que non maîtrisé |
Deux mécanismes HTTP font la différence en pratique :
Set-Cookie: dès qu’une réponse contientSet-Cookie, Varnish considère typiquement que c’est dangereux pour un cache partagé (et vous avez raison de l’être). En e-commerce, c’est le garde-fou principal.Cache-Control: PrestaShop (et des modules) envoie souvent des directives conservatrices, ou au contraire incohérentes. Vous allez donc souvent sur-règle côté VCL : “je cache ceci, j’interdis cela”.
Référence normative utile : RFC 9111 (HTTP Caching). Elle explicite notamment la prudence autour de l’authentification. Extrait (à garder en tête quand un signal de personnalisation est présent) :
“A cache MUST NOT store a response to a request containing an Authorization header field unless the response is explicitly marked as cacheable …” (RFC 9111, 2022)
Source : https://www.rfc-editor.org/rfc/rfc9111
Même si PrestaShop n’utilise pas typiquement Authorization en front-office, l’esprit est identique : dès qu’un signal de personnalisation est présent, vous évitez le stockage, ou vous segmentez proprement (ce qui coûte en complexité et en mémoire cache).
Enfin, attention à un point très “terrain” : certains modules ajoutent des cookies « silencieux » (wishlist, comparaison, recommandations, A/B test, consentement RGPD). Si vous ne les nettoyez pas, votre hit ratio s’écroule (chaque cookie supplémentaire peut créer un chemin “PASS” ou des variations inutiles).
Docker Compose : câblage Varnish ↔ Nginx ↔ PHP-FPM (et pièges de ports)
En Docker, le piège n°1 est de laisser Varnish “écouter” publiquement alors qu’il est censé être un hop interne. Le piège n°2 est d’ignorer les timeouts et buffers, ce qui vous donne des faux négatifs (MISS) ou des 503 sous charge. Gardez Varnish sur un réseau interne, et n’exposez que le reverse proxy edge.
Exemple minimal (à adapter) avec un réseau front (edge) et back (interne). Ici, Traefik (ou nginx) publie, Varnish est interne, et Nginx backend écoute sur 8080.
services:
edge:
image: traefik:v3.1
command:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# ... ACME / providers etc.
ports:
- "80:80"
- "443:443"
networks:
- front
- back
varnish:
image: varnish:7.6
networks:
- back
volumes:
- ./varnish/default.vcl:/etc/varnish/default.vcl:ro
command:
- varnishd
- -F
- -f
- /etc/varnish/default.vcl
- -a
- :80
- -s
- malloc,512m
depends_on:
- nginx
nginx:
image: nginx:1.26
networks:
- back
volumes:
- ./nginx/site.conf:/etc/nginx/conf.d/default.conf:ro
- ./prestashop:/var/www/html:ro
depends_on:
- php
php:
image: php:8.3-fpm
networks:
- back
volumes:
- ./prestashop:/var/www/html
networks:
front:
back:
Vous noterez volontairement l’absence de ports: pour Varnish et Nginx : ils ne doivent pas être accessibles depuis l’extérieur. Si vous devez tester localement Varnish, faites-le via docker compose exec ou via le reverse proxy edge.
Deux « pièges Docker » à connaître, parce qu’ils faussent vos tests de cache :
depends_onne garantit pas que Nginx/PHP soient prêts : il ne fait que gérer l’ordre de démarrage. En prod, ajoutez deshealthcheck(ou une logique de readiness) pour éviter des backend errors temporaires que vous interpréterez comme des soucis Varnish.- Vérifiez que votre Nginx backend écoute bien sur 8080 (sinon Varnish parlera au mauvais port). Un backend Nginx minimal ressemble souvent à ceci :
server {
listen 8080;
server_name _;
root /var/www/html;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php:9000;
}
}
Si vous avez déjà vu des erreurs “too many open files” en prod, Varnish + Nginx + PHP-FPM accélèrent la cadence et peuvent mettre en défaut les limites système ; ce sujet est concret sur PrestaShop 9 sous charge (voir PrestaShop 9 : corriger l’erreur « too many open files »).
VCL PrestaShop : règles strictes, normalisation, TTL et gestion des cookies
Une VCL “générique” trouvée sur GitHub est rarement safe pour PrestaShop, parce que la boutique dépend d’un contexte (langue/devise/pays) et que les modules ajoutent des cookies sans prévenir. L’objectif est double : (1) maximiser le hit ratio sur les pages réellement publiques, (2) forcer PASS sur tout ce qui peut être personnalisé.
Deux optimisations « à fort ROI » côté VCL, avant même de parler d’ESI :
- Nettoyer les cookies non applicatifs (analytics, consentement, pixels) pour ne pas transformer une ressource publique en requête non cacheable.
- Normaliser l’URL (tri des query params, suppression des
utm_*,gclid,fbclid, etc.) pour éviter de stocker 15 variantes d’une page strictement identique.
Base VCL (Varnish 7.x) : backend Nginx en 8080, et règles FO classiques. Ici on introduit un header interne X-Cache-Mode pour tracer PASS/HIT/MISS proprement.
vcl 4.1;
import std;
backend default {
.host = "nginx";
.port = "8080";
.connect_timeout = 1s;
.first_byte_timeout = 30s;
.between_bytes_timeout = 30s;
}
sub vcl_recv {
# Traçage interne
unset req.http.X-Cache-Mode;
# Santé
if (req.url == "/healthz") {
return (synth(200, "OK"));
}
# Normaliser Host (évite des doublons si votre infra varie la casse)
if (req.http.host) {
set req.http.host = std.tolower(req.http.host);
}
# Normaliser la querystring (utile sur catégories/recherche)
# 1) trier les paramètres
set req.url = std.querysort(req.url);
# 2) supprimer les paramètres marketing les plus courants
set req.url = regsuball(req.url, "(?i)(\?|&)(utm_[^=]+|gclid|fbclid|msclkid)=[^&]*", "");
# 3) nettoyer les ?& restants
set req.url = regsub(req.url, "\?$", "");
set req.url = regsub(req.url, "\?&", "?");
set req.url = regsuball(req.url, "&&+", "&");
# Méthodes non cacheables
if (req.method != "GET" && req.method != "HEAD") {
set req.http.X-Cache-Mode = "PASS_METHOD";
return (pass);
}
# Back-office / endpoints sensibles (à adapter à votre admin dir)
if (req.url ~ "^/admin" || req.url ~ "^/index.php\?controller=admin") {
set req.http.X-Cache-Mode = "PASS_BO";
return (pass);
}
# Pages typiquement personnalisées
if (req.url ~ "^/(panier|commande|order|cart|mon-compte|my-account|login|connexion|authentification)") {
set req.http.X-Cache-Mode = "PASS_ACCOUNT";
return (pass);
}
# Assets statiques : on supprime les cookies (améliore énormément le hit ratio)
if (req.url ~ "\.(css|js|png|gif|jpg|jpeg|webp|avif|svg|ico|woff2?|ttf|eot)(\?.*)?$") {
unset req.http.cookie;
}
# Ne jamais mettre en cache si cookie de session PrestaShop présent
if (req.http.cookie ~ "PrestaShop-") {
set req.http.X-Cache-Mode = "PASS_PSCOOKIE";
return (pass);
}
# Nettoyage cookies de tracking (sinon vous tuez le hit ratio)
if (req.http.cookie) {
set req.http.cookie = regsuball(req.http.cookie, "(^|; )(_ga|_gid|_gcl_au|_fbp|IDE|test_cookie|cookie_consent|_hjSessionUser_[^=]+|_hjSession_[^=]+)=[^;]*", "");
set req.http.cookie = regsuball(req.http.cookie, "; +", "; ");
set req.http.cookie = regsub(req.http.cookie, "^; ", "");
if (req.http.cookie == "") { unset req.http.cookie; }
}
# Normalisation Accept-Encoding (évite des variantes inutiles)
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(png|gif|jpg|jpeg|webp|avif|svg|ico|woff2?)$") {
unset req.http.Accept-Encoding;
} else if (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} else {
unset req.http.Accept-Encoding;
}
}
return (hash);
}
sub vcl_backend_response {
# Interdire le cache si le backend pose un Set-Cookie
if (beresp.http.Set-Cookie) {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
return (deliver);
}
# Bon candidat : fichiers statiques (long TTL côté Varnish)
if (bereq.url ~ "\.(css|js|png|gif|jpg|jpeg|webp|avif|svg|ico|woff2?|ttf|eot)(\?.*)?$") {
set beresp.ttl = 24h;
set beresp.grace = 1h;
return (deliver);
}
# Cache des 200/301/302 (les redirections peuvent être cacheables si stables)
if (beresp.status == 200 || beresp.status == 301 || beresp.status == 302) {
# TTL par défaut : à calibrer selon votre catalogue
set beresp.ttl = 10m;
# Grace : sert du "stale" en cas de backend lent/KO
set beresp.grace = 1m;
# Un minimum de sécurité : si le backend dit explicitement no-store, on respecte
if (beresp.http.Cache-Control ~ "no-store" || beresp.http.Cache-Control ~ "private") {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
}
} else {
# Option utile : "hit-for-pass" court sur les statuts non cacheables
# (évite de marteler le backend en boucle sur des URLs non cacheables)
set beresp.uncacheable = true;
set beresp.ttl = 30s;
}
return (deliver);
}
sub vcl_deliver {
# Exposition contrôlée de X-Cache (utile en staging, à restreindre en prod)
if (req.http.X-Cache-Mode) {
set resp.http.X-Cache = req.http.X-Cache-Mode;
set resp.http.X-Cache-Hits = "0";
} else {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
set resp.http.X-Cache-Hits = obj.hits;
}
# Hygiène : ne pas exposer la stack
unset resp.http.Server;
unset resp.http.Via;
}
Trois remarques qui évitent des erreurs coûteuses :
1) La règle if (req.http.cookie ~ "PrestaShop-") return(pass); est volontairement “brutale”. Elle réduit le hit ratio, mais elle évite de servir du contenu lié à une session. Si vous voulez mieux, vous devez segmenter par cookie (ou implémenter ESI / hole punching). Pour une approche ESI, voir Varnish Cache : configuration ESI pour optimiser le cache par fragments.
2) Les redirections (301/302) peuvent être cacheées et améliorer fortement le TTFB sur des patterns fréquents (réécritures, trailing slash), mais attention si vous avez des redirections dépendantes du contexte (geo/IP). Si vous utilisez ce type de logique, testez au minimum depuis plusieurs IP/réseaux avant de les rendre cacheables.
3) La “grace” est un levier anti-panne : la doc Varnish décrit ce mécanisme comme une capacité à servir un objet expiré pendant un laps de temps pour absorber un backend lent ou indisponible (voir docs officielles : https://varnish-cache.org/docs/). C’est utile en e-commerce lors de pics (soldes, drops), mais uniquement si votre donnée tolère quelques secondes (ou minutes) de retard. En pratique, beaucoup de boutiques acceptent une grace courte (30–120 s) sur catalogue, mais pas sur panier/commande (qui sont en PASS de toute façon).
Valider X-Cache et diagnostiquer HIT/MISS sans se mentir
Le seul test qui vaut quelque chose est HTTP brut, pas “j’ai l’impression que c’est plus rapide”. Travaillez avec curl -I et vérifiez systématiquement : X-Cache, X-Cache-Hits, Age, Cache-Control, présence de Set-Cookie, et les variations (Vary). Une validation minimale : 1ère requête = MISS, 2ème requête identique = HIT, puis une requête avec cookie PrestaShop = PASS.
Exemples :
# 1) Visiteur anonyme : on veut MISS puis HIT
curl -I https://shop.example.com/
curl -I https://shop.example.com/
# 2) Forcer une navigation avec cookie (simulation rapide)
curl -I https://shop.example.com/ -H 'Cookie: PrestaShop-TEST=1'
# 3) Page checkout : doit être PASS
curl -I https://shop.example.com/commande
Deux astuces simples pour fiabiliser le diagnostic :
- Ajoutez
-s -o /dev/null -D -pour afficher uniquement les headers (ça évite de se tromper en lisant la sortie). - Regardez
Age: sur un HIT,Agedoit en général être > 0 et augmenter avec le temps (si l’objet reste en cache). SiX-Cache=HITmaisAgereste toujours à 0, vous ne regardez peut-être pas la bonne couche (ou un proxy amont réécrit les headers).
Exemple pratique :
curl -s -o /dev/null -D - https://shop.example.com/ | sed -n '1,20p'
Côté navigateur, un piège fréquent : vous confondez cache navigateur / service worker / CDN avec Varnish. Le header X-Cache que vous ajoutez dans vcl_deliver devient votre source de vérité. Si vous passez par un reverse proxy edge (Traefik, nginx), assurez-vous qu’il ne supprime pas X-Cache et qu’il ne met pas lui-même un cache intermédiaire.
Une discipline efficace (et plutôt “production safe”) consiste à :
- activer
X-Cacheen staging en permanence ; - en prod, ne l’exposer que pour vos IP (ACL) ou via un header de debug interne (sinon vous donnez des infos de comportement cache aux bots, scrapers et concurrents).
Si vous avez besoin de relier ces tests à un objectif de performance (par exemple : “TTFB < 200 ms sur pages publiques”), recroisez avec la méthodologie TTFB et vos mesures DevTools (voir réduction du TTFB sur PrestaShop et le guide de mesure via DevTools).
Invalidation : purge/ban, et pourquoi PrestaShop ne vous aidera pas (sans module)
Le cache HTTP n’est utile que si l’invalidation est maîtrisée. PrestaShop, en standard, ne déclenche pas d’invalidation Varnish au bon niveau lors d’une mise à jour produit, prix, stock, CMS, etc. Il existe des modules qui hookent les événements et envoient des PURGE/BAN, mais ça reste de l’intégration : vous devez définir votre politique (purge par URL ? par tag ? par pattern ?).
Dans Varnish, on distingue généralement :
- PURGE : suppression d’un objet précis (URL exacte). Simple, mais vite insuffisant.
- BAN : invalidation par expression (ex : toutes les URLs
/produit/), plus flexible mais peut coûter cher si mal géré.
En pratique PrestaShop, un compromis souvent réaliste (au début) :
- PURGE au niveau page produit et page catégorie quand vous savez l’URL exacte (mise à jour d’un produit, changement de CMS).
- BAN par pattern sur des zones à forte diffusion (ex :
/+ pages CMS) lors de déploiements, ou après un import massif.
Exemple VCL minimal pour autoriser une purge depuis un réseau interne (à adapter) :
acl purge {
"127.0.0.1";
"172.16.0.0"/12;
"192.168.0.0"/16;
}
sub vcl_recv {
if (req.method == "PURGE") {
if (client.ip !~ purge) {
return (synth(403, "Not allowed"));
}
return (purge);
}
}
Important : si vous reprenez cet exemple, vous devez fusionner ce sub vcl_recv avec celui de votre VCL principale (sinon vous écrasez vos règles). Le plus simple est d’ajouter ce bloc PURGE au début de votre vcl_recv existant.
Si vous devez aller plus loin (purge par tags), vous pouvez implémenter une stratégie par headers (Surrogate-Key) émis par le backend. Mais là encore : PrestaShop ne l’émet pas nativement. Vous devez intervenir via module (hook sur rendu, sur controller) ou via un reverse-proxy applicatif qui enrichit les réponses. Avant d’investir dans cette complexité, validez que vos gains proviennent déjà du cache “anonyme” + pages catalogue (souvent le gros du trafic SEO).
Observabilité Varnish : hit ratio, varnishlog, limites systèmes et runbook incident
Une config VCL qui “marche” en dev peut s’écrouler en prod si vous ne regardez pas les métriques. Les trois commandes que vous devez maîtriser : varnishstat (compteurs), varnishlog (traces requêtes), varnishadm (ban/purge et debug). En Docker, vous pouvez les exécuter dans le conteneur : docker compose exec varnish varnishstat -1.
Surveillez au minimum : MAIN.cache_hit, MAIN.cache_miss, MAIN.cache_hitpass, MAIN.backend_fail, MAIN.sess_dropped, MAIN.threads_failed. Un hit ratio “correct” dépend du business, mais sur un catalogue stable, atteindre 60–90 % sur les pages publiques est réaliste. Si vous restez à 5–10 %, ce n’est pas Varnish “qui ne marche pas” : c’est presque toujours (a) cookies non nettoyés, (b) TTL trop bas, (c) trop de PASS, (d) réponses avec Set-Cookie partout.
Pour diagnostiquer proprement un cas “je m’attendais à HIT mais j’ai MISS” :
varnishlog -g requestpermet de suivre une requête bout à bout (réception → backend fetch → deliver).varnishlog -g request -q 'ReqUrl eq "/"'(à adapter) vous aide à filtrer sur une URL précise.varnishadm backend.listvous confirme l’état du backend (surtout utile quand les 503 apparaissent sous charge).
Enfin, n’ignorez pas les limites OS. En accélérant le débit, vous augmentez le nombre de sockets, fichiers, threads et descripteurs utilisés. Si vous voyez des erreurs de type EMFILE / too many open files, corrigez côté système (ulimits, systemd, conteneur) avant d’accuser la VCL (voir PrestaShop 9 : corriger l’erreur « too many open files »).
Pour industrialiser (tests de charge, seuils, rollback, “mode dégradé” via grace, monitoring), bâtissez un runbook explicite : il vous servira le jour où la boutique prend un pic (soldes, campagne TV, push influenceurs). Pour ça, vous pouvez compléter avec monitoring + tests de charge + runbooks soldes et la vue d’ensemble sur les caches côté serveur (Varnish/Redis/OPcache) dans Cache PrestaShop : Varnish, Redis, Memcached et OPcache côté serveur.
