Le cache de page est ce qui accélère le plus WordPress : sur notre banc d’essai, le temps jusqu’au premier octet (TTFB) est passé de 41 ms à 0,3 ms, et le serveur est passé de 44 à plus de 14 000 pages par seconde. Le deuxième gain le plus important vient d’OPcache (190 ms sans, 41 ms avec), le troisième de la recherche de l’extension qui ralentit le site : notre imitation d’une telle extension ajoutait environ 265 ms à chaque requête. Viennent ensuite, par ordre d’impact mesuré, les workers PHP-FPM, la compression, les images, le cache d’objets et, en dernier, le nettoyage de l’autoload : 3 Mo d’options chargées automatiquement n’ont ajouté qu’environ 4 ms.
Comment nous avons testé
Banc d’essai RichHost, octobre 2026 : un conteneur isolé avec 2 vCPU / 8 Go de RAM, Ubuntu 24.04, nginx 1.24, PHP 8.3-FPM, MariaDB 10.11, WordPress 6.7.2, thème Twenty Twenty-Five, 300 articles générés. Les requêtes partaient de la même machine (127.0.0.1), le réseau et le TLS ne sont donc pas pris en compte. Le TTFB correspond à la médiane et au 90e centile (p90) de 40 requêtes curl après trois requêtes de préchauffage ; le débit a été mesuré avec ab (300 requêtes, 10 simultanées) ; pages testées : la page d’accueil et un article. L’« extension lourde » est notre mu-plugin qui exécute 150 requêtes SQL supplémentaires et attend 250 ms une API externe. Nous avons modifié un seul facteur à la fois. Ce sont des chiffres de laboratoire issus du banc d’essai, pas des valeurs de production de RichHost.
Résultats
| Scénario | TTFB médian (accueil / article) | TTFB p90 | Requêtes par seconde |
|---|---|---|---|
| OPcache désactivé | 190 / 195 ms | 210 / 216 ms | 9,7 / 9,8 |
| OPcache activé | 41 / 45 ms | 45 / 51 ms | 44,1 / 41,6 |
| OPcache + cache d’objets Redis* | 39,7 / 39,6 ms | 49,4 / 46,2 ms | 47,3 / 48,4 |
| OPcache + cache de page nginx | 0,3 / 0,3 ms | 0,4 / 0,3 ms | 21 581 / 14 749 |
| Extension lourde, sans cache de page | 309 / 311 ms | 325 / 323 ms | 15,3 / 15,0 |
| Extension lourde + cache d’objets Redis | 306 / 306 ms | 319 / 314 ms | 15,3 / 15,4 |
| Extension lourde + cache de page | 0,2 / 0,3 ms | 0,3 / 0,3 ms | 18 703 / 15 202 |
| Administrateur connecté (cache contourné) | 44,8 / 45,9 ms | 49,6 / 56,2 ms | 39,8 / 38,8 |
| +3 Mo d’options en autoload | 45,4 / 45,4 ms | 59,5 / 59,0 ms | 42,3 / 39,3 |
* Les lignes Redis proviennent d’une série de mesures distincte, dans laquelle la référence sans cache d’objets était de 43,7 / 46,8 ms.

Deux constats sont moins évidents que ne le laissent penser les conseils habituels. D’abord, le cache de page ne corrige pas une extension lente, il la masque aux visiteurs anonymes. Un administrateur, un client WooCommerce avec un panier ou toute personne ayant un cookie de connexion reçoit la page non mise en cache : 45 ms sur un site vierge et plus de 300 ms avec l’extension lourde. Ensuite, un autoload surchargé, souvent cité comme la première chose à corriger, n’a presque pas modifié la vitesse sur notre banc.
Étape 1. Cache de page
Sans cache, WordPress exécute PHP à chaque requête, lance 22 à 29 requêtes SQL (notre compteur sur la page d’accueil et un article) et reconstruit le HTML. Un cache de page sert du HTML déjà prêt : le TTFB est donc tombé de 41-45 ms à 0,2-0,3 ms, aussi bien pour le site vierge que pour le site avec l’extension lourde. Un vrai site ajoute le temps réseau, mais le travail du serveur disparaît presque entièrement.
Voici la configuration nginx fastcgi_cache que nous avons testée. Elle ne met en cache que les requêtes GET anonymes et contourne le cache pour les requêtes POST, les URL avec paramètres, l’administration, l’API REST et les visiteurs ayant un cookie de connexion, de commentaire ou de panier WooCommerce :
fastcgi_cache_path /var/cache/nginx/wp levels=1:2 keys_zone=WPCACHE:64m max_size=1g inactive=60m use_temp_path=off;
server {
# ... listen, server_name, root, index ...
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|wp-.*\.php|/feed/|sitemap(_index)?\.xml") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in|woocommerce_items_in_cart|woocommerce_cart_hash") { set $skip_cache 1; }
location / { try_files $uri $uri/ /index.php?$args; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_cache WPCACHE;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 10m;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_background_update on;
fastcgi_cache_lock on;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-Cache $upstream_cache_status always;
}
}
Vérification des règles (l’en-tête X-Cache indique d’où vient la page) :
$ curl -sI http://127.0.0.1:8081/post-293/ | grep X-Cache
X-Cache: MISS
$ curl -sI http://127.0.0.1:8081/post-293/ | grep X-Cache
X-Cache: HIT
$ curl -sI -b wordpress_logged_in_abc=x http://127.0.0.1:8081/post-293/ | grep X-Cache
X-Cache: BYPASS
$ curl -sI http://127.0.0.1:8081/post-293/?utm_source=x | grep X-Cache
X-Cache: BYPASS
Deux points à connaître à l’avance. Les pages restent en cache 10 minutes (fastcgi_cache_valid) : après la modification d’un article, les visiteurs anonymes peuvent donc voir l’ancienne version pendant 10 minutes au maximum, sauf si vous mettez en place la purge du cache, par exemple avec l’extension Nginx Helper. fastcgi_cache_use_stale et fastcgi_cache_lock empêchent des centaines de visiteurs de solliciter PHP en même temps lorsqu’une entrée du cache expire. Sur un hébergement mutualisé, vous n’avez généralement pas accès à nginx : une extension fait alors le même travail, WP Super Cache, W3 Total Cache, ou LiteSpeed Cache si le serveur tourne sous LiteSpeed. Nous présentons les autres niveaux de cache et leurs besoins en mémoire dans notre article sur le cache et la RAM d’un hébergement.
Étape 2. OPcache
OPcache garde le code PHP compilé en mémoire, si bien que WordPress n’a pas à analyser des milliers de fichiers à chaque requête. OPcache désactivé, le banc a donné un TTFB de 190-195 ms et 9,7 requêtes par seconde ; activé, 41-45 ms et 42-44 requêtes, soit environ 4,5 fois plus rapide. C’est la vitesse que perçoivent tous ceux qui passent à côté du cache de page : administrateurs, rédacteurs, clients de la boutique.
Vérifiez OPcache pour le site, pas dans le terminal : PHP en ligne de commande (CLI) et PHP-FPM peuvent différer. Sur notre banc, php -v indique PHP 8.4 alors que le site tourne sous PHP 8.3-FPM, et OPcache est désactivé pour la CLI. Une vérification plus sûre consiste à créer à la racine du site un fichier temporaire contenant <?php var_dump(opcache_get_status(false)['opcache_enabled'] ?? false); : ouvrez-le dans un navigateur (sur le banc, il a affiché bool(true)) puis supprimez-le aussitôt. Les réglages par défaut du paquet Ubuntu ont suffi sur le banc.
Étape 3. Trouver l’extension lente
L’imitation d’extension lourde a fait passer le TTFB de 41-45 ms à 309-311 ms. Il existe trois moyens de trouver le coupable.
Query Monitor. Une extension d’administration qui affiche dans la barre d’outils le nombre et la durée des requêtes SQL par extension, ainsi que les requêtes HTTP sortantes.
wp profile. Une commande du paquet wp-cli/profile-command (à installer avec wp package install wp-cli/profile-command) qui montre quelle étape du chargement consomme le temps. Sortie réelle du banc avec l’imitation activée :
$ wp profile stage --url=http://127.0.0.1:8080/post-293/ --fields=stage,time,query_time,query_count
stage time query_time query_count
bootstrap 0.2059s 0.0017s 4
main_query 0.2701s 0.0107s 155
template 0.0751s 0.0062s 20
total (3) 0.5512s 0.0186s 179
$ wp profile stage main_query --spotlight --url=http://127.0.0.1:8080/post-293/ --fields=hook,time,query_time,query_count
hook time query_time query_count
pre_get_posts:before 0.2779s 0.0151s 150
wp 0.2765s 0.0151s 150
Sans l’imitation, main_query prenait 0,005 s et 5 requêtes ; avec elle, 0,27 s et 155, et --spotlight a réduit la recherche au hook wp. Les temps absolus sont plus élevés que dans un navigateur, car WP-CLI tourne sans OPcache, mais la comparaison des étapes reste valable. La sous-commande wp profile hook, qui désigne la fonction exacte, n’a pas fonctionné avec WP-CLI 2.12 sur le banc : elle nécessite une version plus récente de WP-CLI.
Slow log de PHP-FPM. Si vous pouvez modifier les réglages du pool, deux lignes suffisent pour que FPM enregistre une trace de pile pour chaque requête de plus d’une seconde :
; /etc/php/8.3/fpm/pool.d/www.conf
request_slowlog_timeout = 1s
slowlog = /var/log/php8.3-fpm-slow.log
Pour le tester, nous avons ajouté une fonction qui « attend une API » pendant 1,5 s. Le journal a pointé directement vers la fonction et le fichier :
[24-Oct-2026 23:45:18] [pool www] pid 17625
script_filename = /var/www/bench/index.php
[0x00007f56fc215510] usleep() /var/www/bench/wp-content/mu-plugins/r1-slowapi.php:3
[0x00007f56fc2154c0] r1_bench_fetch_remote_rates() /var/www/bench/wp-content/mu-plugins/r1-slowapi.php:4
[0x00007f56fc215440] {closure}() /var/www/bench/wp-includes/class-wp-hook.php:324
...
[0x00007f56fc215100] wp() /var/www/bench/wp-blog-header.php:16
Que faire du coupable : le mettre à jour, le remplacer par une alternative plus légère ou demander au développeur de mettre en cache la réponse de l’API externe (par exemple dans un transient), pour que le site ne l’attende pas à chaque affichage de page.
Étape 4. Workers PHP-FPM
Un worker PHP-FPM traite une seule requête à la fois, les autres attendent dans une file. Nous avons envoyé 20 requêtes lentes simultanées (extension lourde, sans cache) avec pm.max_children = 5 puis 20.

Avec 20 workers, la médiane est passée de 1272 ms à 846 ms et le débit de 14,0 à 18,8 requêtes par seconde. Mais le p95 a à peine bougé (1693 contre 1642 ms) : il n’y avait toujours que deux cœurs CPU, que les workers se disputaient. Chaque worker utilisait en outre 40 à 42 Mo de RAM sur le banc, donc 20 workers occupent environ 0,8 Go rien que pour PHP. Règle approximative : pm.max_children est égal à la mémoire que vous pouvez accorder à PHP divisée par la taille d’un worker :
$ ps --no-headers -o rss,cmd -C php-fpm8.3
10644 php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)
42888 php-fpm: pool www
41148 php-fpm: pool www
Plus de workers raccourcissent la file d’attente, ils ne rendent pas le code plus rapide. Les autres réglages de la pile sont traités dans notre guide sur la configuration du serveur.
Étape 5. Compression gzip
Le HTML de la page d’accueil du banc pesait 56,8 Ko sans compression et 12,3 Ko avec gzip, soit 4,6 fois moins. Le piège : le nginx.conf par défaut d’Ubuntu n’active gzip que pour le HTML, car la ligne gzip_types est commentée. Résultat, la feuille de style des blocs du cœur de WordPress (style.min.css) était servie sans compression, avec 114 706 octets. Après l’ajout de gzip_types, le même fichier pesait 15 608 octets :
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_proxied any;
gzip_types text/css application/javascript application/json image/svg+xml text/xml application/xml application/rss+xml;
Pour vérifier votre propre site, comparez la taille de la réponse avec et sans demande de compression :
$ curl -s -o /dev/null -w "%{size_download}\n" http://127.0.0.1:8080/
56776
$ curl -s -o /dev/null -H "Accept-Encoding: gzip" -w "%{size_download}\n" http://127.0.0.1:8080/
12319
Étape 6. Images et CDN
Les images n’influencent pas le TTFB, mais elles représentent généralement l’essentiel du poids de la page. Nous avons fait passer deux vraies photos PNG par l’éditeur d’images de WordPress (GD), avec la qualité par défaut et sans redimensionnement :
| Photo | PNG | JPEG (qualité 82) | WebP (86) | AVIF (82) |
|---|---|---|---|---|
| 741×500 px | 630 Ko | 84,4 Ko | 76,3 Ko | 86,3 Ko |
| 600×400 px | 456 Ko | 50,1 Ko | 49,0 Ko | 52,5 Ko |
Le vrai gain consiste à ne pas utiliser le PNG pour les photos : 7 à 9 fois plus léger. Passer du JPEG au WebP avec les réglages par défaut de WordPress n’a fait gagner que 2 à 10 %, et l’AVIF via GD en qualité 82 était même plus lourd que le JPEG. Activez donc le WebP, mais n’en attendez pas de miracles. Des tailles d’image correctes comptent davantage (WordPress réduit les originaux de plus de 2560 px de large et génère des tailles intermédiaires), tout comme le chargement différé natif. Nous n’avons mesuré ni les grandes photos de smartphone ni un CDN : un banc local ne présente pas la latence réseau qu’un CDN réduit.
Étape 7. Cache d’objets (Redis)
Un cache d’objets Redis conserve les résultats des requêtes à la base de données d’un affichage de page à l’autre. Sur le banc (Redis 7.0, php-redis et un drop-in minimal), le nombre de requêtes SQL par page est tombé de 22-29 à zéro, le TTFB de 43,7-46,8 ms à 39,6-39,7 ms, et le débit a augmenté de 10 à 20 %. Redis n’a pas sauvé l’extension lourde : ses 150 requêtes directes et l’attente de l’API sont restées, avec 306 ms contre 311-313 ms sans Redis. La base de données du banc est petite et locale, une grande boutique ou une base distante peut donc y gagner davantage. Le cache d’objets est surtout utile là où le cache de page ne s’applique pas : espace client, panier, administration.
Étape 8. Autoload : à vérifier, mais pas en premier
WordPress charge à chaque requête les options marquées autoload. Nous avons ajouté 60 options de 50 Ko chacune (environ 3 Mo au total), et le TTFB médian n’a augmenté que de 0,4 à 4,4 ms, le p90 de 8 à 14 ms. Nettoyez donc l’autoload lorsqu’il atteint des dizaines de mégaoctets ou que l’outil Santé du site signale sa taille, mais pas à la place du cache de page et d’OPcache. Pour vérifier la taille et les options les plus volumineuses :
$ wp option list --autoload=on --format=total_bytes
44236
$ wp db query "SELECT option_name, LENGTH(option_value) bytes FROM wp_options WHERE autoload IN ('yes','on','auto','auto-on') ORDER BY bytes DESC LIMIT 5"
option_name bytes
_transient_wp_core_block_css_files 20490
_transient_wp_styles_for_blocks 10690
rewrite_rules 8495
wp_user_roles 3133
cron 1027
44 Ko, c’est normal pour un site vierge. Si le haut de la liste contient des centaines de kilooctets laissés par une extension supprimée depuis longtemps, vous pouvez retirer cette option de l’autoload avec wp option set-autoload option_name off, après avoir sauvegardé la base de données.
Hébergement mutualisé ou VPS : ce que vous pouvez faire
| Étape | Hébergement mutualisé | VPS |
|---|---|---|
| Cache de page | Une extension de cache (WP Super Cache, W3 Total Cache, LiteSpeed Cache sur LiteSpeed) | nginx fastcgi_cache avec la configuration ci-dessus |
| OPcache | Vérifier avec un fichier de test ; s’il est désactivé, l’activer dans les réglages PHP du panneau ou demander au support | opcache.enable=1 dans le php.ini de FPM |
| Extensions lentes | Query Monitor | Query Monitor, wp profile, slow log de PHP-FPM |
| Workers PHP | Les limites sont fixées par l’hébergeur ; si le site les atteint, il faut une autre offre | pm.max_children selon la mémoire, comme à l’étape 4 |
| Compression | Généralement déjà active, à vérifier avec curl | gzip_types dans nginx |
| Cache d’objets | Seulement si l’hébergeur propose Redis ou Memcached | redis-server, php-redis et une extension de cache d’objets |
Si les pages non mises en cache restent lentes après le cache de page, OPcache et la revue des extensions, et que les workers manquent aux heures de pointe, il est temps d’envisager une offre plus grande ou un VPS ; nous expliquons séparément quel type d’hébergement choisir. Les prérequis serveur de WordPress sont réunis dans notre guide de l’hébergement WordPress, et les offres d’hébergement RichHost sont des hébergements mutualisés sur disques NVMe avec le panneau de contrôle maison doté d’un assistant IA et un certificat SSL Let’s Encrypt gratuit.
FAQ
Faut-il une extension de cache si le serveur a déjà un cache nginx ?
Non. Deux caches de page en même temps compliquent seulement la purge. Gardez-en un : le cache serveur sur un VPS ou une extension sur un hébergement mutualisé.
Pourquoi PageSpeed Insights a-t-il à peine changé après la mise en cache ?
PageSpeed évalue toute la page (images, scripts, polices) et mesure depuis les serveurs de Google, avec la latence réseau. Le cache ne réduit que la partie serveur ; les étapes 5 et 6 traitent le reste.
À partir de combien d’extensions est-ce trop ?
Nous n’avons pas mesuré le nombre d’extensions, mais une seule extension avec 150 requêtes et un appel d’API coûtait environ 265 ms par requête. Ce qui compte, c’est ce que fait chaque extension à chaque affichage de page, pas leur nombre ; Query Monitor et wp profile le montrent.



