Symptôme. Un client nous a contactés parce que son site, correctement mis en cache côté navigateur et servi par un CDN pour les images, affichait malgré tout un Time To First Byte supérieur à deux secondes sur ses pages dynamiques. Les images optimisées, la minification du CSS, tout ce que les outils habituels recommandaient était déjà en place. Le problème se situait ailleurs, avant même que le premier octet HTML ne parte du serveur.
Diagnostic : commencer par isoler réseau et serveur
La première étape consiste à savoir si le délai vient du réseau (latence, DNS, TLS) ou du traitement côté serveur. La commande curl avec un format de sortie personnalisé donne cette décomposition en une seule requête :
curl -o /dev/null -s -w "dns: %{time_namelookup}s\nconnect: %{time_connect}s\nttfb: %{time_starttransfer}s\ntotal: %{time_total}s\n" https://exemple-client.test/
Sur ce dossier, time_namelookup et time_connect étaient négligeables, sous 50 millisecondes chacun. L’essentiel du délai se situait entre la connexion établie et time_starttransfer : le serveur mettait bien plus d’une seconde à produire la première ligne de réponse. Le réseau était disculpé, il fallait regarder du côté de PHP et de la base.

Diagnostic : activer le slow log de PHP-FPM
PHP-FPM propose un journal des requêtes lentes qui capture la pile d’appels exacte au moment où une requête dépasse un seuil défini. C’est l’outil le plus direct pour trouver la fonction fautive sans installer de profiler complet :
; dans le pool www.conf
request_slowlog_timeout = 1s
slowlog = /var/log/php-fpm/slow.log
Après rechargement du service, chaque requête dépassant une seconde écrit sa pile d’appels complète dans ce fichier. Sur ce projet, le slow log a immédiatement pointé vers une fonction de génération de menu appelée à chaque chargement de page, qui recalculait l’arborescence complète du site à chaque requête au lieu d’utiliser le cache d’objets.
tail -n 50 /var/log/php-fpm/slow.log
Correctif : mettre en cache ce qui peut l’être
La fonction fautive appelait wp_get_nav_menu_items() sans jamais passer par le cache transitoire. Le correctif a consisté à envelopper l’appel dans un cache d’objets avec une invalidation sur la sauvegarde des menus :
function wpm_get_menu_cached( $location ) {
$cache_key = 'wpm_menu_' . $location;
$items = wp_cache_get( $cache_key, 'wpm_menus' );
if ( false === $items ) {
$items = wp_get_nav_menu_items( $location );
wp_cache_set( $cache_key, $items, 'wpm_menus', HOUR_IN_SECONDS );
}
return $items;
}
Ce seul correctif a fait passer le TTFB de 2,1 secondes à environ 0,9 seconde. Le reste du délai provenait d’une deuxième cause, plus discrète.
Diagnostic : le cas des requêtes en cascade
Une fois la fonction de menu corrigée, le slow log a révélé une deuxième pile d’appels, cette fois liée à une extension de widgets qui interrogeait la base de données une fois par widget affiché, sans regroupement. Une simple activation de Query Monitor en environnement de recette a confirmé 43 requêtes SQL redondantes sur cette seule zone de la page.
Prévention : ce qui évite de revivre ce scénario
- Ne changez jamais deux paramètres en même temps lors d’un diagnostic : impossible de savoir lequel a réellement agi.
- Gardez le slow log PHP-FPM actif en permanence avec un seuil raisonnable (1 seconde), il ne coûte presque rien et fait gagner un temps précieux le jour où un problème survient.
- Revérifiez le TTFB après chaque mise à jour majeure de thème ou d’extension : une régression de performance passe rarement par un message d’erreur.
Un TTFB élevé n’est jamais « la faute du serveur » par défaut : c’est une conséquence, il faut remonter à la cause avant de changer d’offre d’hébergement.
En résumé
La méthode reste la même quel que soit le projet : décomposer le délai avec curl -w, isoler PHP avec le slow log de PHP-FPM, corriger une seule chose à la fois, puis remesurer. Sur ce dossier, deux correctifs ciblés ont suffi à diviser le TTFB par cinq, sans changer d’hébergeur ni ajouter de couche de cache supplémentaire.