Il est 9h47 un mardi matin. Une notification New Relic arrive : CPU à 94% sur les serveurs de production. Trente secondes plus tard, une deuxième alerte : temps de réponse moyen à 8 secondes. Puis un message Slack du client : "Le site est inaccessible, nos équipes ne peuvent plus travailler."
Ce post-mortem est un scénario illustratif, construit à partir de patterns que l'on rencontre régulièrement sur des plateformes Drupal en production. La chronologie, les commandes, les décisions et surtout les erreurs sont représentatives de ce type d'incident.
L'objectif n'est pas de montrer que tout se passe toujours bien. L'objectif est de documenter comment un tel incident se déroule, pourquoi, et ce qu'on met en place pour que ça ne se reproduise plus.
CPU à 94% sur le conteneur applicatif. L'alerte est configurée sur un seuil à 80% pendant plus de 2 minutes. Le déclenchement signifie que la situation dure depuis un moment avant même la notification.
Première action : vérification du dashboard New Relic.
Constat immédiat : le nombre de transactions web en cours a triplé par rapport à la normale. Le throughput est passé de 40 req/s à environ 140 req/s en l'espace de 15 minutes.
Connexion SSH sur l'environnement de production :
ssh deploy@prod-web-01Lecture des access logs en temps réel :
tail -f /var/log/nginx/access.log | grep -v "varnishd"Observation immédiate : un volume anormal de requêtes sur des URLs avec des query strings variés. Exemple de pattern :
185.220.x.x - - [31/May/2026:09:48:12 +0000] "GET /?page=1&sort=asc&filter=new HTTP/1.1" 200 48291
185.220.x.x - - [31/May/2026:09:48:12 +0000] "GET /?page=2&sort=desc&filter=old HTTP/1.1" 200 48301
185.220.x.x - - [31/May/2026:09:48:13 +0000] "GET /?page=1&sort=asc&filter=featured HTTP/1.1" 200 48288Plusieurs centaines de requêtes par minute depuis une plage d'IPs sur la même plage /24. Les query strings sont différents à chaque requête, ce qui empêche tout hit de cache.
Extraction des IPs les plus actives :
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20Résultat : les 5 premières IPs cumulent 3 200 requêtes sur les 10 dernières minutes. Clairement non humain.
Vérification côté Cloudflare : les requêtes arrivent bien depuis Cloudflare (headers CF-Ray présents), mais Cloudflare les transmet toutes. Aucune règle WAF ne bloque ce pattern.
Vérification du cache Varnish :
varnishstat -1 -f MAIN.cache_hit,MAIN.cache_missRatio de cache miss anormalement élevé : 78% de miss au lieu du 15% habituel. Explication : les query strings inconnus font sortir chaque requête du cache. Varnish ne normalise pas les paramètres — il traite ?page=1&sort=asc et ?page=1&sort=desc comme deux URLs différentes.
Chaque miss remonte jusqu'à Drupal. Drupal génère la page complète. Le CPU explose.
Création d'une règle WAF Cloudflare en urgence :
(ip.src in {185.220.0.0/24}) => BlockEffet immédiat : les requêtes depuis ces IPs sont bloquées. Le CPU redescend à 60%. Pas encore normal, mais la pression diminue.
Problème : les bots utilisent plusieurs plages d'IPs. En quelques minutes, de nouvelles IPs prennent le relais depuis d'autres /24.
Plutôt que de chasser les IPs une à une, on passe à un ciblage par comportement. Règle Cloudflare ajoutée :
(http.user_agent contains "python-requests") or
(http.user_agent contains "curl") or
(http.user_agent eq "")
=> Managed Challenge (CAPTCHA)Résultat : le volume de requêtes chute de 60%. Les bots simples (scripts Python sans gestion de JS) ne passent plus.
Le vrai problème de fond reste entier : Varnish ne normalise pas les query strings. Même avec les bots bloqués, un crawl Google mal configuré ou un partenaire qui ajoute des UTM à chaque requête peut reproduire le même effet.
Modification dans la configuration VCL Varnish :
sub vcl_recv {
# Normaliser l'ordre des query strings
set req.url = regsuball(req.url, "([?&])(utm_[^&]*)", "");
set req.url = regsuball(req.url, "([?&])(sort|filter|page)=[^&]*", "");
# Supprimer le ? final si tous les params ont été retirés
set req.url = regsub(req.url, "\?$", "");
}Note : cette modification a nécessité un redéploiement et a été testée en staging avant mise en production. On ne modifie pas Varnish à chaud sans filet.
CPU redescend à 22%. Temps de réponse moyen de retour sous 400ms. Le client est informé. Le site est stable.
Durée totale de l'incident : 44 minutes.
L'incident a trois causes, pas une seule. C'est toujours le cas dans les incidents de production réels.
Cloudflare était configuré en mode proxy standard, sans règle de rate limiting activée. N'importe quelle IP pouvait envoyer autant de requêtes qu'elle voulait sans friction.
Le rate limiting Cloudflare est disponible sur tous les plans payants. Il n'était pas activé parce que "ça n'avait jamais posé de problème". Erreur classique.
Par défaut, Varnish considère chaque combinaison unique de query strings comme une URL distincte. Sans normalisation, un bot qui varie ses paramètres à chaque requête contourne le cache à 100%, même si le contenu rendu est identique.
Cette configuration aurait dû être en place dès le départ. Elle ne l'était pas.
Quand Drupal reçoit plus de requêtes qu'il ne peut en traiter, il continue de les accepter jusqu'à saturation du CPU. Il n'existe pas, nativement dans Drupal, de mécanisme qui dit : "Je suis saturé, je retourne un 503 temporaire plutôt que de continuer à me noyer."
Ce comportement crée une spirale : plus le CPU est chargé, plus chaque requête prend de temps à traiter, plus le nombre de requêtes en attente augmente, plus le CPU monte.
Règle simple, à mettre en place sur tout projet :
Chemin : /*
Seuil : 100 requêtes par IP par minute
Action : Managed Challenge
Durée : 10 minutesPour les endpoints sensibles (/admin, /user, /api) : seuil beaucoup plus bas, 10 req/min, action Block directe.
La configuration VCL doit être revue sur chaque projet pour :
New Relic ou Cloudwatch peuvent monitorer varnishstat. Un ratio de cache miss qui dépasse 40% sur une fenêtre de 5 minutes devrait déclencher une alerte, pas attendre que le CPU soit à 94%.
Maintenir une liste des user agents connus pour le scraping agressif et créer une règle Cloudflare qui les envoie systématiquement en Managed Challenge. Cette liste se construit au fil des incidents — autant commencer tôt.
Règle globale à 100 req/min/IP, règles spécifiques à 10 req/min sur :
/admin/*/user/*/api/*/node/*/editListe blanche des paramètres autorisés dans le cache key. Tout paramètre non listé est supprimé avant que la requête atteigne le cache.
Déclenchement si le nombre de transactions/minute dépasse 3× la moyenne des 7 derniers jours pendant plus de 3 minutes.
Un document dans Confluence qui documente exactement quoi faire en cas de CPU spike :
Un test k6 ou Gatling sur l'environnement de staging pour vérifier que la stack tient à 3× le trafic nominal. Ce test aurait révélé la faiblesse de la normalisation des query strings bien avant l'incident.
La plupart des incidents de production ne sont pas causés par un seul problème. Ici, le bot était le déclencheur, mais le vrai problème était l'absence de rate limiting et la mauvaise configuration du cache. Sans le bot, la plateforme était stable. Mais elle était fragile.
L'alerting sur le CPU seul est insuffisant. À 94% de CPU, il est trop tard. Il faut alerter en amont : ratio de cache miss, throughput anormal, temps de réponse P95 qui monte. Le CPU est une conséquence, pas la cause.
La configuration Varnish n'est pas "set and forget". Elle doit être revue à chaque projet, avec une attention particulière sur la normalisation des URLs. Un template de VCL de base devrait faire partie du kit de démarrage de tout projet Drupal avec Varnish.
Documenter pendant l'incident, pas seulement après. Pendant la résolution, on prenait des notes dans un fil Slack dédié. Ce post-mortem est construit à partir de ces notes. Sans elles, la chronologie aurait été impossible à reconstituer précisément.
Un CPU spike à 100% en production est stressant. Il le sera toujours. Mais la différence entre une équipe qui gère l'incident en 44 minutes avec un plan de prévention solide, et une équipe qui passe 4 heures à chercher la cause dans le noir, c'est la préparation.
Rate limiting, normalisation des query strings, alerting multi-niveaux, et runbook documenté : ce sont quatre éléments simples qui coûtent peu à mettre en place et qui peuvent épargner beaucoup de dégâts — techniques, commerciaux, et relationnels.
Les plateformes Drupal enterprise ne tombent généralement pas à cause d'un bug dans le core. Elles tombent parce qu'un élément de leur stack n'était pas configuré pour résister à un scénario imprévu. Ce post-mortem en est un exemple concret.