TTFB élevé sous WordPress : accélérer la réponse du serveur sans plugins magiques

Cet article vous a aidé ?

Un TTFB élevé est le type de problème de performance qui rend tout le monde confiant et faux en même temps. Le marketing dit « le site est lent », un fournisseur de plugin propose « installez ceci », et votre hébergeur répond « c’est WordPress ». Pendant ce temps, vos utilisateurs regardent une page blanche en attendant le premier octet comme si c’était 2006 et que nous étions tous en connexion commutée.

Ceci n’est pas un comparatif de plugins. C’est un playbook de production pour reprendre le contrôle du temps de réponse serveur : mesurer où le premier octet est bloqué (réseau, TLS, serveur web, PHP, base de données, cache, stockage), corriger le goulot d’étranglement, et éviter les changements qui paraissent rapides sur un graphique mais fondent sous un trafic réel.

Vérification du TTFB : ce que c’est (et ce que ce n’est pas)

Time To First Byte est le temps entre le début de la requête côté client et la réception par le client du premier octet du corps de la réponse. Cela inclut :

  • la résolution DNS (parfois, selon l’outil)
  • la connexion TCP
  • la négociation TLS
  • le routage de la requête (load balancer, reverse proxy)
  • le traitement du serveur web et les attentes en amont
  • le travail applicatif (PHP + WordPress)
  • les requêtes vers la base de données
  • les fins de cache, les stampedes de cache, et les moments « pourquoi Redis est à 100 % CPU ? »
  • la latence de stockage (surtout sur des volumes partagés ou à rafales)

Le TTFB n’est pas un pur « temps backend ». C’est une métrique de bout en bout vue par le client. C’est pourquoi elle est à la fois utile et facile à mal utiliser. Si vous mesurez le TTFB depuis votre ordinateur portable sur le Wi‑Fi d’un hôtel, vous apprenez des choses sur l’hôtel. Si vous le mesurez depuis l’intérieur du même datacenter, vous apprenez sur votre stack.

De plus : un TTFB faible ne garantit pas une page rapide. Il garantit seulement que le serveur a commencé à répondre rapidement. Vous pouvez toujours envoyer 7 Mo de JavaScript et mettre le navigateur en feu. Mais quand le TTFB est élevé, ce n’est rarement un problème front-end. C’est le système qui n’arrive pas à produire le « premier octet » rapidement — généralement parce qu’il attend quelque chose.

Règle d’opinion : ne traitez pas un TTFB élevé comme « WordPress est lent ». Traitez-le comme « une requête est bloquée ». Puis trouvez sur quoi elle est bloquée.

Faits intéressants et un peu d’histoire

  1. Le TTFB a précédé Core Web Vitals. Les ingénieurs l’utilisent depuis des décennies car il correspond bien à la latence côté serveur et au coût des poignées de main réseau.
  2. WordPress a commencé en 2003 comme un fork de b2/cafelog. L’hébergement mutualisé précoce a façonné de nombreux choix par défaut : PHP, MySQL, et des hypothèses de cache « garder simple ».
  3. Le cache d’opcode PHP était autrefois optionnel et chaotique. Avant qu’OPcache ne devienne standard, exécuter WordPress signifiait reparser PHP à chaque requête — la douleur du TTFB était normale.
  4. Le keep-alive HTTP/1.1 (fin des années 1990) a été une révolution discrète pour la performance. Sans lui, les connexions répétées gonflaient massivement la latence perçue.
  5. Les négociations TLS étaient coûteuses. TLS 1.3 moderne a réduit les allers-retours, et la reprise de session est importante pour les visiteurs répétés.
  6. Le cache de requêtes MySQL a été supprimé (effectivement déprécié puis retiré) car il provoquait des contentions de verrous et des performances imprévisibles dans des charges d’écriture — courant dans l’admin WP et WooCommerce.
  7. « Le cache de page règle tout » était vrai dans une certaine mesure quand les sites étaient principalement du trafic anonyme. Les utilisateurs connectés, la personnalisation et les paniers ont changé la donne.
  8. Le cache objet pour WordPress a mûri tard. Le cache objet persistant (Redis/Memcached) est devenu courant à mesure que les sites WP sont devenus plus dynamiques et plugin‑lourds.
  9. Les CDN n’ont pas annihilé les problèmes de TTFB. Ils peuvent les masquer pour les pages en cache, mais les requêtes dynamiques et les misses de cache retournent toujours vers votre origine comme si elle leur devait de l’argent.

Playbook de diagnostic rapide

Quand le TTFB augmente, ne commencez pas par installer des plugins, changer de thème, ou discuter sur Slack. Commencez par répondre rapidement à trois questions :

1) Est-ce le réseau/TLS ou le serveur/l’application ?

  • Mesurez le TTFB depuis l’extérieur et depuis l’intérieur du serveur/VPC.
  • Si le TTFB interne est correct mais que l’externe est mauvais : pensez DNS, négociation TLS, load balancer, routage, perte de paquets, ou WAF.
  • Si l’interne est aussi mauvais : c’est votre stack applicative (PHP, BD, cache, stockage, contention CPU).

2) Est-ce un endpoint isolé ou tout le site ?

  • Homepage seulement ? Cela peut être une requête lente ou un thème/template qui fait trop de travail.
  • wp-admin seulement ? Cela peut être l’auth, des appels externes, ou une contention de verrous BD.
  • Tout (y compris les assets statiques) ? Pensez saturation du serveur web, CPU en surcharge, ou limites réseau en amont.

3) Est-ce « lent tout le temps » ou « lent sous charge » ?

  • Lent même à 1 requête : cherchez les goulots par requête unique (recherches DNS depuis PHP, requête BD lente, latence de stockage, démarrage PHP/OPcache mal configuré).
  • Rapide à 1 requête mais lent à 20 : cherchez le dimensionnement des pools, connexions BD, verrous, stampedes de cache, limitation de débit, saturation CPU.

Condition d’arrêt : Une fois que vous pouvez dire « nous attendons X pendant Y millisecondes », vous avez fini le diagnostic et pouvez commencer à corriger. Si vous ne pouvez pas le dire, vous êtes encore en train de deviner.

Mesurer d’abord : construire une chronologie d’une requête

Le TTFB est un nombre unique. Vous avez besoin d’une chronologie. L’objectif est de découper la requête en phases et d’attacher des chiffres à chaque phase :

  • Recherche de nom (DNS)
  • Connexion (TCP)
  • Connexion application (négociation TLS)
  • Pré-transfert (requête envoyée, attente de réponse)
  • Début du transfert (le TTFB lui‑même)
  • Total (réponse complète)

Côté serveur, vous voulez :

  • timings Nginx/Apache : temps de requête, temps de réponse upstream
  • timings PHP-FPM : durée de requête, traces slowlog
  • timings BD : slow query log, attentes de verrou, taux de hit du buffer pool
  • timings système : pression CPU, file d’exécution, latence I/O, retransmissions réseau

Blague #1 : Si votre monitoring se limite à « rafraîchir la page et plisser les yeux », vous n’avez pas de monitoring ; vous avez un rituel.

Une citation, parce qu’elle est intemporelle : L’espoir n’est pas une stratégie. — James Cameron.

Tâches pratiques avec commandes : quoi lancer, ce que ça signifie, quelle décision prendre

Ce ne sont pas des « essayez peut‑être ceci ». Ce sont les actions que vous pouvez faire sur un hôte Linux WordPress avec Nginx + PHP-FPM + MariaDB/MySQL (les idées sont similaires pour Apache). Chaque tâche inclut une commande, un exemple de sortie, ce que la sortie signifie, et la décision à prendre.

Tâche 1 : Mesurer le TTFB depuis un client avec curl (baseline)

cr0x@server:~$ curl -o /dev/null -s -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/
dns=0.012 connect=0.031 tls=0.082 ttfb=1.247 total=1.392

Sens : DNS/connect/TLS sont faibles ; le TTFB est élevé. Le serveur/l’application met du temps à produire le premier octet.

Décision : Concentrez‑vous sur le traitement à l’origine (attentes Nginx upstream, PHP-FPM, BD, caches), pas sur le réseau ou TLS.

Tâche 2 : Mesurer le TTFB depuis l’intérieur du serveur/VPC (réseau séparé de l’appli)

cr0x@server:~$ curl -o /dev/null -s -w 'connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://127.0.0.1/
connect=0.000 tls=0.000 ttfb=1.106 total=1.219

Sens : Toujours lent en interne. Ce n’est pas votre FAI, pas le DNS, pas le CDN.

Décision : Instrumentez le serveur : logs du serveur web, statut/slowlog PHP-FPM, requêtes lentes BD, vérifications des ressources système.

Tâche 3 : Séparer rapidement statique vs dynamique

cr0x@server:~$ curl -o /dev/null -s -w 'ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/wp-content/uploads/2025/01/logo.png
ttfb=0.045 total=0.046

Sens : Le statique est OK ; le dynamique est le problème.

Décision : Ne perdez pas de temps à optimiser sendfile ou gzip pour l’instant. Allez directement vers PHP/BD/cache.

Tâche 4 : Activer le chronométrage des requêtes dans les logs Nginx (voir le temps upstream)

cr0x@server:~$ sudo grep -R "log_format" /etc/nginx/nginx.conf /etc/nginx/conf.d 2>/dev/null
/etc/nginx/nginx.conf:log_format main '$remote_addr - $host [$time_local] "$request" $status $body_bytes_sent '
/etc/nginx/nginx.conf:'"$http_referer" "$http_user_agent" rt=$request_time urt=$upstream_response_time uct=$upstream_connect_time';

Sens : Vous avez déjà des variables pour request_time et upstream timings. Bien.

Décision : Assurez‑vous que le bloc serveur actif utilise ce log_format puis inspectez les requêtes lentes.

Tâche 5 : Trouver les requêtes les plus lentes dans les logs Nginx (URLs réelles, timings réels)

cr0x@server:~$ sudo awk '{for(i=1;i<=NF;i++) if($i ~ /^rt=/){sub("rt=","",$i); print $i, $0}}' /var/log/nginx/access.log | sort -nr | head -n 5
2.914 203.0.113.10 - example.com [27/Dec/2025:10:11:09 +0000] "GET /product/widget/ HTTP/2.0" 200 84512 "-" "Mozilla/5.0" rt=2.914 urt=2.902 uct=0.000
2.501 203.0.113.11 - example.com [27/Dec/2025:10:10:58 +0000] "GET / HTTP/2.0" 200 112340 "-" "Mozilla/5.0" rt=2.501 urt=2.489 uct=0.000

Sens : request_time (rt) correspond approximativement à upstream_response_time (urt). Nginx n’est pas le goulot ; il attend l’upstream (PHP-FPM).

Décision : Passez à PHP-FPM : saturation de pool, scripts lents, appels externes, OPcache.

Tâche 6 : Vérifier la santé et la saturation du pool PHP-FPM

cr0x@server:~$ curl -s http://127.0.0.1/fpm-status?full | sed -n '1,25p'
pool:                 www
process manager:      dynamic
start time:           27/Dec/2025:09:31:02 +0000
start since:          2430
accepted conn:        18291
listen queue:         17
max listen queue:     94
listen queue len:     128
idle processes:       0
active processes:     24
total processes:      24
max active processes: 24
max children reached: 63
slow requests:        211

Sens : une file d’attente existe et max children reached est non nul. PHP-FPM est saturé : des requêtes attendent avant même de démarrer PHP.

Décision : Ajustez PHP-FPM : augmentez pm.max_children si le CPU/RAM le permet, et réduisez le coût par requête (OPcache, BD, cache). Confirmez aussi que vous ne masquez pas une BD lente derrière « plus d’enfants ».

Tâche 7 : Consulter le slowlog PHP-FPM pour voir où le temps passe

cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm/www-slow.log
[27-Dec-2025 10:11:09]  [pool www] pid 22107
script_filename = /var/www/html/index.php
[0x00007f2a2c1a3f40] curl_exec() /var/www/html/wp-includes/Requests/src/Transport/Curl.php:205
[0x00007f2a2c19b8a0] request() /var/www/html/wp-includes/Requests/src/Session.php:214
[0x00007f2a2c193250] wp_remote_get() /var/www/html/wp-includes/http.php:197
[0x00007f2a2c18aa10] some_plugin_check_updates() /var/www/html/wp-content/plugins/some-plugin/plugin.php:812

Sens : Un plugin effectue un appel HTTP sortant pendant la génération de la page. Cela va tuer le TTFB dès que le service distant est lent ou bloqué.

Décision : Supprimez/remplacez le comportement du plugin, déplacez‑le en cron/arrière‑plan, ou mettez le résultat fortement en cache. Vérifiez aussi le DNS sortant et les règles de pare‑feu.

Tâche 8 : Vérifier qu’OPcache est activé et pas à l’étroit

cr0x@server:~$ php -i | grep -E 'opcache.enable|opcache.memory_consumption|opcache.interned_strings_buffer|opcache.max_accelerated_files|opcache.validate_timestamps'
opcache.enable => On => On
opcache.memory_consumption => 128 => 128
opcache.interned_strings_buffer => 8 => 8
opcache.max_accelerated_files => 10000 => 10000
opcache.validate_timestamps => On => On

Sens : OPcache est activé, mais 128Mo et 10k fichiers peuvent être justes pour un WordPress chargé en plugins. La validation des timestamps en production ajoute aussi des vérifications filesystem.

Décision : Augmentez la mémoire OPcache et le nombre maximal de fichiers ; envisagez opcache.validate_timestamps=0 sur des déploiements immuables (avec un redémarrage lors du déploiement).

Tâche 9 : Vérifier les requêtes lentes en base de données (on ne peut pas cacher les verrous)

cr0x@server:~$ sudo tail -n 20 /var/log/mysql/mysql-slow.log
# Time: 2025-12-27T10:10:58.114221Z
# Query_time: 1.873  Lock_time: 0.001 Rows_sent: 20  Rows_examined: 945321
SET timestamp=1766830258;
SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes';

Sens : Cette requête d’autoload des options balaye trop de lignes. En général c’est un autoload gonflé (les plugins aiment y stocker des données) ou des index manquants combinés à une table volumineuse.

Décision : Réduisez le bloat d’autoload, inspectez la taille de wp_options, et confirmez les index adéquats. Envisagez le cache objet pour éviter des hits répétés, mais réparez aussi le bloat sous‑jacent.

Tâche 10 : Vérifier la santé du buffer pool InnoDB (lisez‑vous depuis le disque ?)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+----------+
| Variable_name                         | Value    |
+---------------------------------------+----------+
| Innodb_buffer_pool_read_requests      | 98765432 |
| Innodb_buffer_pool_reads              | 1234567  |
+---------------------------------------+----------+

Sens : Beaucoup de lectures proviennent du disque (Innodb_buffer_pool_reads n’est pas minime). Cela ajoute de la latence et fait grimper le TTFB sous charge.

Décision : Augmentez innodb_buffer_pool_size (dans les limites de la RAM), réduisez la taille du dataset (nettoyage), et vérifiez la latence de stockage.

Tâche 11 : Repérer en temps réel les attentes de verrous BD (le mode « tout est bloqué »)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST;" | head -n 12
Id	User	Host	db	Command	Time	State	Info
291	wpuser	localhost	wp	Query	12	Sending data	SELECT * FROM wp_posts WHERE post_status='publish' ORDER BY post_date DESC LIMIT 10
305	wpuser	localhost	wp	Query	9	Locked	UPDATE wp_options SET option_value='...' WHERE option_name='woocommerce_sessions'

Sens : Des requêtes attendent des verrous. Cela ressemble souvent à des pics aléatoires de TTFB car certaines requêtes se retrouvent coincées derrière une écriture ou une longue requête.

Décision : Identifiez la requête qui verrouille, corrigez le pattern (index, réduire les écritures de session, éviter les tempêtes d’autocommit), et envisagez d’isoler les tâches admin lourdes du trafic front‑end.

Tâche 12 : Vérifier la pression CPU et la run queue (les requêtes attendent‑elles le CPU ?)

cr0x@server:~$ uptime
 10:12:33 up 12 days,  3:41,  2 users,  load average: 18.42, 17.90, 16.85

Sens : La charge moyenne est très élevée. Si vous avez, disons, 8 vCPU, vous êtes en surcharge et les workers PHP vont faire la queue.

Décision : Réduisez le coût CPU par requête (cache, OPcache, moins de plugins), limitez la concurrence, ou ajoutez du CPU. N’augmentez pas aveuglément les enfants PHP-FPM sur une machine saturée en CPU.

Tâche 13 : Repérer les pics de latence I/O (le stockage peut dominer le TTFB)

cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (server) 	12/27/2025 	_x86_64_	(8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          22.10    0.00    6.30    9.80    0.00   61.80

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s  w_await aqu-sz  %util
nvme0n1         210.0  18560.0     0.0   0.00   18.40    88.38    95.0   9120.0   42.10   5.12  98.20

Sens : Await élevé et util proche de 100 % : le stockage est saturé. Les lectures/écritures BD et les stats filesystem PHP vont s’enrayer.

Décision : Passez à un stockage plus rapide, réduisez l’I/O (buffer pool, cache), enquêtez sur des voisins bruyants (disques partagés), et vérifiez les jobs de sauvegarde ou les tempêtes de logs.

Tâche 14 : Vérifier les retransmissions réseau et pertes (tueurs de latence discrets)

cr0x@server:~$ netstat -s | egrep -i 'retrans|segments retransmited|listen|overflow' | head -n 12
    14572 segments retransmited
    0 listen queue overflows
    0 listen queue drops

Sens : Des retransmissions sont présentes. Si elles montent vite, vous avez perte de paquets ou congestion.

Décision : Vérifiez les erreurs d’interface, le réseau amont, et la santé du load balancer. Des retransmissions élevées peuvent gonfler le TTFB même quand l’appli est correcte.

Tâche 15 : Vérifier que Nginx ne vous bufferise pas en latence (rare mais réel)

cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'proxy_buffering|fastcgi_buffering|fastcgi_read_timeout|keepalive_timeout' | head -n 20
42:	keepalive_timeout 65;
117:	fastcgi_read_timeout 60s;
118:	fastcgi_buffering on;

Sens : Le buffering est activé. Cela aide typiquement le débit, mais peut masquer les réponses en stream et compliquer le débogage. Les timeouts sont normaux.

Décision : Ne désactivez pas le buffering par superstition. Utilisez‑le intentionnellement (le laisser activé dans la plupart des cas). Si vous avez des requêtes longues, corrigez la cause, pas le timeout.

Tâche 16 : Confirmer le keep-alive HTTP et la réutilisation des connexions (réduire le coût des handshakes)

cr0x@server:~$ curl -I -s https://example.com/ | egrep -i 'server:|connection:|keep-alive|alt-svc|via'
server: nginx
connection: keep-alive

Sens : Keep-alive est activé. Bien. Si vous voyiez connection: close partout, vous paieriez des coûts de handshake supplémentaires à répétition.

Décision : Conservez‑le. Réglez les timeouts de façon sensée. Ne « optimisez » pas en fermant les connexions tôt à moins d’aimer la latence auto‑infligée.

Tâche 17 : Rechercher des appels externes depuis WordPress au niveau réseau

cr0x@server:~$ sudo ss -tpn | grep php-fpm | head -n 10
ESTAB 0 0 10.0.0.10:48722 93.184.216.34:443 users:(("php-fpm8.2",pid=22107,fd=15))
ESTAB 0 0 10.0.0.10:48724 151.101.2.132:443 users:(("php-fpm8.2",pid=22111,fd=17))

Sens : Des workers PHP-FPM ouvrent des connexions HTTPS sortantes pendant le service des requêtes. C’est une bombe à retardement pour le TTFB.

Décision : Identifiez quel plugin/thème le fait ; déplacez‑le en tâche de fond ; mettez les résultats en cache ; imposez des timeouts ; bloquez l’egress inattendu si vous le pouvez.

Tâche 18 : Vérification rapide « quoi a changé ? » (mises à jour de paquets, déploiements, redémarrages)

cr0x@server:~$ sudo journalctl -p warning -S "2 hours ago" | head -n 12
Dec 27 09:02:11 server php-fpm8.2[1022]: WARNING: [pool www] server reached pm.max_children setting (24), consider raising it
Dec 27 09:05:44 server mariadbd[881]: Aborted connection 305 to db: 'wp' user: 'wpuser' host: 'localhost' (Got timeout reading communication packets)

Sens : Vous avez une corroboration : saturation PHP-FPM et timeouts de communication BD (possiblement dus à une surcharge).

Décision : Traitez‑le comme un problème de capacité/latence, pas comme une « lenteur mystère WordPress ». Corrigez le dimensionnement du pool et la performance BD ; envisagez une montée en charge.

Corrections qui réduisent réellement le TTFB

1) Ne faites pas de travail à chaque requête : cache de page où c’est pertinent

Pour le trafic anonyme, le cache de page complet est le levier le plus puissant. Pas parce que c’est à la mode, mais parce qu’il retire PHP et BD du chemin de la requête. Vous pouvez le faire via :

  • Nginx fastcgi_cache
  • Cache reverse proxy (Varnish, ou un cache edge géré)
  • CDN pour le HTML (attention aux cookies et à la logique Vary)

Mais vous devez être honnête sur votre site :

  • Si vous êtes lourd en WooCommerce, la majeure partie du trafic utile est connecté ou a des cookies. Le taux de hit du cache de page peut être faible.
  • Si vous avez une forte personnalisation, le cache devient un problème de routage : quoi varie et pourquoi ?

Astuce de décision : si votre page d’accueil est cacheable mais pas les pages produits, le cache reste gagnant. Corrigez d’abord ce que vous pouvez mettre en cache.

2) Cache objet persistant : Redis (ou Memcached) pour les parties répétées de WordPress

WordPress répète les mêmes lectures : options, post meta, relations de taxonomies, transients. Un cache objet persistant réduit les allers‑retours BD et aide le TTFB des pages dynamiques.

À surveiller :

  • Le cache objet ne règle pas les appels HTTP externes lents.
  • Le cache objet ne règle pas la contention de verrous BD causée par des patterns d’écriture.
  • Le cache objet peut se retourner contre vous si vous en faites un tas partagé sans hygiène de clés et sans stratégie d’éviction.

3) PHP-FPM : ajuster pour votre matériel, pas vos espoirs

Les paramètres PHP-FPM sont faciles à changer et plus faciles à ruiner. Votre but est d’éviter la mise en file tout en gardant le CPU et la RAM stables.

  • Symptôme : listen queue et max children reached montent pendant les pics.
  • Correction : Augmentez pm.max_children si vous avez de la marge, et réduisez le temps par requête pour que les enfants se libèrent plus vite.
  • Anti-correction : Doubler les enfants sur une machine juste en RAM. Vous allez swapper, et le swap n’est que de la latence avec des étapes supplémentaires.

Calculez un plafond approximatif : mesurez la mémoire par processus PHP-FPM sous charge, puis fixez les enfants pour que le total ne dépasse pas la RAM disponible moins les besoins BD et cache du système.

4) OPcache : donnez au PHP un cerveau et assez d’espace pour l’utiliser

Sans OPcache, PHP re‑parse les scripts et brûle du CPU et des appels filesystem. Avec OPcache mais trop peu de mémoire, il thrash et vous retournez au démarrage lent.

Faites ceci :

  • Augmentez opcache.memory_consumption pour les sites lourds en plugins.
  • Augmentez opcache.max_accelerated_files pour couvrir thème + plugins.
  • Privilégiez des patterns de déploiement immuables ; désactivez la validation des timestamps quand c’est sûr.

5) Base de données : rendre les requêtes chaudes peu coûteuses et les attentes de verrous rares

La plupart des douleurs BD WordPress ne sont pas exotiques. Ce sont des bases que vous savez déjà devoir faire :

  • Gardez l’autoload de wp_options sous contrôle. Les plugins le gonflent ; vous payez la taxe à chaque requête.
  • Indexez ce que vous interrogez. Beaucoup de plugins livrent des requêtes douteuses en supposant que la BD « s’en sortira ». Elle s’en sortira — en devenant lente.
  • Dotez InnoDB d’un buffer pool suffisant. Si les lectures frappent le disque, le TTFB devient un benchmark de stockage.
  • Surveillez les tables à verrous (sessions, options, postmeta) sous charge d’écriture.

6) Stockage : éliminez la latence queue, pas seulement la latence moyenne

Le TTFB souffre de la latence queue. Le 99e centile compte parce que c’est ce dont se souviennent les utilisateurs et ce qui déclenche vos alertes.

Tueurs courants de TTFB liés au stockage :

  • Volumes réseau partagés avec crédits de rafale (rapide jusqu’à ce que ça ne le soit plus)
  • Sauvegardes occupantes sur le même disque que MySQL
  • Tempêtes de logs (logs debug, access logs, slow logs tous sur un même volume)
  • Churn des métadonnées filesystem (tonnes d’appels stat() si la validation OPcache est active et pas de realpath cache réglé)

7) Appels externes : rendez‑les asynchrones ou faites‑les disparaître

Si WordPress attend des API tierces pendant le traitement de la requête, vous avez transformé votre SLA en passe‑temps pour eux. Imposer des timeouts stricts sur les appels sortants, mettre en cache les résultats, et déplacer le travail non critique en cron/queue.

Blague #2 : Les API tierces sont comme des ascenseurs : au moment où vous êtes en retard, tous sont occupés.

8) CDN et edge : utilisez‑les pour réduire les hits à l’origine, pas pour cacher un incendie à l’origine

Un CDN aide quand vous pouvez mettre en cache le HTML et les assets statiques. Il réduit aussi les coûts TLS et TCP pour les utilisateurs globaux. Mais si votre origine a un TTFB de 1,5 s pour les pages dynamiques non mises en cache, votre CDN n’est qu’un messager poli livrant de mauvaises nouvelles plus rapidement.

Trois mini‑histoires du monde de l’entreprise (et les leçons qu’elles ont payées)

Histoire 1 : L’incident causé par une mauvaise hypothèse

L’équipe avait un site WordPress qui était « correct » au bureau et « affreux » pour les utilisateurs à l’étranger. Ils ont regardé quelques rapports Lighthouse et ont blâmé le thème. Un sprint plus tard, le thème était plus léger et le problème… inchangé.

Pendant la revue d’incident, quelqu’un a finalement mesuré le TTFB depuis deux endroits : depuis le serveur lui‑même et depuis une région distante. Dans le VPC, le TTFB était constamment bas. De l’extérieur, il grimpait et oscillait. L’application n’était pas lente ; le chemin vers l’application l’était.

Le load balancer terminait TLS, et un jeu de règles WAF faisait une inspection approfondie de chaque requête — y compris les requêtes en cache. Sous trafic de pointe, la file d’inspection grandissait, et le TTFB aussi. Tout le monde supposait « WAF = gratuit ». Ce n’était pas le cas.

Ils ont corrigé cela en restreignant le jeu de règles, en contournant l’inspection pour des chemins statiques connus, et en ajoutant de la capacité là où il le fallait. Les changements de thème ont aidé un peu, mais la vraie correction a été d’admettre l’hypothèse erronée : « TTFB = temps PHP ». Ce n’était pas vrai.

Histoire 2 : L’optimisation qui s’est retournée contre eux

Une autre entreprise a décidé de gagner la bataille du TTFB en augmentant vigoureusement pm.max_children de PHP-FPM. Le graphique avait l’air génial en staging. En production, ce fut un succès spectaculaire pendant environ dix minutes.

Puis la base de données a commencé à timeout. Pas parce qu’elle était sous‑dimensionnée, mais parce que la nouvelle concurrence PHP l’a estampillée. Le nombre de requêtes simultanées a grimpé, le buffer pool a churné, et la contention de verrous est apparue à des endroits que personne n’avait profilés. Le TTFB est passé de « moyen » à « effondrement ».

Pour rendre ça plus épicé, l’usage mémoire a bondi. La machine a commencé à swapper. Une fois le swap invité, la latence devient une danse interprétative : parfois correcte, parfois horrible, toujours difficile à raisonner.

La correction finale était ennuyeuse : réduire la concurrence PHP à ce que la BD peut gérer, activer le cache objet persistant, nettoyer quelques requêtes pathologiques, et dimensionner correctement le buffer pool BD. Ils ont aussi ajouté un test de charge mesurant la mise en file, pas seulement la latence moyenne. La leçon : si vous « optimisez » en augmentant le parallélisme, vous n’optimisez pas — vous négociez avec votre goulot.

Histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la journée

Un site média faisait tourner WordPress derrière Nginx et une couche de cache. Ils avaient une habitude que j’aime : chaque fois que la performance semblait étrange, ils regardaient trois logs avant de toucher à quoi que ce soit — le access log Nginx avec timings upstream, le slowlog PHP-FPM, et le slow query log MySQL.

Un après‑midi, le TTFB a monté par intermittence. La personne de garde aurait pu faire la danse habituelle : redémarrer PHP, vider les caches, blâmer le dernier déploiement. Au lieu de cela, ils ont suivi l’habitude. Nginx montrait le temps upstream en pic. Le slowlog PHP montrait des workers coincés dans des appels filesystem. Le slow log MySQL était calme.

Ils ont vérifié les métriques de stockage et trouvé des pics de latence alignés avec une sauvegarde automatisée qui avait dérivé en heures ouvrables après un changement de fuseau horaire. Personne n’a eu besoin de deviner. Personne n’a eu à argumenter. Ils ont déplacé la fenêtre de sauvegarde et ajouté une alerte sur le await disque soutenu.

Cela n’était pas du génie héroïque. C’était de l’hygiène opérationnelle banale. Le genre qui paraît peu impressionnant jusqu’à ce qu’il vous sauve le week‑end.

Erreurs courantes : symptôme → cause racine → correction

  • Symptôme : TTFB élevé seulement à la première requête après un déploiement/redémarrage
    Cause racine : OPcache froid, cache page/objet froid, warmup JIT, cache DNS vide
    Correction : Pré‑chauffer les caches (précharger des URLs), s’assurer que OPcache est dimensionné correctement, éviter les redémarrages fréquents, et pré‑résoudre DNS sortants critiques si vous devez appeler des services externes.
  • Symptôme : Pics de TTFB pendant les pointes, correct hors‑pic
    Cause racine : saturation et mise en file de PHP-FPM, limites de connexions BD, stampede de cache
    Correction : Adapter pm.max_children, ajouter cache de page/objet, implémenter verrouillage de cache ou coalescence de requêtes, et réduire les endpoints coûteux non mis en cache.
  • Symptôme : wp-admin lent, front‑end généralement OK
    Cause racine : Les requêtes d’admin contournent le cache de page ; requêtes lourdes, vérifications de mise à jour, appels distants
    Correction : Profilage des endpoints admin ; désactiver ou planifier les vérifications de mise à jour ; corriger les requêtes lentes ; séparer le trafic admin si nécessaire.
  • Symptôme : Assets statiques rapides, HTML lent
    Cause racine : Travail PHP/BD dominant ; pas de cache efficace ; appels externes lents
    Correction : Ajouter cache de page où sûr ; ajouter cache objet persistant ; supprimer les appels synchrones externes ; régler OPcache et BD.
  • Symptôme : Outliers aléatoires de TTFB (p95/p99 moches)
    Cause racine : latence queue du stockage, attentes de verrous BD, effets de voisin bruyant, cron jobs chevauchant le trafic
    Correction : Déplacer la BD sur stockage stable à faible latence ; réduire la contention de verrous ; reprogrammer les cron/sauvegardes lourdes ; ajouter des alertes sur la latence I/O et les attentes de verrous.
  • Symptôme : Augmenter les enfants PHP-FPM a empiré les choses
    Cause racine : La BD est devenue le goulot ; pression mémoire a causé du swap
    Correction : Réduire la concurrence ; mesurer le RSS par processus ; optimiser BD et caches d’abord ; scaler verticalement/horizontalement de manière réfléchie.
  • Symptôme : Monitoring externe montre un TTFB élevé, contrôles internes semblent normaux
    Cause racine : DNS, négociation TLS, queueing WAF/LB, perte de paquets
    Correction : Mesurer avec curl depuis plusieurs régions ; inspecter les métriques LB/WAF ; optimiser la reprise TLS ; corriger la perte réseau ; contourner l’inspection coûteuse pour des chemins sûrs.
  • Symptôme : Le TTFB a empiré après activation d’un plugin de cache
    Cause racine : Configuration de cache provoquant contention, tempêtes de purge, ou désactivant des patterns favorables à OPcache
    Correction : Préférer le cache au niveau Nginx/proxy quand possible ; s’assurer que les clés de cache sont correctes ; éviter de purger tout le cache pour des changements mineurs ; valider avec des tests de charge.

Listes de contrôle / plan étape par étape

Étape par étape : de « TTFB est mauvais » à une correction spécifique en une session de travail

  1. Établir des bases : lancer des timings curl depuis votre laptop et depuis le serveur. Sauvegarder les chiffres.
  2. Confirmer la portée : tester la homepage, un article typique, une page produit, et un asset statique. Identifier la lenteur « uniquement dynamique ».
  3. Lire la vérité du serveur web : activer/vérifier les champs Nginx rt et urt. Trouver les URLs lentes principales.
  4. Vérifier la saturation PHP-FPM : regarder la listen queue et le max children reached. Décider si vous êtes en mise en file.
  5. Activer le slowlog PHP (si pas déjà) avec un seuil vivable (par ex. 2s). Capturer des traces de pile pendant la lenteur.
  6. Vérifier les appels sortants : stacks slowlog et sortie de ss. Si présents, corriger cela en premier — rien d’autre n’a d’importance.
  7. Vérifier les requêtes lentes BD et les verrous : activer le slow query log ; inspecter le processlist pour les verrous.
  8. Vérifier la latence stockage : utiliser iostat. Si await et util sont mauvais, traiter le stockage comme suspect principal.
  9. Implémenter un changement à la fois : couche de cache, réglage OPcache, dimensionnement buffer pool BD, dimensionnement pool PHP.
  10. Valider avec les mêmes mesures : timings curl et timings de logs. Conserver une preuve avant/après.

Checklist opérationnelle : éviter que le TTFB ne régresse le mois prochain

  • Les access logs incluent le temps de requête et le temps de réponse upstream.
  • Le endpoint de statut PHP-FPM est protégé et monitoré (queue, processus actifs, max children reached).
  • Les slow logs sont activés (PHP et BD) avec une rétention raisonnable.
  • Les sauvegardes et les cron jobs lourds sont planifiés et surveillés pour dérive d’exécution.
  • Le processus de déploiement chauffe les caches ou du moins évite des cold starts simultanés sur tous les nœuds.
  • Il existe un test de charge mesurant p95/p99, pas seulement la moyenne.
  • Une politique de plugins existe : tout ce qui fait de l’I/O externe synchrone dans le chemin de requête est considéré comme un risque en production.

FAQ

1) Quel est un « bon » TTFB pour WordPress ?

Pour du HTML mis en cache à l’edge ou proxy : des dizaines de millisecondes à quelques centaines. Pour un WordPress dynamique non caché : viser un p50 sous 400 ms est raisonnable sur une stack saine. Votre p95 est ce qui vous fera du tort publiquement.

2) Pourquoi le TTFB est‑il élevé alors que l’usage CPU semble faible ?

Parce que vous pouvez être bloqué sans être lié au CPU : en attente d’I/O disque, verrous BD, appels HTTP sortants, timeouts DNS, ou une file PHP-FPM saturée où les workers sont coincés ailleurs.

3) Ai‑je besoin d’un plugin de cache ?

Pas forcément. Le cache au niveau serveur/proxy est souvent plus prévisible et plus rapide. Les plugins peuvent aider à gérer les purges de cache, mais ils ajoutent aussi complexité et modes de défaillance. Si vous en utilisez un, traitez‑le comme de l’infrastructure : configurez, mesurez et testez.

4) Si j’ajoute Redis, le TTFB va‑t‑il magiquement chuter ?

Non. Redis aide quand la BD effectue des lectures répétitives (options, meta, taxonomie). Il ne corrigera pas un code PHP lent, des appels externes, ou la contention BD due à des écritures massives. C’est un levier, pas une religion.

5) Dois‑je simplement augmenter pm.max_children de PHP‑FPM ?

Seulement si vous avez prouvé que vous faites la queue et que vous avez de la marge CPU/RAM. Sinon, vous augmenterez la concurrence jusqu’à atteindre le vrai goulot (souvent la BD) et empirerez la latence.

6) Pourquoi le TTFB pique‑t‑il aléatoirement la nuit ?

Parce que la « nuit » est souvent le moment des cron jobs et des sauvegardes. Cherchez les dumps BD, snapshots filesystem, rotation de logs, scans anti‑malware, ou des jobs plugin programmés qui ont dérivé dans les fenêtres de trafic.

7) Comment savoir si la base de données est le goulot sans APM sophistiqué ?

Corrélez : pics dans le temps de réponse upstream Nginx + slowlog PHP montrant du temps dans les appels BD + slow log/attentes de verrous MySQL affichant de l’activité. Si le temps BD domine, vous le verrez à travers ces signaux.

8) HTTP/2 ou HTTP/3 réduisent‑ils le TTFB ?

Ils peuvent réduire le coût de connexion et améliorer le multiplexage, surtout sur des réseaux perdus. Mais si votre origine met 1–2 secondes à construire le HTML, le protocole ne vous sauvera pas. Corrigez l’origine d’abord, puis profitez des gains protocolaires.

9) Un CDN vaut‑il le coup si mon TTFB est élevé ?

Oui pour les assets statiques et le HTML cacheable, parce que ça réduit la charge sur l’origine et améliore la latence globale. Mais ne l’utilisez pas comme excuse pour ignorer la performance de l’origine. Les misses de cache révéleront toujours la vérité.

10) Pourquoi wp-admin est plus lent après avoir « optimisé » le front‑end ?

Parce que la plupart des optimisations étaient pour des pages cacheables. wp-admin est dynamique, contourne souvent les caches, et déclenche des comportements plugin supplémentaires (vérifications de mise à jour, analytics, appels distants). Profilez‑le séparément.

Étapes suivantes réalisables cette semaine

  1. Obtenir les chiffres : timings curl depuis l’intérieur et l’extérieur, et logs Nginx avec timings upstream.
  2. Prouver ou infirmer la mise en file PHP-FPM : activer et vérifier /fpm-status ; surveiller max children reached et la listen queue pendant la charge.
  3. Activer le slowlog PHP et attendre qu’il vous dise la vérité inconfortable (généralement appels externes ou un chemin de code coûteux).
  4. Activer le slow query log BD pour un jour, puis corriger les principaux coupables et le bloat d’autoload.
  5. Valider la latence stockage avec iostat pendant le pic. Si vous êtes sur des volumes bursty ou partagés, planifier un déplacement.
  6. Ajouter du caching délibérément : cache de page pour le trafic anonyme ; cache objet pour les répétitions dynamiques ; garder les clés de cache et le comportement de purge sensés.
  7. Retester et verrouiller : mêmes mesures curl, même inspection de logs, et un petit test de charge. Notez les résultats pour que votre futur vous ne refasse pas le même travail de détective.

Si vous faites les mesures et que vous ne pouvez toujours pas expliquer où le temps passe, c’est le signal pour ajouter du tracing (même léger) et arrêter de deviner. Les systèmes de production ne répondent pas aux impressions.

← Précédent
Indexation MariaDB vs PostgreSQL : pourquoi les « meilleures pratiques » se retournent contre les charges réelles
Suivant →
Timeouts des conteneurs Docker : ajuster les retries correctement (pas infinis)

Laisser un commentaire