On est tombés de notre chaise un mardi matin. Un site client, 60 000 URLs, perdait 30 % de son trafic organique en trois jours. Search Console muette, Core Web Vitals dans le vert, CDN au vert, monitoring de disponibilité au vert. Le problème ? Un déploiement d’edge function qui avait désactivé le cache pour les requêtes sans cookie, mais uniquement quand la réponse était servie en HTTP/2. Le monitoring ne testait qu’en HTTP/1.1. On l’a trouvé en 12 minutes avec trois commandes Bash. Pas en ouvrant un dashboard.
Ce jour-là, on a reposé le diagnostic serveur sur des bases simples : les chiffres les plus bruts viennent du terminal. Les interfaces graphiques agrègent, lissent, masquent. Quand vous cherchez pourquoi Googlebot ralentit son crawl ou pourquoi votre TTFB passe de 120 ms à 1,8 seconde sur la troisième requête, un curl bien placé vaut 20 000 points de données moyennés. Voici comment on teste, sans outil, juste avec ce qui est déjà installé sur votre machine.
Le TTFB synthétique n’est qu’un miroir déformant
Un dashboard de monitoring calcule un TTFB médian sur une fenêtre glissante. Il efface les requêtes froides, les relances TCP, la montée en charge. Si votre serveur met 900 ms au premier montage de connexion HTTP/2 puis 50 ms ensuite, l’outil affiche un joli 60 ms. Googlebot, lui, arrive en visiteur froid. Il mesure le temps réel jusqu’au premier octet, et s’il dépasse un seuil, il réduit sa pression de crawl. La Search Console ne vous dira jamais « votre TTFB froid est trop haut ». Elle vous mettra une baisse de pages indexées.
Ouvrez un terminal. Laissez tomber les rapports.
curl -w "TTFB: %{time_starttransfer}\nTotal: %{time_total}\n" -o /dev/null -s https://votre-site.com
Le time_starttransfer donne le temps jusqu’au premier octet. Lancez la commande trois fois. Si la première passe est trois à cinq fois plus lente que les suivantes, votre cache serveur est mal configuré, ou votre CDN n’enregistre pas les warm-up. Un écart supérieur à 400 ms sur une page statique doit déclencher une revue.
Ajoutez -H "Accept-Encoding: gzip, deflate, br" pour vérifier la compression. Si le TTFB ne bouge pas et que le poids transféré reste identique, votre serveur ne compresse pas ce type de contenu. Googlebot le voit.
On pousse le test avec un User-Agent Googlebot :
curl -w "TTFB: %{time_starttransfer}\n" -o /dev/null -s -H "User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://votre-site.com
Une réponse en 2 secondes signifie que votre rendu côté client balance un squelette vide au bot. Mais ça, c’est un autre sujet qu’on a traité dans l’article sur le state management React Zustand.
Pourquoi le header Age raconte la vérité que votre CDN cache
Les dashboards de CDN affichent un joli ratio de cache hit. 97 % de hit rate, tout va bien. Mais le header Age, lui, ne ment pas. Il indique le nombre de secondes écoulées depuis que l’objet a été mis en cache. Un Age: 86400 sur une page d’accueil dont le TTL est réglé à un jour, c’est normal. Un Age: 3 qui revient toutes les 3 secondes, c’est un objet qui n’est jamais servi depuis le cache, même si le dashboard le comptabilise comme hit.
Testez :
curl -sI https://votre-site.com | grep -i 'Age\|Cache-Status\|X-Cache\|cf-cache-status'
Répétez toutes les 2 secondes. Si le header Age n’augmente pas de façon cohérente avec le temps écoulé, le CDN ne stocke rien. Il vous ment peut-être, mais Googlebot, lui, reçoit des réponses non cachées et consomme du budget crawl sur des ressources qui devraient être statiques. Une boucle de ce type vous alerte avant que la Search Console ne réagisse.
On a déjà vu un site perdre 40 % de son crawl après une migration vers App Router mal configurée, simplement parce que le header Age restait à zéro. Le développeur s’était concentré sur l’optimisation des Core Web Vitals côté client. L’absence de cache serveur a tué la visibilité.
Une boucle qui vaut une heure de réunion
for i in $(seq 1 20); do curl -sI https://votre-site.com/ | grep -i 'age'; sleep 2; done
Vingt itérations, deux secondes d’intervalle. Si le Age dépassé le TTL configuré puis retombe à zéro, vous avez un cache qui purges trop tôt. Si le Age s’arrête à une valeur fixe, le CDN est en mode stale et ne remonte jamais à l’origine. Ce comportement arrive fréquemment quand une règle de cache est modifiée sans invalidation. Les dashboards ne montrent pas le drift.
La même boucle avec grep -i 'x-cache' vous signale immédiatement un basculement intempestif entre HIT et MISS. Quatre lignes de Bash, et vous avez en main ce que trois outils SaaS peinent à reproduire parce qu’ils lissent les échantillons.
Les redirections en chaîne minent votre crawl budget sans bruit
Googlebot suit les redirections, mais pas à l’infini. Une chaîne de trois 301 successives peut paraître anodine. En pratique, chaque saut ajoute de la latence, cumule les DNS lookups si les redirections pointent vers des domaines différents, et grignote le temps alloué au crawl de la page. Sur un site e-commerce avec des redirections de facettes ou de canonicalisation, on arrive vite à quatre sauts. À cinq, Googlebot peut abandonner.
Un curl -I ne suit pas les redirections par défaut. Il affiche la première réponse. Ajoutez -L pour les suivre, et surtout -w pour compter :
curl -Ls -o /dev/null -w "URL effective: %{url_effective}\nCode final: %{http_code}\nNombre de redirections: %{num_redirects}\n" https://ancien-slug/
Si le champ num_redirects affiche 3 ou plus, chaque requête de crawler coûte trois hits. Multipliez par 10 000 URLs, et le gâchis est énorme. Combiné avec du cache mal géré, le budget crawl s’effondre. On a passé des semaines à réparer ce type de fuite après que des consultants SEO ont déployé des redirections par lots sans vérifier les chaînes. L’outil graphique disait « redirection OK ». Le terminal affichait num_redirects: 4.
Testez aussi les redirections avec un curl en HTTP/1.1 :
curl -sI --http1.1 -L https://ancien-slug/ | grep 'HTTP/'
Vous verrez chaque saut. Si un intermédiaire répond en 302 temporaire alors que la cible finale est définitive, corrigez avant que Google n’interprète le signal comme flou. Une règle solide : une redirection par URL, point.
HTTP/2, ALPN et les surprises de déploiement
Un matin, le TTFB d’une landing produit passe de 200 ms à 1,3 seconde. Rien n’a changé dans le code. L’infra a juste activé une règle de micro-pare-feu qui ralentissait le handshake TLS en HTTP/2 quand le client ne négociait pas correctement ALPN. Les navigateurs s’en sortaient tant bien que mal, les bots de test synthétique utilisaient HTTP/1.1, mais Googlebot (qui privilégie HTTP/2) prenait le ralentissement de plein fouet.
Un diagnostic rapide :
curl -sI --http2 https://votre-site.com | grep -i 'HTTP/2\|ALPN'
Le header ALPN proposé par le serveur apparaît dans la sortie de curl -v. Si le protocole négocié est h2 mais que le temps de transfert explose uniquement sur les requêtes sans cookie, le problème est probablement côté infrastructure, pas côté applicatif. Un test comparé HTTP/1.1 vs HTTP/2 avec curl évite de perdre des heures dans les logs applicatifs.
⚠️ Attention : Si un CDN force HTTP/1.1 sur les requêtes provenant d’un subnet Googlebot, le gain de crawl espéré via HTTP/2 n’est jamais réalisé. Vérifiez avec
curl -sI --http2 -H "User-Agent: Googlebot" https://example.com.
Le sujet rejoint celui des outils d’IA capables de lire un log serveur, un débat qu’on a mené en comparant Claude Code et Cursor IDE dans un contexte de CI. Parfois, un copilot vous signale l’anomalie de handshake avant que vous ne lanciez le test manuel. Mais faites quand même le test manuel.
Intégrer le test Bash dans une pipeline CI
Un test ponctuel, c’est bien. Un test automatisé à chaque merge request, c’est la tranquillité. On a vu trop d’équipes pousser en production une configuration CDN qui cassait le cache sans que personne ne s’en aperçoive jusqu’à la chute de trafic. Ajouter un job léger dans la CI prend dix lignes de YAML et cinq secondes d’exécution.
Exemple avec une boucle Bash dans un runner GitLab :
test-cache:
script:
- |
for i in $(seq 1 5); do
age=$(curl -sI https://staging.example.com | grep -i 'age:' | awk '{print $2}')
if [ "$age" -lt 10 ]; then
echo "Cache probablement désactivé"
exit 1
fi
sleep 2
done
Ce test vérifie que le cache vieillit bien. Si l’âge stagne sous 10 secondes, le pipeline échoue. Il ne remplace pas un monitoring complet, mais il attrape les régressions silencieuses là où ça compte : avant la mise en production. On peut étendre la logique à la vérification des redirections, des codes HTTP d’erreur, ou des temps de réponse froids.
Intégré à une pipeline qui déploie des edge functions, ce filet évite les sueurs froides du lundi matin. Le confort d’un tableau de bord ne doit jamais faire oublier qu’un simple curl peut tester ce qu’un dashboard ne montre pas. On ne croit pas que Google préfère le cache. On sait que Googlebot mesure le temps de chargement réel.
Questions fréquentes
Est-ce qu’un test Bash remplace un outil de monitoring synthétique ?
Non, il le complète. Le monitoring synthétique donne une vue continue et alerte sur les dérives. Le test Bash, lui, débusque les anomalies ponctuelles et les états transitoires que les agrégations masquent. Utilisez les deux, mais commencez par un curl pour enquêter.
Pourquoi ne pas utiliser directement la Search Console pour détecter un problème de serveur ? La Search Console rapporte des tendances d’indexation, pas des mesures de TTFB froid ou des chaînes de redirections. Quand elle signale une anomalie, le problème existe souvent depuis plusieurs jours. Le terminal vous donne un retour en temps réel.
Peut-on tester le lazy-loading ou l’hydration React avec Bash ? Non. Bash excelle sur les headers HTTP, le cache, le statut des redirections, le TTFB. Pour tester l’exécution JavaScript, il faut un navigateur headless. Mais une partie des problèmes de performance vient de l’infrastructure, et c’est là que le terminal reste irremplaçable.