Les pics de trafic ne tuent pas WordPress. Un comportement de cache non prévu, oui. Le site tient à 30 requêtes par seconde… jusqu’à ce que le cache de la page d’accueil expire,
qu’une purge se déclenche, que les utilisateurs connectés contournent le cache, et que soudain PHP et MySQL fassent du CrossFit contre votre gré.
Voici la configuration de mise en cache pratique et de niveau production qui tient quand le marketing « balance » une campagne, qu’un crawler s’emballe, ou que votre PDG
rafraîchit la page d’accueil 400 fois pour « tester ». Nous allons construire un cache en couches, définir ce qu’il ne faut pas mettre en cache, et rendre l’histoire de la purge ennuyeuse — parce que
l’ennui, c’est la haute disponibilité.
Ce qui tient réellement au trafic réel (et ce qui ne tient pas)
Les conversations sur la performance WordPress commencent souvent par les extensions. Le trafic réel se moque de votre liste d’extensions. Il regarde si votre
architecture peut servir les 95 % des requêtes qui devraient être identiques sans lancer PHP du tout.
Une configuration survivable a trois propriétés :
- Empilement : CDN/cache edge, cache full-page à l’origine, cache d’objets, puis uniquement le calcul.
- Règles de contournement correctes : sessions connectées, paniers, aperçus et administration ne doivent pas être mis en cache comme les pages publiques.
- Invalidation contrôlée : vous purgez de manière ciblée et prévisible, et vous évitez les ruées quand les caches expirent.
Si votre « cache » signifie encore « appeler PHP, puis peut‑être utiliser un plugin pour stocker du HTML sur disque », vous optimisez la mauvaise chose. I/O disque et workers PHP
ne sont pas l’endroit où vous voulez que votre trafic de pointe atterrisse.
L’objectif : pour le trafic anonyme, retourner une réponse mise en cache par le CDN ou Nginx en quelques millisecondes, avec PHP endormi et MySQL paisiblement ignorant.
Pour les flux connectés et transactionnels, rendre le chemin dynamique efficace et stable. Vous ne pouvez pas régler une caisse cassée par du cache, mais vous pouvez
empêcher la page d’accueil de tout embraser.
Faits et contexte historique (court, utile, légèrement alarmant)
- WordPress a débuté (2003) à une époque où « mettre en cache » voulait souvent dire « activer gzip et prier ». Les schémas de trafic modernes sont plus durs : bots, scrapers et aperçus de liens agissent comme des tests de charge permanents.
- Memcached précède Redis et était le cache d’objets de référence pour l’échelle WordPress ; Redis a gagné en popularité par la suite grâce à la persistance, aux structures de données et à de meilleurs outils opérationnels.
- Varnish a popularisé le caching HTTP devant les apps dynamiques ; Nginx a ensuite absorbé une grande partie de ce rôle pour beaucoup d’équipes avec FastCGI cache et une opération plus simple.
- L’« explosion des cookies » est une taxe moderne WordPress : analytics, tests A/B, widgets de chat, bannières de consentement — chaque cookie peut silencieusement détruire le taux de HIT si vous variez dessus.
- HTTP/2 n’a pas résolu les goulets backend : il a amélioré la gestion des connexions et le multiplexage, mais si les workers PHP-FPM sont saturés, le navigateur attendra quand même poliment.
- La plupart des sites WordPress sont en lecture majoritaire jusqu’à ce qu’ils ne le soient plus. Une campagne marketing peut transformer un site « principalement en lecture » en « tout le monde frappe /wp-admin/admin-ajax.php » en quelques minutes.
- Le query cache MySQL a disparu (retiré dans MySQL 8). Si vous vous fiez encore à ce concept, vous vivez dans un musée avec du Wi‑Fi très rapide.
- OPcache n’est pas optionnel pour la performance PHP ; sans lui, PHP recompile les scripts sous charge et vous payez en CPU pour le privilège d’être lent.
L’architecture cible : une mise en cache en couches que vous pouvez raisonner
Voici la pile qui se comporte réellement sous pression :
- CDN (edge) : met en cache les pages publiques et les assets ; impose des clés de cache sensées ; protège l’origine des pics.
- Cache full-page à l’origine Nginx : FastCGI cache pour GET/HEAD anonymes ; microcaching pour certains endpoints si besoin.
- PHP-FPM + OPcache : nombre de workers ajustés ; mémoire stable ; pas de « roulette max_children ».
- Cache d’objets Redis : met en cache les recherches d’objets coûteuses WP ; évite les hits répétés sur la BD pour options/transients.
- MySQL : réglé pour la concurrence ; visibilité des requêtes lentes ; index qui reflètent la réalité.
- Observabilité : métriques de HIT cache, timings en amont, et classification des requêtes (cached vs bypass vs dynamic).
Vous remarquerez ce qui manque : « un plugin qui promet 10x de vitesse ». Les plugins aident pour l’intégration et les hooks de purge, mais le mécanisme central de survie
est au niveau HTTP. C’est là que vous empêchez la charge d’entrer dans votre runtime.
Une idée paraphrasée à coller sur chaque laptop d’astreinte, attribuée à Werner Vogels (fiabilité/architecture) : Tout finit par tomber en panne ; concevez pour que les modes de défaillance soient contenus et prévisibles.
Blague #1 : L’invalidation de cache est difficile. L’autre problème difficile est d’expliquer à la finance pourquoi « plus de CPU » n’a pas réglé « trop de requêtes ».
Couche 1 : CDN qui met en cache sans casser le site
Si vous avez un CDN et que votre origine voit encore la majeure partie du trafic anonyme, votre CDN est essentiellement un terminas TLS très cher.
Le premier travail est de faire en sorte que le CDN mette en cache avec confiance ce qui est sûr.
Ce que le CDN doit mettre en cache
- Assets statiques : images, CSS, JS, polices. TTL long, noms immuables si possible.
- Pages HTML publiques pour utilisateurs anonymes : page d’accueil, articles, catégories, tags, pages marketing.
- Certaines réponses API si vous les contrôlez et qu’elles sont publiques (rare pour WordPress à moins d’exposer un endpoint en lecture seule).
Ce que le CDN ne doit pas mettre en cache (ou doit varier prudemment)
- Tout ce qui change par utilisateur : pages connectées, admin, compte, checkout, panier.
- Réponses qui définissent des cookies ou dépendent de cookies à moins que votre clé de cache soit disciplinée.
- Pages d’aperçu, brouillons, articles protégés par mot de passe.
Conseil pratique : traitez Set-Cookie comme un pictogramme de déchet toxique pour le HTML public. Si l’origine pose des cookies sur des vues anonymes
parce qu’un plugin « en a besoin », votre taux de HIT CDN mourra en silence.
Discipline de la clé de cache : votre taux de HIT en dépend
Par défaut, de nombreux CDN peuvent varier le cache sur une longue liste : chaînes de requête, en-têtes, et parfois cookies. C’est une arme pour tuer le taux de HIT.
Pour WordPress, vous voulez typiquement :
- Varier sur le chemin URL et un ensemble contrôlé de paramètres de requête (souvent aucun pour le HTML).
- Ignorer la plupart des cookies pour le trafic anonyme, mais contourner le cache quand des cookies WordPress spécifiques existent.
- Respecter
Cache-Controlde l’origine seulement si vous faites confiance à votre origine pour être correcte. La plupart ne le sont pas initialement.
Si votre CDN gère « serve stale on error » et « serve stale while revalidating », activez-les pour le contenu public. C’est la différence entre
« l’origine est malade mais les utilisateurs sont tranquilles » et « l’origine est malade et tout le monde le sait ».
Règles de clé de cache : cookies, en-têtes, et pourquoi votre hit rate ment
WordPress définit et lit quelques cookies qui comptent :
wordpress_logged_in_*: l’utilisateur est connecté. Contourner le cache full-page et le cache HTML CDN.wp-postpass_*: contenu protégé par mot de passe. Ne pas mettre en cache publiquement.woocommerce_cart_hash,woocommerce_items_in_cart: état transactionnel. Traiter comme signaux de contournement.comment_author_*: peut affecter le rendu de la page pour les formulaires de commentaire. Généralement mieux de contourner ou de varier prudemment.
Maintenant le problème plus subtil : les cookies tiers. Beaucoup d’extensions posent des cookies pour « groupe de test A/B », « source de référence », « consentement », « session de chat », etc.
Si votre couche de cache varie par cookie ou si l’origine renvoie du HTML différent selon ces cookies, vous fragmentez votre cache en morceaux minuscules.
Règle de décision : si un cookie ne change pas de manière significative le HTML pour les utilisateurs anonymes, il ne doit pas faire partie de la clé de cache.
S’il change le HTML, vous devez décider si cette personnalisation vaut le coût en performance. Dans la plupart des environnements d’entreprise, la réponse est « non, pas sur les pages d’atterrissage critiques ».
Couche 2 : cache page complète à l’origine (Nginx FastCGI) bien fait
Le cache full-page à l’origine est le bourreau de travail. Quand le CDN manque (edge froid, purge, comportement géo-spécifique), votre origine doit encore servir du HTML mis en cache
sans lancer WordPress. Le FastCGI cache de Nginx le fait de manière fiable si vous le configurez avec des règles adultes.
Pourquoi FastCGI cache bat les « plugins de cache HTML » en production
- Concurrence : Nginx peut servir des réponses mises en cache avec un overhead minimal, même sous forte charge.
- Isolation : le cache se situe devant PHP, donc l’épuisement des workers PHP ne fait pas immédiatement tomber les pages publiques.
- Contrôle : vous pouvez définir clés de cache, règles de contournement et TTLs en un seul endroit, et vous pouvez l’observer.
Modèle de base FastCGI cache pour Nginx (conceptuel)
Vous voulez une clé de cache qui ignore les poubelles, un contournement pour cookies connectés/paniers/admin, et une protection contre les ruées.
Utilisez fastcgi_cache_lock pour qu’une requête réchauffe le cache pendant que les autres attendent brièvement au lieu de se ruer sur PHP.
Conseils pratiques sur les TTL :
- Articles/pages publiques : 5–30 minutes à l’origine est courant si vous avez des hooks de purge ; plus long si vous n’en avez pas.
- Page d’accueil : souvent plus court si elle change fréquemment, mais mise en cache néanmoins.
- Pages de recherche : délicates ; souvent mises en cache brièvement (30–120 secondes) ou ignorées si personnalisées.
Le microcaching (1–5 secondes) est un outil légitime pour des endpoints que vous ne pouvez pas entièrement mettre en cache mais qui sont frappés (certains endpoints AJAX). N’en faites pas votre premier réflexe.
Utilisez-le lorsque vous avez des preuves.
Couche 3 : cache d’objets (Redis) sans en faire une poubelle
Le cache d’objets aide surtout quand vos pages ne sont pas complètement mises en cache : tableaux de bord connectés, flux WooCommerce, et tout site avec beaucoup de blocs dynamiques.
Il réduit les requêtes répétées à la base pour les options, transients, et recherches répétées dans une même requête et entre les requêtes.
Mais le cache d’objets Redis a un mode de défaillance : il peut devenir un tiroir commun rempli de clés sans limite, TTLs imprévisibles, et des plugins « utiles » stockant des fragments rendus.
Quand Redis commence à évincer des clés chaudes ou à swaper, ce ne sera pas juste lent — ce sera lent de façon créative.
Règles pour que Redis reste utile
- Limiter la mémoire : régler
maxmemoryet une politique d’éviction sensée (souventallkeys-lfupour les charges mixtes). - Garder Redis local quand possible : la latence réseau s’additionne. S’il doit être distant, gardez-le proche et surveillez le p99.
- Utiliser un préfixe clair : éviter les collisions de clés et rendre possible la purge par préfixe si besoin.
- Surveiller fragmentation et évictions : les évictions ne sont pas « normales ». Ce sont le symptôme d’« on devine ».
Quand le cache d’objets n’aide pas
- Si vous servez déjà la plupart du trafic depuis CDN + cache full-page, le cache d’objets ne change pas la voie principale.
- Si votre goulet est le CPU PHP dû à une logique de template lourde, Redis ne vous sauvera pas beaucoup.
- Si votre base est lente à cause d’index manquants, le cache peut masquer le problème jusqu’à ce qu’il ne puisse plus le faire.
Couche 4 : PHP-FPM et OPcache (parce que les misses existent)
Vous pouvez construire un excellent cache et quand même fondre parce que le chemin non mis en cache est instable. Utilisateurs connectés, écrans admin, et réchauffements de purge toucheront PHP.
Si PHP-FPM est réglé comme un projet hobby, il se comportera comme tel.
PHP-FPM : régler d’abord la mémoire, puis la concurrence
L’erreur classique est de régler pm.max_children en fonction du nombre de CPU. WordPress consomme beaucoup de mémoire. Vous fixez le maximum d’enfants en fonction de :
(RAM disponible – marge de sécurité) / RSS moyen d’un processus PHP.
Vous devez aussi observer :
- événements
max children reached: signifie que les requêtes font la queue. - entrées du slow log : signifie que des scripts spécifiques prennent trop de temps.
- distribution des durées de requête : p95 et p99 comptent plus que la moyenne.
OPcache : le gain de performance le moins cher
Avec OPcache activé et dimensionné correctement, PHP évite de recompiler les scripts et garde le code chaud en mémoire.
Sous charge, un OPcache mal dimensionné se traduit par un CPU élevé, des redémarrages fréquents, et un « pourquoi c’est plus lent après le déploiement ? ».
Couche 5 : réglage MySQL pour les charges WordPress
WordPress n’est pas exotique. C’est une application web classique : beaucoup de lectures, quelques écritures, et quelques requêtes qui deviennent monstrueuses quand le dataset grandit.
La plupart des douleurs de base WordPress proviennent de :
- Index manquants pour des tables créées par des plugins ou des requêtes meta.
- Gonflement d’options autoloadées dans
wp_options. - Requêtes admin lentes que personne ne teste à l’échelle des données de production.
- Orages de connexions quand les caches expirent et que les workers PHP montent en flèche.
La base de données ne doit pas être votre cache de page. Elle doit être une source de vérité durable. Si votre page d’accueil a besoin de 200 requêtes, le cache masque le problème ; il ne le résout pas.
Mais avec les bonnes couches de cache, la charge BD devient prévisible et vous pouvez tuner et indexer en fonction des véritables outliers.
Invalidation et purge : la prévisibilité bat l’astuce
« Purger tout à la publication » est l’équivalent WordPress de tirer l’alarme incendie pour la tester. Oui, l’alarme fonctionne. Maintenant tout le monde est dehors et en colère.
L’invalidation doit être :
- Étroitement ciblée : purger l’URL modifiée et le petit ensemble de pages qui la référencent (page de catégorie, page d’accueil, flux).
- Limitée en débit : surtout pour les mises à jour en masse, imports et workflows éditoriaux.
- Observable : vous pouvez voir le volume de purge et le corréler aux baisses de taux de HIT et à la charge sur l’origine.
La protection contre la ruée est importante. Si vous purgez une URL populaire et que 5 000 utilisateurs la demandent en même temps, vous voulez une requête pour reconstruire et les autres attendre ou recevoir brièvement du contenu périmé.
Blague #2 : La façon la plus simple d’augmenter le taux de HIT est de publier moins. Les éditeurs n’ont pas encore adopté cette idée révolutionnaire.
Playbook de diagnostic rapide : trouver le goulot en minutes
Quand le site est lent, vous n’avez pas le temps pour la philosophie. Il vous faut une classification rapide : manquons‑nous de cache, sommes‑nous saturés en PHP, ou bloqués sur la base ?
Voici l’ordre qui trouve des réponses rapidement.
Première étape : confirmer où le temps est passé (edge vs origine vs upstream)
- Vérifier les en-têtes de statut cache CDN/edge (HIT/MISS/BYPASS). Si vous êtes majoritairement MISS, corrigez les règles de cache avant de toucher PHP.
- Vérifier les en-têtes de réponse de l’origine pour le statut du FastCGI cache (HIT/MISS/BYPASS/EXPIRED).
- Mesurer le TTFB depuis l’extérieur et comparer aux timings d’origine. Si l’edge est rapide mais l’origine lente, vous avez un problème d’origine mais les utilisateurs peuvent être tranquilles.
Deuxième étape : vérifier les signaux de saturation
- PHP-FPM : max children reached, listen queue, entrées slowlog.
- MySQL : threads en cours, requêtes lentes, waits de verrou.
- CPU et mémoire : swapping, steal time (VM), iowait (stockage).
Troisième étape : identifier la classe de requêtes qui cause la douleur
- Est‑ce /wp-admin/admin-ajax.php ?
- Est‑ce la recherche ?
- Est‑ce un crawler qui ignore robots ?
- Est‑ce une tempête de purge ?
Le chemin le plus rapide vers la stabilité n’est souvent pas « optimiser WordPress ». C’est « arrêter d’envoyer du trafic dynamique à WordPress ».
Cela signifie mettre en cache, limiter le débit, et discipliner les règles de contournement.
Tâches pratiques (commandes + sorties + décisions)
Ce sont les tâches que j’exécute réellement en réponse d’incident et pour l’optimisation. Chacune inclut : la commande, ce que signifie la sortie, et la
décision à prendre.
Task 1: Measure TTFB and confirm cache headers from the edge
cr0x@server:~$ curl -s -D- -o /dev/null https://example.com/ | egrep -i '^(HTTP/|age:|cache-control:|cf-cache-status:|x-cache:|via:|server-timing:)'
HTTP/2 200
cache-control: public, max-age=300
age: 142
cf-cache-status: HIT
via: 1.1 varnish
Signification : cf-cache-status: HIT et un age non nul suggèrent que le CDN sert du HTML mis en cache. Bien.
Décision : Si vous voyez MISS pour vos pages principales en trafic normal, corrigez les règles de cache CDN et la logique de contournement des cookies avant de toucher au tuning de l’origine.
Task 2: Compare edge vs origin directly (bypass CDN)
cr0x@server:~$ curl -s -D- -o /dev/null --resolve example.com:443:203.0.113.10 https://example.com/ | egrep -i '^(HTTP/|x-fastcgi-cache:|x-cache:|server:|cache-control:)'
HTTP/2 200
server: nginx
cache-control: public, max-age=300
x-fastcgi-cache: HIT
Signification : Vous avez atteint l’IP d’origine ; x-fastcgi-cache: HIT signifie que Nginx sert du HTML mis en cache sans PHP.
Décision : Si l’origine est en MISS alors que le CDN est en MISS, attendez‑vous à un effondrement : vous exécutez WordPress pour du trafic anonyme. Corrigez le cache full-page d’origine immédiatement.
Task 3: Check Nginx cache hit ratio from logs (quick and dirty)
cr0x@server:~$ sudo awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c
81234 HIT
10421 MISS
3211 BYPASS
442 EXPIRED
Signification : Cela suppose que votre format de log finit par le statut du cache. Un HIT élevé est sain ; beaucoup de BYPASS indique des règles de cookie ou du trafic d’auth.
Décision : Si BYPASS est élevé pour des pages publiques, trouvez quels cookies causent le contournement et arrêtez de les poser pour les utilisateurs anonymes.
Task 4: Identify top URLs causing origin load
cr0x@server:~$ sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
15432 /wp-admin/admin-ajax.php
9821 /
6110 /wp-json/wp/v2/posts
5880 /product/widget-1/
4122 /?s=widget
Signification : Cela montre les endpoints les plus occupés. Admin AJAX et la recherche sont des coupables habituels.
Décision : Si /wp-admin/admin-ajax.php domine, investiguez ce qui l’appelle (fonctions front-end, heartbeat, plugins), puis limitez le débit ou mettez en cache/microcache là où c’est sûr.
Task 5: Check PHP-FPM saturation (status page)
cr0x@server:~$ curl -s http://127.0.0.1/php-fpm-status | egrep -i 'pool|process manager|active processes|idle processes|max active processes|listen queue'
pool: www
process manager: dynamic
active processes: 48
idle processes: 2
max active processes: 50
listen queue: 37
Signification : Vous êtes au plafond ; la queue se construit. Les requêtes attendent des workers.
Décision : Si la mémoire le permet, augmentez pm.max_children. Si la mémoire ne le permet pas, réduisez la charge dynamique via cache/règles de contournement et corrigez les endpoints coûteux.
Task 6: Confirm “max children reached” events
cr0x@server:~$ sudo journalctl -u php8.2-fpm --since "30 min ago" | egrep -i 'max_children|max children'
Feb 04 10:12:09 web1 php-fpm8.2[1203]: [WARNING] [pool www] server reached pm.max_children setting (50), consider raising it
Signification : Preuve tangible de saturation.
Décision : Traitez cela comme un signal de planification de capacité. Soit augmentez les workers (si la RAM le permet), soit réduisez le trafic non mis en cache atteignant PHP.
Task 7: Estimate PHP worker memory to set max_children safely
cr0x@server:~$ ps -ylC php-fpm8.2 --sort:rss | awk 'NR==1{print} NR>1{rss+=$8; n++} END{printf "avg_rss_kb=%d\n", rss/n}'
S UID PID PPID C PRI NI RSS SZ WCHAN TTY TIME CMD
avg_rss_kb=142000
Signification : Mémoire résidente moyenne par worker ≈142MB. C’est typique pour WordPress avec extensions.
Décision : Si vous avez, disons, 8GB libres pour PHP, vous ne pouvez pas configurer 200 workers. Vous swapperez et la performance va mourir lentement et publiquement.
Task 8: Check OPcache status and memory pressure
cr0x@server:~$ php -i | egrep -i 'opcache.enable|opcache.memory_consumption|opcache.interned_strings_buffer|opcache.max_accelerated_files'
opcache.enable => On => On
opcache.memory_consumption => 256
opcache.interned_strings_buffer => 16
opcache.max_accelerated_files => 20000
Signification : OPcache est activé et dimensionné. La mémoire peut encore être insuffisante selon la taille du codebase.
Décision : Si les déploiements causent des pics CPU et des réinitialisations du cache, augmentez la mémoire OPcache et vérifiez que vous ne redémarrez pas fréquemment PHP-FPM.
Task 9: Check Redis health for object cache
cr0x@server:~$ redis-cli INFO memory | egrep -i 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
used_memory_human:1.42G
maxmemory_human:2.00G
mem_fragmentation_ratio:1.57
Signification : Redis utilise 1.42G sur 2G, la fragmentation est modérée/élevée. La fragmentation peut monter sous churn.
Décision : Si la fragmentation augmente et que la latence monte, pensez à régler la politique d’éviction, réduire le churn de clés (plugins problématiques), ou prévoir une marge.
Task 10: Check Redis evictions (a quiet performance killer)
cr0x@server:~$ redis-cli INFO stats | egrep -i 'evicted_keys|expired_keys|keyspace_hits|keyspace_misses'
keyspace_hits:98234123
keyspace_misses:7712231
expired_keys:412332
evicted_keys:118443
Signification : Les évictions signifient que Redis est sous pression mémoire et jette des clés que vous vouliez garder en cache.
Décision : Si des évictions ont lieu pendant les pics, augmentez la mémoire Redis, changez ce que vous stockez, ou fixez des TTLs plus sensés. Ne prétendez pas que les évictions sont acceptables.
Task 11: Check MySQL threads and contention quickly
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_running'; SHOW GLOBAL STATUS LIKE 'Threads_connected';"
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| Threads_running | 64 |
+-----------------+-------+
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 210 |
+-------------------+-------+
Signification : Beaucoup de threads en cours suggèrent que la BD est occupée ; les connexions montrent une pression de connexion.
Décision : Si les threads en cours montent quand le cache expire, vous avez un problème de ruée. Ajoutez des verrous de cache, du stale serving, et réduisez le trafic de contournement.
Task 12: Find slow queries (top offenders)
cr0x@server:~$ sudo mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
Count: 18 Time=2.31s (41s) Lock=0.00s (0s) Rows=1.0 (18), root[root]@localhost
SELECT option_value FROM wp_options WHERE option_name = 'autoload_big_blob' LIMIT 1;
Count: 9 Time=1.97s (17s) Lock=0.12s (1s) Rows=1200.0 (10800), app[app]@10.0.0.12
SELECT * FROM wp_postmeta WHERE meta_key = '...' AND meta_value LIKE '...%';
Signification : Les lookups d’options et les requêtes meta non indexées vous tuent. La seconde requête crie « plugin fait une recherche meta ».
Décision : Corrigez le gonflement des autoload, ajoutez des index ciblés quand c’est sûr, et envisagez de repenser les requêtes (ou désactiver la fonction du plugin). Le cache seul ne rendra pas un LIKE non indexé rapide.
Task 13: Check autoloaded options size (common WordPress landmine)
cr0x@server:~$ mysql -e "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
+-------------+
| autoload_mb |
+-------------+
| 18.47 |
+-------------+
Signification : 18MB d’options autoload signifie que chaque requête charge pas mal de choses en mémoire. Cela grossit silencieusement avec le temps.
Décision : Auditez quelles options sont autoload, désactivez l’autoload pour les gros blobs, et corrigez le plugin/thème qui les crée.
Task 14: Confirm cache-control and vary headers from origin
cr0x@server:~$ curl -s -D- -o /dev/null https://example.com/ | egrep -i 'cache-control:|pragma:|expires:|vary:|set-cookie:'
cache-control: public, max-age=300
vary: Accept-Encoding
Signification : Aucun Set-Cookie sur la page d’accueil publique, et Vary n’explose pas sur des en-têtes non pertinents.
Décision : Si vous voyez Set-Cookie sur des pages publiques, trouvez le plugin qui le pose et arrêtez‑le. Sinon, vous payez pour du cache et vous n’obtenez pas de cache.
Task 15: Detect bot spikes and bad actors quickly
cr0x@server:~$ sudo awk -F\" '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
22110 Mozilla/5.0 (compatible; SomeBot/1.0; +http://bot.example)
11842 Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 Chrome/121.0 Mobile
7441 Mozilla/5.0 (compatible; AnotherCrawler/2.1)
Signification : Un seul bot domine le trafic. Ça peut être acceptable si c’est mis en cache ; désastreux si ça contourne le cache.
Décision : Si des bots frappent des endpoints dynamiques, ajoutez des limites de débit, resserrez les règles robots, et assurez-vous que les pages publiques sont mises en cache pour rendre les bots bon marché.
Task 16: Validate Nginx upstream response time distribution
cr0x@server:~$ sudo awk '{print $(NF-1)}' /var/log/nginx/access.log | sed 's/upstream_response_time=//' | awk -F, '{print $1}' | sort -n | tail -n 5
0.842
1.003
1.217
2.884
5.991
Signification : Vos réponses upstream les plus lentes font plusieurs secondes. Ce sont probablement des misses de cache frappant PHP/MySQL.
Décision : Investiguer les endpoints lents et aligner la politique de cache : soit les mettre en cache, soit les rendre moins coûteux (indexes, correctifs de code), soit les protéger (rate limits).
Erreurs fréquentes : symptômes → cause profonde → correctif
1) Le taux de HIT CDN est bas même si les pages sont « cachables »
Symptômes : Le CDN affiche beaucoup de MISS/BYPASS ; la charge sur l’origine est élevée pendant les pics.
Cause profonde : La clé de cache varie sur des cookies ou des chaînes de requête qui ne devraient pas varier ; l’origine envoie Set-Cookie sur du HTML anonyme.
Correctif : Supprimez les cookies non pertinents de la clé de cache ; contournez uniquement sur les cookies d’auth/cart WordPress connus ; empéchez les plugins de poser des cookies pour les utilisateurs anonymes ; normalisez les query strings.
2) « Ça marche en staging » mais la production fond quand le cache expire
Symptômes : Toutes les quelques minutes, la latence pique ; PHP et BD surgissent ; puis « ça revient ».
Cause profonde : Ruée de cache : beaucoup de clients manquent en même temps ; pas de verrou de cache ; TTL court sur des pages chaudes ; tempêtes de purge.
Correctif : Activez le verrou de cache à l’origine, servez du stale pendant la revalidation au niveau du CDN, étalez les TTLs (jitter), et arrêtez de tout purger.
3) Des pages WooCommerce affichent aléatoirement un mauvais contenu
Symptômes : Le contenu du panier fuit, « Bonjour Bob » apparaît pour Alice, les prix varient de façon inattendue.
Cause profonde : Le cache full-page est appliqué à des pages personnalisées/transactionnelles ; la clé de cache ignore l’état de session.
Correctif : Contourner le cache sur panier/checkout/mon-compte, contourner sur les cookies WooCommerce, et ne jamais mettre en cache des réponses qui posent des cookies de session.
4) L’administration est lente alors que le site public est rapide
Symptômes : Les éditeurs se plaignent ; wp-admin timeouts ; les pages publiques vont bien.
Cause profonde : L’admin contourne le cache full-page et frappe des requêtes lourdes (recherches postmeta, autoload bloat) et une exécution PHP lente.
Correctif : Ajouter un cache d’objets Redis, réduire les options autoloadées, corriger les requêtes lentes, tuner PHP-FPM pour la concurrence des utilisateurs connectés, et envisager de séparer le trafic admin si nécessaire.
5) Après avoir activé Redis, la performance se dégrade
Symptômes : Latence plus élevée, timeouts, CPU Redis élevé, évictions.
Cause profonde : Redis sous-dimensionné ; churn de clés et évictions ; latence Redis distante ; utilisation de Redis pour des blobs transients géants.
Correctif : Augmenter la mémoire, choisir une politique d’éviction sensée, garder Redis proche de l’app, auditer les plugins qui stockent de grosses valeurs, et surveiller évictions et latence.
6) « Ajouter plus de workers PHP » cause swap et effondrement
Symptômes : La charge moyenne monte ; iowait explose ; tout ralentit ; les logs kernel montrent une pression mémoire.
Cause profonde : Le nombre de workers est réglé au-delà de la capacité RAM ; RSS par worker sous-estimé ; OPcache ou fuites mémoire PHP ; pas de marge.
Correctif : Mesurer le RSS, fixer max_children en fonction de la mémoire, ajouter de la marge, et réduire le trafic dynamique via le cache. Se tromper d’échelle est pire que de ne pas scaler.
7) Les purges provoquent des coupures brèves
Symptômes : Juste après un déploiement/publication, l’origine monte en charge ; le CDN passe en MISS ; latence visible pour les utilisateurs.
Cause profonde : Purges larges (tout) ; pas de warmup ; pas de verrou de cache ; TTL trop court.
Correctif : Purger seulement les URLs modifiées, limiter le débit des purges, utiliser le verrou de cache, éventuellement préchauffer les pages critiques, et permettre du stale au bord.
Trois mini-histoires d’entreprise issues du terrain
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne utilisait WordPress comme porte d’entrée marketing pour un produit qui avait grandi vite. Ils avaient un CDN, une base gérée, et une
plateforme de conteneurs. Tout semblait moderne. La performance était « correcte »… jusqu’à ce qu’elle ne le soit plus.
La mauvaise hypothèse : « Si on met Cache-Control: public, le CDN va mettre en cache le HTML et tout est réglé. » En réalité, un plugin de consentement posait un
cookie au premier affichage pour chaque utilisateur, et leur configuration CDN variait le cache sur tous les cookies par défaut. Ainsi chaque utilisateur avait sa propre
entrée de cache privée. Le taux de HIT a chuté sans que personne ne le remarque, car le tableau de bord montrait toujours des « requêtes servies » impressionnantes.
Puis ils ont lancé une campagne. La page d’accueil est redevenue un endpoint PHP. PHP-FPM a saturé, les connexions MySQL ont grimpé, et les checks de santé se sont mis à fluctuer.
Le CDN ne les avait pas protégés parce qu’il ne mettait pas en cache ce qu’ils pensaient mettre en cache.
La solution a été ennuyeuse et efficace : contourner le cache uniquement sur des cookies WordPress spécifiques, ignorer le cookie de consentement dans la clé de cache, et arrêter
de poser des cookies marketing sur les réponses HTML quand ce n’est pas nécessaire. Ils ont aussi ajouté un FastCGI cache d’origine comme filet.
La leçon : en travail de performance, l’ennemi n’est pas la complexité. C’est le comportement implicite. Si vous ne pouvez pas expliquer votre clé de cache en une phrase,
vous n’avez pas un cache — vous avez une rumeur.
Mini-histoire 2 : L’optimisation qui s’est retournée contre eux
Une autre équipe voulait une « fraîcheur » plus rapide pour les éditeurs. Ils ont réduit les TTLs sur tout à 30 secondes et ajouté des purges agressives à la publication.
Le site public semblait plus réactif pour les mises à jour de contenu. Les éditeurs étaient ravis. Pendant environ une semaine.
Le retour de bâton est arrivé pendant le trafic normal en semaine. Toutes les 30 secondes, des pages populaires expiraient à peu près en même temps. Le CDN et le cache d’origine
ont eu des MISS synchronisés. Soudain, des milliers de requêtes par minute reconstruisaient des pages identiques en exécutant PHP et frappant MySQL. C’était une ruée classique,
sauf qu’elle était auto-infligée.
Ils ont essayé de « réparer » en augmentant les workers PHP-FPM. Cela a poussé les serveurs en pression mémoire et swap intermittent. La latence s’est aggravée et est devenue moins prévisible.
Les graphiques de monitoring ressemblaient à de l’art moderne : piqués, colorés, et inutilisables pour la décision.
La vraie correction : TTLs plus longs à l’origine, verrou de cache, stale-while-revalidate côté edge, et purge ciblée pour les URLs modifiées seulement. Ils ont ajouté du jitter aux TTLs
pour quelques routes chaudes afin que les expirations ne s’alignent pas. La fraîcheur est restée acceptable parce que les purges étaient correctes, pas parce que les TTLs étaient minuscules.
La leçon : réduire les TTLs n’est pas « plus temps réel ». C’est « plus de charge ». Si vous avez besoin de fraîcheur, construisez une invalidation en laquelle vous pouvez avoir confiance.
Mini-histoire 3 : La pratique ennuyeuse qui a sauvé la mise
Une grande entreprise gérait plusieurs propriétés WordPress derrière un edge partagé. L’équipe n’était pas spectaculaire. Ils avaient des runbooks, des restores testés, et l’habitude
de mesurer avant de changer quoi que ce soit. Leur plus grande « innovation » a été d’écrire ce que signifient les cookies.
Lors d’une grande annonce produit, le trafic a monté puis a monté encore quand un agrégateur de nouvelles l’a repris. Ce qui aurait dû être un exercice incendie s’est transformé en une heure
légèrement tendue à regarder les dashboards.
La raison : ils avaient une politique de cache stricte pour le trafic anonyme, avec des règles de contournement explicites pour les cookies d’auth/panier, et ils loguaient le statut du cache
sur chaque requête. Quand l’edge a commencé à voir plus de MISS à cause de la latence de propagation régionale, le FastCGI cache d’origine était déjà chaud et protégé par verrou.
L’utilisation PHP a monté légèrement mais n’a jamais atteint la queue.
Un bot a commencé à frapper les recherches, qui n’étaient pas mises en cache. Leurs règles de rate limit l’ont attrapé, renvoyant des 429 sans pénaliser les vrais utilisateurs.
Les éditeurs ont continué à publier sans purger tout le site parce que leur intégration de purge était ciblée et limitée en débit.
La leçon : les pratiques ennuyeuses — clés de cache documentées, journalisation du statut du cache, et limitation de débit — ne sont pas de la bureaucratie. Ce sont comment vous évitez d’expliquer
une panne à des gens qui ne veulent pas apprendre ce qu’est un cookie.
Listes de contrôle / plan étape par étape
Phase 1 : Rendre le trafic anonyme bon marché (1–2 jours)
- Définir « anonyme » précisément : pas de
wordpress_logged_in_*, pas de cookies panier WooCommerce, pas de cookies de mot de passe d’article. - Activer le caching CDN pour le HTML public avec une clé de cache contrôlée ; contourner uniquement sur les cookies d’auth/panier reconnus.
- S’assurer que l’origine ne pose pas de cookies sur les pages publiques. Corriger le plugin qui le fait ou le configurer pour éviter les cookies anonymes.
- Déployer un cache full-page à l’origine (Nginx FastCGI) avec cache lock activé et règles de contournement claires.
- Logger le statut du cache au CDN (si possible) et à l’origine. Si vous ne pouvez pas mesurer le taux de HIT, vous devinez.
Phase 2 : Stabiliser le chemin dynamique (2–5 jours)
- Activer OPcache et confirmer qu’il est correctement dimensionné.
- Tuner pm.max_children de PHP-FPM basé sur le RSS mesuré et la RAM disponible.
- Déployer un cache d’objets Redis si le trafic connecté et l’admin sont significatifs. Régler maxmemory et surveiller les évictions.
- Activer les slow logs pour PHP-FPM et MySQL. Identifier les plus gros coupables avant d’« optimiser ».
- Corriger le gonflement d’autoload dans
wp_options. Cela donne souvent des gains disproportionnés.
Phase 3 : Rendre l’invalidation sûre (continu)
- Implémenter des purges ciblées à la publication/mise à jour : page modifiée + page d’accueil + archives pertinentes, pas « purge totale ».
- Limiter le débit des appels de purge et les batcher pour les opérations en masse.
- Activer le serving stale au edge pour de courtes fenêtres pendant la revalidation ou les erreurs d’origine.
- Préchauffer sélectivement (page d’accueil et landing pages principales) après les déploiements, pas tout le sitemap.
- Valider en continu les règles de contournement quand le marketing ajoute des scripts ou des plugins qui posent des cookies.
FAQ
1) Ai‑je vraiment besoin à la fois d’un cache CDN et d’un cache full-page à l’origine ?
Oui, si vous tenez à survivre au trafic réel. Le CDN réduit la latence globale et absorbe les pics, mais les MISS de cache arrivent (purges, edge froid, géographie, en‑têtes).
Le cache d’origine est votre filet pour que les MISS ne deviennent pas automatiquement du travail PHP.
2) Un plugin de cache WordPress ne suffit‑il pas ?
Les plugins aident pour les hooks de purge et certaines intégrations, mais le caching le plus robuste se passe au niveau HTTP (CDN + Nginx). Le caching par fichier via plugin peut convenir pour les petits sites, mais ce n’est pas l’architecture sur laquelle parier pour l’astreinte.
3) Comment savoir si un cookie tue mon taux de HIT ?
Inspectez les en‑têtes de réponse pour Set-Cookie sur les pages publiques et examinez les règles de clé de cache du CDN. Puis corrélez le statut du cache (HIT/MISS) avec la présence de certains cookies dans les requêtes. Si les requêtes « anonymes » portent encore des dizaines de cookies, attendez‑vous à de la fragmentation.
4) Dois‑je mettre en cache les résultats de recherche ?
Parfois. Si la recherche est publique et non personnalisée, un caching à TTL court (ou microcaching) peut beaucoup aider. Si la recherche dépend de l’état utilisateur ou inclut des prix par utilisateur, soyez prudent.
Sinon, limitez le débit des requêtes abusives et envisagez d’améliorer l’implémentation de la recherche.
5) Et WooCommerce ?
Mettre en cache les pages produit pour les utilisateurs anonymes. Contourner le cache pour panier, checkout et pages compte, et pour les requêtes avec cookies panier/session. Surveillez aussi les endpoints AJAX et les fragments ; ils peuvent générer une charge surprenante.
6) Redis ou Memcached pour le cache d’objets ?
Les deux peuvent fonctionner. Redis l’emporte souvent en flexibilité opérationnelle et outillage dans beaucoup d’organisations. L’important n’est pas la marque ; c’est le dimensionnement mémoire, la politique d’éviction, la latence, et la discipline des plugins.
7) Combien de temps doivent durer mes TTL ?
Assez longs pour être utiles, assez courts pour être corrects. Si vous avez des purges fiables, le TTL peut être plus long. Si les purges sont fragiles, le TTL devient votre filet de sécurité.
Pour beaucoup de sites, 5–30 minutes à l’origine pour les pages publiques est un point de départ sensé, l’edge pouvant garder une durée souvent plus longue.
8) Dois‑je activer « cache everything » sur le CDN ?
Pas sans discernement. Vous pouvez mettre en cache agressivement le HTML public, mais il faut implémenter des règles explicites de contournement pour les cookies connectés et transactionnels.
« Cache everything » sans discipline cookies est la manière d’obtenir des incidents de sécurité déguisés en gains de performance.
9) Mes pages publiques sont rapides, mais les appels API sont lents. Que faire ?
Identifiez quels endpoints sont lents et s’ils doivent être mis en cache ou limités. Pour wp-json, décidez ce qui est public et stable. Pour admin AJAX, investiguez les appelants et réduisez le bavardage.
Une page d’accueil rapide ne sert à rien si le reste de l’app génère une attaque par déni de service.
Prochaines étapes à déployer cette semaine
- Instrumenter le statut du cache de bout en bout : statut cache edge, statut FastCGI cache d’origine, temps de réponse upstream.
- Corriger la clé de cache : contourner uniquement sur les cookies d’auth/panier WordPress ; ignorer les autres pour le HTML anonyme.
- Déployer un FastCGI cache d’origine avec verrou : arrêter les ruées qui frappent PHP.
- Rendre les purges ciblées : purger les URLs modifiées et un petit ensemble de dépendances ; limiter le débit des opérations en masse.
- Tuner PHP-FPM selon la mémoire : mesurer le RSS ; fixer max_children en toute sécurité ; confirmer l’absence de swap.
- Auditer les options autoload : réduire le gonflement ; retirer les blobs générés par des plugins de l’autoload.
- Protéger les endpoints dynamiques chauds : limiter le débit des patterns abusifs ; microcacher seulement avec preuve.
Le résultat recherché n’est pas « un site rapide en labo ». C’est un site qui reste rapide quand les vraies personnes arrivent avec de vrais navigateurs, de vrais bots,
et le chaos réel. Une mise en cache en couches, des règles de contournement disciplinées et une invalidation ennuyeuse vous y conduisent.