Chaque page web que vous affichez est le résultat d’une chaîne de calculs et de transferts : le navigateur interroge un serveur, le serveur exécute du code, le code interroge une base de données, et le tout revient sous forme de HTML, de CSS, de JavaScript et d’images. Le cache est l’art de ne pas refaire ce travail quand il a déjà été fait. À chaque étage de cette chaîne, un mécanisme de mémorisation peut conserver le résultat d’une opération coûteuse et le resservir en une fraction du temps initial : c’est le levier de performance au meilleur rapport effort sur gain de tout le web.
Le mot « cache » recouvre pourtant des réalités très différentes. Le cache du navigateur, celui d’un CDN, celui d’un plugin WordPress, celui de Redis ou celui de MySQL ne stockent ni les mêmes choses, ni au même endroit, ni pour les mêmes raisons. Les confondre mène aux deux écueils classiques : des pages qui affichent des contenus périmés, ou des serveurs qui recalculent inutilement ce qu’une couche de cache aurait dû retenir. Comprendre qui cache quoi, où, et pour combien de temps est le préalable à toute stratégie de performance sérieuse.
Dans ce guide, nous descendons la pile complète, dans l’ordre où une requête la traverse : le cache du navigateur et ses en-têtes HTTP, le CDN et l’edge caching, les reverse proxies comme Varnish, le cache de page des plugins WordPress, le cache objet avec Redis, Memcached et APCu, l’OPcache de PHP, et enfin le buffer pool de MySQL et MariaDB. Avec, en fil rouge, la distinction qui structure tout : les ressources statiques et les pages dynamiques n’ont pas les mêmes besoins de cache.
Le cache, une même idée déclinée à chaque étage
Le principe est toujours identique : conserver le résultat d’une opération coûteuse dans un espace de stockage plus rapide ou plus proche du consommateur, et le resservir tant qu’il reste valable. Ce qui change d’un étage à l’autre, c’est la nature de l’opération économisée. Le cache navigateur économise un transfert réseau. Le CDN économise la distance géographique. Le cache de page économise l’exécution de PHP. Le cache objet économise des requêtes SQL. L’OPcache économise la compilation du code. Le buffer pool économise des lectures disque. Mis bout à bout, ces étages forment une cascade où chaque niveau protège celui d’en dessous : plus une requête est interceptée haut, moins elle coûte.
Cette cascade a une contrepartie : chaque cache introduit un risque de servir une donnée périmée. Toute la discipline du cache consiste à arbitrer entre fraîcheur et économie, et cet arbitrage dépend entièrement de la nature du contenu. Une feuille de style versionnée peut être conservée un an sans risque : si elle change, son URL change aussi. Une page produit avec un stock en temps réel, elle, ne tolère que quelques secondes de décalage. C’est pourquoi la première question à se poser devant n’importe quelle ressource n’est pas « combien de temps la cacher », mais « est-elle statique ou dynamique, et qu’est-ce qui la rend périmée ».
Les ressources statiques (CSS, JavaScript, polices, images, SVG) sont identiques pour tous les visiteurs et changent au rythme des déploiements. Les pages dynamiques (HTML généré, réponses d’API, flux) peuvent varier selon l’utilisateur, sa session, ses cookies, et changent au rythme du contenu. Les premières appellent des durées de cache longues et agressives, les secondes des durées courtes, conditionnelles, voire nulles. Chacun des étages que nous allons parcourir applique cette distinction à sa manière, à commencer par le plus proche du visiteur : son propre navigateur.
Le cache navigateur : Cache-Control, ETag et immutable
Le cache HTTP du navigateur est le plus rentable de tous : une ressource servie depuis le disque ou la mémoire du visiteur coûte zéro octet de réseau et zéro milliseconde de latence serveur. Il est aussi le seul étage que vous ne contrôlez qu’indirectement, par les en-têtes que votre serveur envoie. Toute la mécanique est spécifiée par la RFC 9111, et elle tient essentiellement dans deux en-têtes : Cache-Control pour la durée de vie, ETag pour la revalidation.
Cache-Control, la grammaire de la fraîcheur
L’en-tête Cache-Control combine des directives dont chacune répond à une question précise : qui a le droit de stocker la réponse, pendant combien de temps, et que faire à l’expiration. Les plus importantes se comptent sur les doigts d’une main.
max-age fixe la durée de fraîcheur en secondes pour tous les caches. s-maxage fixe une durée spécifique aux caches partagés (CDN, proxies), ignorée par le navigateur. public et private autorisent ou interdisent le stockage en cache partagé. no-cache autorise le stockage mais impose une revalidation à chaque utilisation. no-store interdit tout stockage. La nuance entre ces deux dernières est capitale : no-cache revalide, no-store interdit, et les confondre coûte cher.
# Ressource statique versionnée : cache long et ferme
Cache-Control: public, max-age=31536000, immutable
# HTML : stockable mais revalidé à chaque affichage
Cache-Control: max-age=0, must-revalidate
# Page personnalisée : navigateur seulement, jamais les caches partagés
Cache-Control: private, max-age=0, must-revalidate
# Donnée sensible : aucun stockage nulle part
Cache-Control: no-store
Un piège méconnu mérite d’être signalé : une réponse servie sans aucun en-tête de cache n’est pas exclue du cache pour autant. Le navigateur applique alors un cache heuristique, en déduisant une durée de vie de la date de dernière modification. Le comportement devient imprévisible et différent d’un navigateur à l’autre : des visiteurs peuvent voir une version périmée sans que rien dans votre configuration ne l’explique. La règle est donc simple : toute ressource doit porter une politique de cache explicite, même si cette politique est « ne pas cacher ».
ETag et la revalidation conditionnelle
Quand une ressource expire, tout n’est pas perdu. Si la réponse portait un en-tête ETag (une empreinte du contenu) ou Last-Modified, le navigateur ne retélécharge pas le fichier : il envoie une requête conditionnelle avec If-None-Match ou If-Modified-Since. Si le contenu n’a pas changé, le serveur répond 304 Not Modified, sans corps : quelques centaines d’octets au lieu du fichier complet. La combinaison d’un max-age raisonnable et d’un ETag constitue ce que nous appelons une configuration de cache forte : la durée de conservation est maîtrisée, et l’expiration se solde par une vérification quasi gratuite plutôt que par un re-téléchargement.
À l’inverse, les configurations faibles (pas d’en-tête du tout, un Expires seul, ou un max-age sans validateur) laissent une marge d’interprétation au navigateur et provoquent des re-téléchargements inutiles. Lors de nos audits, nous vérifions systématiquement les en-têtes des ressources les plus lourdes de la page : le CSS principal, le bundle JavaScript, l’image LCP et la police principale. Un max-age=0 ou un no-store négligé sur l’une d’elles annule tout le bénéfice du cache navigateur en visite récurrente, précisément là où il devrait briller.
Trois politiques pour trois familles de ressources
La distinction statique contre dynamique se traduit ici en trois politiques types. Pour les ressources versionnées (un hash dans le nom de fichier, comme app.4f8d9.js), le cache peut être maximal : un an, avec la directive immutable qui indique aux navigateurs qui la prennent en charge, Firefox et Safari selon MDN, de ne jamais revalider tant que la ressource est fraîche. Pour les ressources statiques non versionnées (logo, favicon), un cache intermédiaire de trente jours avec ETag offre un bon compromis.
Pour le HTML, enfin, la bonne politique est un cache court ou nul, mais jamais no-store : cette directive désactive le back/forward cache du navigateur, ce mécanisme qui rend les navigations par les boutons précédent et suivant instantanées, comme le documente web.dev.
<IfModule mod_headers.c>
# Assets versionnés : un an, immutable
<FilesMatch "\.(css|js|woff2|svg)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# Images : un mois, revalidation ensuite
<FilesMatch "\.(jpg|jpeg|png|webp|avif)$">
Header set Cache-Control "public, max-age=2592000"
</FilesMatch>
# HTML : revalidation systématique, compatible bfcache
<FilesMatch "\.(html|php)$">
Header unset Cache-Control
Header always set Cache-Control "max-age=0, must-revalidate"
</FilesMatch>
</IfModule>
Ces en-têtes règlent le comportement du navigateur de chaque visiteur. Ils ne règlent pas encore la question de la distance : entre un serveur à Paris et un visiteur à Montréal, même une ressource parfaitement cachée devra, au premier chargement, traverser un océan. C’est le problème que résout l’étage suivant.
Le CDN et l’edge caching : rapprocher les fichiers des visiteurs
Un CDN (Content Delivery Network) interpose entre vos visiteurs et votre serveur un réseau de points de présence répartis dans le monde. Chaque point de présence maintient son propre cache : quand un visiteur demande une ressource, le CDN la sert depuis le nœud le plus proche si elle s’y trouve, et ne remonte à votre serveur d’origine qu’en cas d’absence. Le gain est double : la latence chute puisque la distance physique chute, et votre serveur d’origine est déchargé de la majorité des requêtes. Nous avons détaillé les avantages et les limites des CDN dans un article dédié ; concentrons-nous ici sur leur dimension de cache.
s-maxage : parler aux caches partagés sans toucher au navigateur
Le CDN étant un cache partagé, il obéit à des directives spécifiques. La plus utile est s-maxage, qui fixe la durée de fraîcheur côté CDN indépendamment du max-age vu par les navigateurs, comme le précise la documentation MDN. Un en-tête Cache-Control: max-age=0, s-maxage=60, must-revalidate, public produit un comportement subtil et précieux pour du HTML : chaque navigateur revalide sa copie à chaque affichage, mais le CDN, lui, mutualise une même version pendant soixante secondes pour tous les visiteurs. Sur un site à fort trafic, cette minute de cache partagé suffit à absorber des pics entiers sans solliciter le serveur d’origine, tout en gardant un contenu quasi temps réel.
Les Cache Rules : imposer une politique cohérente à l’edge
Les CDN modernes permettent de surcharger à l’edge les en-têtes envoyés par l’origine. Chez Cloudflare, les Cache Rules permettent de définir séparément un Edge TTL (la durée de conservation sur le réseau Cloudflare) et un Browser TTL (le Cache-Control réécrit vers les navigateurs). C’est une soupape précieuse quand l’origine sert des en-têtes défaillants : un CMS mutualisé qui envoie des TTL trop courts, ou aucun en-tête du tout, se corrige en une règle, sans toucher au serveur. Une configuration que nous déployons couramment cible les ressources statiques par leur extension de fichier :
(http.request.uri.path.extension in {
"css" "js" "woff" "woff2" "ttf" "svg"
"jpg" "jpeg" "png" "webp" "avif" "gif" "ico"
"mp4" "webm" "pdf" "zip"
})
Sur ce périmètre, la règle force l’éligibilité au cache, un Edge TTL adapté au rythme de déploiement, et éventuellement une Custom Cache Key. Cette dernière répond à un problème très fréquent sur WordPress : les paramètres de versioning comme ?ver=6.4.2, ajoutés par les thèmes et les plugins, créent une entrée de cache distincte par variation d’URL et effondrent le taux de hit du CDN. Configurer la clé de cache pour ignorer ces paramètres, comme le permet la documentation Cloudflare, ramène toutes les variations vers une seule entrée.
La vérification se fait dans l’onglet Réseau des DevTools, en observant l’en-tête cf-cache-status : HIT signifie que la ressource est servie depuis l’edge, MISS qu’elle a été cherchée à l’origine, BYPASS que le cache a été contourné. Un site bien configuré doit servir la grande majorité de ses ressources statiques en HIT ; un flot de MISS ou de BYPASS sur des fichiers CSS ou des images est le signe d’un problème de configuration à corriger en priorité.
stale-while-revalidate : servir vite, rafraîchir en coulisses
Une dernière directive mérite sa place dans l’arsenal des caches partagés : stale-while-revalidate, spécifiée par la RFC 5861. Elle autorise un cache à servir une réponse expirée pendant une fenêtre de grâce, tout en la revalidant en arrière-plan auprès de l’origine. Le visiteur reçoit une réponse immédiate, légèrement périmée, et le visiteur suivant reçoit la version fraîche : la latence de revalidation est déplacée hors du chemin critique de l’utilisateur.
Un en-tête comme Cache-Control: s-maxage=60, stale-while-revalidate=300 combine ainsi une minute de fraîcheur stricte et cinq minutes de tolérance pendant lesquelles personne n’attend jamais l’origine. Sur du HTML éditorial, où une poignée de secondes de décalage est invisible, c’est un des réglages au meilleur rapport risque sur bénéfice qui soient.
Varnish et les reverse proxies : le cache de page avant votre application
Redescendons sur votre infrastructure. Entre le réseau et votre application peut s’intercaler un reverse proxy de cache : un serveur qui reçoit les requêtes HTTP, sert depuis sa mémoire les réponses qu’il connaît déjà, et ne transmet à l’application que ce qu’il ne sait pas servir. Le représentant le plus connu de cette famille est Varnish, conçu exclusivement pour ce rôle : il conserve les réponses en RAM et les ressert sans jamais réveiller PHP ni la base de données. Le principe vaut aussi pour ses équivalents intégrés, comme le module de cache de NGINX ou le cache serveur de LiteSpeed : dans tous les cas, la requête est servie avant même que votre application ne démarre.
Varnish se configure dans un langage dédié, le VCL (Varnish Configuration Language), documenté dans la documentation officielle. Son comportement par défaut illustre parfaitement la frontière statique contre dynamique : une requête portant un cookie n’est pas mise en cache, car un cookie signale une réponse potentiellement personnalisée. Tout le travail de configuration consiste à affiner cette frontière, en retirant les cookies sans valeur applicative (analytics, consentement) pour rendre les pages anonymes cacheables, tout en préservant les sessions réelles :
sub vcl_recv {
# Les ressources statiques se cachent toujours, cookies ignorés
if (req.url ~ "\.(css|js|woff2|jpg|png|webp|avif|svg)$") {
unset req.http.Cookie;
return (hash);
}
# Utilisateur WordPress connecté : ne jamais servir depuis le cache
if (req.http.Cookie ~ "wordpress_logged_in_") {
return (pass);
}
# Visiteur anonyme : retirer les cookies parasites (analytics, consentement)
unset req.http.Cookie;
}
Varnish reprend aussi, côté serveur, l’idée de la fenêtre de grâce : son grace mode, décrit dans la documentation dédiée, permet de continuer à servir une réponse expirée pendant que la version fraîche se régénère en arrière-plan, et même de tenir en cas de panne de l’origine. Un site dont le backend redémarre peut ainsi continuer à servir ses pages sans qu’aucun visiteur ne s’en aperçoive : au-delà de la vitesse, le cache devient un instrument de résilience, capable d’encaisser un pic de trafic ou un incident que l’application seule n’aurait pas absorbé.
Reste la question de l’invalidation : quand un contenu change, les copies en cache doivent disparaître. Varnish propose pour cela des mécanismes de purge ciblée et de bannissement par motif d’URL, décrits dans son guide utilisateur, que les CMS pilotent via des extensions dédiées. En pratique, sur les hébergements infogérés qui intègrent Varnish ou un équivalent, cette plomberie est déjà raccordée : publier un article purge automatiquement les pages concernées. C’est un des critères qui nous font recommander un hébergement WordPress pensé pour la performance plutôt qu’un mutualisé générique où aucune de ces couches n’existe.
Votre site est-il aussi rapide que vos visiteurs l’espèrent ?
Le cache de page WordPress : ce que font vraiment les plugins
Tous les sites ne disposent pas d’un Varnish devant leur application. C’est précisément le vide que comblent les plugins de cache WordPress : WP Rocket, LiteSpeed Cache, W3 Total Cache ou WP Super Cache reproduisent, à l’intérieur de WordPress, le principe du cache de page complet. À la première visite d’une URL, le plugin laisse WordPress générer la page normalement, puis enregistre le HTML final dans un fichier sur disque. Aux visites suivantes, ce fichier est servi directement, et toute la chaîne PHP et SQL est court-circuitée : une page qui coûtait des centaines de millisecondes de calcul en coûte quelques-unes.
Techniquement, ces plugins s’appuient sur un mécanisme prévu par WordPress : la constante WP_CACHE, documentée dans le guide d’administration officiel, qui charge très tôt un fichier advanced-cache.php déposé par le plugin. C’est lui qui vérifie si une copie en cache existe et la sert avant que le cœur de WordPress ne soit chargé.
Les plugins les plus aboutis vont plus loin en écrivant des règles de réécriture directement dans la configuration du serveur web, si bien que le fichier HTML est servi par Apache ou NGINX sans même démarrer l’interpréteur PHP. Le choix du plugin dépend du contexte, et notamment du serveur : nous avons partagé nos critères dans notre guide pour bien choisir son thème WordPress, et la logique est la même pour l’écosystème de cache.
Utilisateurs connectés, panier, zones privées : les exclusions qui sauvent
Le cache de page a une limite structurelle : il ne peut servir une même copie à tout le monde que si la page est identique pour tout le monde. Un utilisateur connecté, un panier e-commerce ou un espace membre produisent des pages personnalisées qui ne doivent jamais atterrir dans un cache partagé. La détection repose sur les cookies : WordPress pose un cookie wordpress_logged_in_ après authentification, documenté dans le guide officiel des cookies, et tous les plugins de cache sérieux excluent d’office les requêtes qui le portent. La même logique peut être portée au niveau du serveur, pour différencier les en-têtes de cache envoyés aux visiteurs anonymes et aux utilisateurs connectés :
<IfModule mod_headers.c>
SetEnvIf Cookie "wordpress_logged_in_" wp_logged_in=1
# Anonymes : cache CDN partagé de 60 s
Header set Cache-Control "max-age=0, s-maxage=60, must-revalidate, public"
# Connectés : jamais de cache partagé
Header set Cache-Control "max-age=0, must-revalidate, private" env=wp_logged_in
</IfModule>
La vérification de ce genre de configuration tient en deux commandes curl -I, l’une sans cookie, l’autre avec un cookie wordpress_logged_in_test factice : la première doit renvoyer public avec le s-maxage, la seconde private. Attention enfin à un point qui piège beaucoup d’installations : les plugins de cache peuvent émettre leurs propres en-têtes Cache-Control via PHP sur les pages qu’ils génèrent, et écraser silencieusement la configuration du serveur. Le seul verdict fiable est celui des en-têtes effectivement reçus, jamais celui du fichier de configuration.
Préchargement et purge : garder le cache chaud sans servir du périmé
Un cache de page vit au rythme de deux événements : sa purge, quand un contenu change, et son remplissage, quand un visiteur demande une page qui n’y est pas encore. La purge est aujourd’hui bien gérée par les plugins : publier ou modifier un article invalide automatiquement les URL concernées (la page elle-même, la page d’accueil, les archives).
Le remplissage, lui, mérite un réglage : sans préchargement, chaque page purgée redevient lente pour le premier visiteur qui la demande, celui qui paiera le coût de génération complet. Les plugins sérieux proposent donc un préchargement qui parcourt le sitemap et régénère le cache en tâche de fond, si bien qu’aucun visiteur réel ne tombe jamais sur une page froide.
Ce confort a un coût qu’il faut doser : un préchargement trop agressif sur un gros site revient à lancer un crawl complet de soi-même à chaque purge, avec la charge CPU qui l’accompagne. Les réglages par défaut sont généralement raisonnables, mais sur les sites à forte volumétrie de pages, nous limitons la fréquence du préchargement et le réservons aux gabarits à fort trafic. La logique est la même que pour toute la pile : le cache est là pour lisser la charge, pas pour en créer une nouvelle.
Le cache de page traite magnifiquement les visiteurs anonymes, qui constituent l’essentiel du trafic d’un site éditorial ou vitrine. Mais il laisse entier le cas des pages qu’il ne peut pas cacher : l’administration, les utilisateurs connectés, le tunnel d’achat. Pour accélérer celles-là, il faut descendre d’un étage et s’attaquer au coût de génération lui-même.
Le cache objet : Redis, Memcached et APCu
Générer une page WordPress, c’est exécuter des dizaines de requêtes SQL : les options du site, les métadonnées des contenus, les taxonomies, les réglages des plugins. Beaucoup de ces requêtes retournent les mêmes résultats des milliers de fois par jour.
WordPress embarque nativement une couche d’abstraction pour les mémoriser, la classe WP_Object_Cache, mais avec une limite de taille : par défaut, cette mémoire ne vit que le temps d’une requête PHP, et tout est recalculé à la page suivante. Le cache objet persistant lève cette limite en branchant cette abstraction sur un stockage externe qui survit d’une requête à l’autre : une requête SQL exécutée une fois sert alors des milliers d’affichages.
Le branchement passe par un drop-in object-cache.php, généralement installé par un plugin dédié comme Redis Object Cache, et par quelques constantes de configuration :
// wp-config.php
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
// Indispensable si plusieurs sites partagent la même instance Redis
define( 'WP_CACHE_KEY_SALT', 'monsite_' );
Trois technologies se partagent ce rôle, avec des profils distincts. Redis, le choix le plus courant aujourd’hui, offre des structures de données riches et une visibilité fine sur ce qui est stocké. Memcached, plus ancien et volontairement minimaliste, reste pertinent sur des architectures multi-serveurs où sa simplicité est un atout.
APCu, enfin, est un module de cache mémoire interne à PHP : pas de service séparé à administrer, une latence minimale puisque tout vit dans le processus PHP, mais un cache non partageable entre plusieurs serveurs. Sur un VPS unique hébergeant un site, APCu constitue une alternative sobre et efficace à Redis ; dès que l’infrastructure se distribue, Redis ou Memcached s’imposent.
L’effet du cache objet est maximal précisément là où le cache de page ne peut rien : l’administration WordPress, les utilisateurs connectés, les pages WooCommerce à panier. C’est aussi lui qui rend les transients réellement efficaces : sans cache objet persistant, ces données temporaires que les plugins stockent volontiers finissent dans la table des options de la base de données, comme l’explique la documentation de l’API Transients, et gonflent cette table au lieu de soulager la base.
Depuis WordPress 6.1, l’outil Santé du site signale d’ailleurs explicitement l’absence de cache objet persistant sur les sites qui en bénéficieraient, un signal officialisé par le manuel des hébergeurs WordPress. La vérification, une fois le drop-in en place, se fait dans les deux sens : le plugin affiche son taux de hit, et un outil comme Query Monitor permet de constater la chute du nombre de requêtes SQL par page. Sur les pages d’administration d’un site chargé en plugins, le passage d’un cache objet volatile à un cache persistant se ressent immédiatement, sans qu’aucune autre ligne de configuration n’ait changé.
L’OPcache : le cache dont votre PHP ne peut pas se passer
Avant d’exécuter la moindre ligne de votre site, PHP doit lire chaque fichier source, l’analyser et le compiler en instructions internes, les opcodes. Sans cache, ce travail est refait à chaque requête, pour chaque fichier, alors même que le code ne change qu’aux déploiements. L’extension OPcache, intégrée à PHP, conserve le résultat de cette compilation en mémoire partagée : les requêtes suivantes exécutent directement les opcodes, sans lecture disque ni compilation. Sur un CMS comme WordPress ou Prestashop, qui charge des centaines de fichiers PHP par requête, c’est le cache au meilleur rendement de toute la pile serveur, et il est presque toujours actif par défaut sur les hébergements récents.
Actif ne veut pas dire bien dimensionné. Trois directives de php.ini, décrites dans la documentation de configuration, méritent une attention systématique : la mémoire allouée, le tampon des chaînes internées et le nombre maximal de fichiers cachés. Les valeurs par défaut sont pensées pour de petites applications ; un WordPress chargé de plugins les dépasse facilement, et un OPcache saturé recommence à compiler en silence. Voici les valeurs que nous déployons sur les serveurs que nous administrons :
; php.ini
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
; En production stricte, on peut désactiver la revérification
; des fichiers et vider l'OPcache à chaque déploiement
; opcache.validate_timestamps=0
Depuis PHP 8.0, l’OPcache embarque un étage supplémentaire, encore plus proche du cœur du processeur : le compilateur JIT (Just In Time), présenté dans les notes de version de PHP 8.0. Là où l’OPcache classique conserve des opcodes que l’interpréteur PHP doit encore exécuter un à un, le JIT traduit les portions de code les plus sollicitées en instructions machine natives, exécutées directement par le CPU sans passer par l’interpréteur. C’est le dernier maillon de la chaîne : après avoir économisé la lecture disque et la compilation, on économise l’interprétation elle-même.
; php.ini : activer le JIT (PHP 8.0+), en complément de l'OPcache
opcache.jit=tracing
opcache.jit_buffer_size=128M
Un mot d’honnêteté s’impose sur ce que le JIT rapporte. Les notes de version officielles situent ses gains les plus nets sur les charges de calcul intensif ; or un CMS comme WordPress passe l’essentiel de son temps à attendre la base de données et les entrées-sorties, pas à calculer. Sur une page web classique, le gain du JIT est donc réel mais modeste, loin derrière celui du dimensionnement de l’OPcache : c’est un complément à activer une fois le reste en place, pas un levier à la place du reste. Le mode tracing, recommandé par la documentation de configuration, cible automatiquement les boucles chaudes du code.
L’OPcache, JIT compris, ne demande aucune invalidation applicative : il se rafraîchit seul quand les fichiers changent, à la fréquence définie par la configuration. Sa surveillance se limite à vérifier périodiquement que la mémoire allouée n’est pas pleine et que le taux de hit reste proche de 100 %. Une fois le code compilé servi depuis la mémoire, il reste un dernier goulet d’étranglement possible, tout en bas de la pile : la base de données elle-même.
Le cache de la base de données : le buffer pool de MySQL et MariaDB
Une base de données passe son temps à lire des pages de tables et d’index. Le buffer pool d’InnoDB, le moteur de stockage de MySQL et MariaDB, est la zone de RAM où ces pages sont conservées après lecture : tant qu’une donnée demandée s’y trouve, la base répond depuis la mémoire, sans toucher au disque. La documentation MariaDB le décrit comme la zone mémoire la plus importante à régler, et indique qu’on peut lui allouer jusqu’à 80 % de la RAM sur une machine dédiée à la base. Sous-dimensionné, il force des lectures disque à répétition : chaque requête ralentit, WordPress attend la base, et le temps de réponse serveur se dégrade sur toutes les pages non cachées.
Le problème est d’autant plus sournois que la valeur par défaut, souvent 128 Mo, est presque toujours laissée telle quelle, y compris sur des serveurs dotés de plusieurs gigaoctets de RAM. Nos règles de dimensionnement sont simples : 70 à 80 % de la RAM sur une machine dédiée à la base, 40 à 50 % sur un VPS qui héberge aussi le serveur web et PHP, jamais plus que la RAM physique disponible, et inutile de dépasser la taille totale de la base. La valeur courante se lit en une requête :
-- Taille actuelle du buffer pool, en mégaoctets
SELECT @@innodb_buffer_pool_size/1024/1024;
-- Réglage, dans la section [mysqld] du fichier de configuration
-- MariaDB : /etc/mysql/mariadb.conf.d/50-server.cnf
-- MySQL : /etc/mysql/mysql.conf.d/mysqld.cnf
-- innodb_buffer_pool_size = 2048M
Un mot sur une fausse piste encore très répandue dans les tutoriels : le query cache, qui mémorisait le résultat textuel des requêtes SELECT, a été retiré de MySQL 8.0 et reste désactivé par défaut sur MariaDB en raison de ses problèmes de montée en charge, comme le rappelle la documentation du query cache.
Le mettre en avant n’a plus de sens : la mémorisation des résultats de requêtes se joue aujourd’hui un étage plus haut, dans le cache objet applicatif, et la base concentre son intelligence mémoire dans le buffer pool. En hébergement mutualisé, ce paramètre n’est d’ailleurs pas accessible : c’est un des arbitrages qui séparent structurellement les offres, et une raison de plus de choisir son hébergement sur des critères techniques plutôt que tarifaires.
Statique ou dynamique : la stratégie d’ensemble
Récapitulons la pile entière sous l’angle qui la structure : ce que chaque couche cache, pour qui, et pour quel type de contenu. Ce tableau résume les réglages de départ que nous appliquons, à affiner évidemment selon le contexte de chaque site :
| Couche | Ce qu’elle évite | Statique | Dynamique |
|---|---|---|---|
| Navigateur | Le transfert réseau | 1 an, immutable | max-age=0, must-revalidate |
| CDN / edge | La distance et l’origine | TTL long, clé de cache épurée | s-maxage court (0 à 60 s) |
| Varnish / cache de page | L’exécution PHP + SQL | Systématique | Anonymes seulement, purge à la publication |
| Cache objet (Redis, APCu) | Les requêtes SQL répétées | Sans objet | Systématique, y compris connectés |
| OPcache | La compilation PHP | Systématique | Systématique |
| Buffer pool InnoDB | Les lectures disque | Sans objet | Dimensionné selon la RAM |
La lecture verticale de ce tableau révèle la logique d’ensemble. Pour les ressources statiques, tout se joue dans les couches hautes : navigateur et CDN font l’essentiel, et les couches serveur ne les voient presque jamais passer. Pour les pages dynamiques, c’est l’inverse : les couches hautes se font discrètes pour préserver la fraîcheur, et ce sont les couches basses, du cache de page au buffer pool, qui absorbent le coût de génération. Une stratégie de cache mûre n’est donc pas une couche miracle, mais un empilement cohérent où chaque étage traite ce que le précédent laisse passer.
Par où commencer sur votre site
Face à cet empilement, l’erreur serait de tout activer d’un coup et d’espérer. Chaque couche s’évalue par la mesure : les en-têtes réellement servis se lisent dans les DevTools ou via curl -I, le taux de hit du CDN dans ses analytics, l’efficacité du cache de page dans le temps de réponse serveur, celle du cache objet et du buffer pool dans les métriques de la base.
Le point de départ le plus rentable dépend du profil du site : un site éditorial à trafic anonyme gagnera d’abord au cache de page et au CDN, une boutique aux utilisateurs connectés gagnera d’abord au cache objet et au buffer pool. Et le cache n’exonère de rien : une page lente à générer restera lente à chaque expiration, et un cache ne fait que répartir un coût qu’il vaut toujours mieux réduire à la source.
Reste la question du diagnostic : sur un site existant, savoir quelle couche manque, laquelle est mal réglée et laquelle écrase les autres demande de lire l’ensemble de la chaîne, des en-têtes HTTP au comportement de la base. C’est exactement le périmètre d’un audit de web performance : cartographier les caches en place, mesurer ce que chacun intercepte réellement, et prioriser les réglages qui changeront les métriques de vos visiteurs. Car derrière chaque couche de cache bien réglée, il y a un TTFB qui baisse, un LCP qui suit, et des visiteurs qui restent.