WordPress 502 Bad Gateway : PHP-FPM, Nginx, Cloudflare — comment identifier le responsable

Cet article vous a aidé ?

Les 502 sont le pire type de panne : celles qui déclenchent une bataille sur les responsabilités. Cloudflare pointe vers votre origine. Nginx pointe vers PHP-FPM. PHP-FPM pointe vers WordPress. WordPress pointe vers un plugin écrit en 2013 par quelqu’un qui a depuis trouvé la paix dans la menuiserie.

Ce guide adopte une approche orientée production pour arrêter les suppositions. Vous allez identifier où la requête est morte, pourquoi elle est morte, et quel changement la répare réellement — sans transformer votre serveur en foire scientifique.

Ce que signifie réellement un 502 (dans cette pile)

Un 502 Bad Gateway n’est pas une erreur WordPress. C’est une erreur de proxy. Quelque chose qui agit comme une passerelle (Cloudflare, Nginx, un load balancer) a essayé de parler à un amont (votre origine, PHP-FPM) et a reçu des données corrompues, rien du tout, ou a subi un timeout produisant un 502.

Cette distinction est importante parce que vous déboguez les 502 en prouvant où l’échec est survenu. Votre objectif n’est pas « réparer WordPress ». Votre objectif est « identifier le premier composant qui n’a pas fait son travail ». Une fois que vous savez cela, la correction devient évidente. Souvent ennuyeuse. Habituellement efficace.

Deux réalités courantes :

  • 502 depuis Cloudflare : Cloudflare n’a pas obtenu de réponse valide de votre origine dans les temps, ou l’origine a clos la connexion. Cloudflare n’a pas exécuté votre code PHP. Il a demandé poliment à l’origine et s’est fait ignorer.
  • 502 depuis Nginx : Nginx n’a pas pu parler à PHP-FPM (socket refusé, permission refusée, amont clos, timeout d’amont). Nginx n’a pas non plus exécuté votre PHP. Il a tenté de le confier et la passation a échoué.

Voici le modèle mental que j’utilise en astreinte : les 502 sont des échecs de passation. La question la plus précieuse est : quelle passation ?

Plan de diagnostic rapide

Si vous ne retenez rien d’autre, retenez cet ordre. Il évite les blâmes et vous amène rapidement au coupable.

Étape 1 : Classifier la source du 502 (edge vs origine)

  • Si les utilisateurs voient une page d’erreur brandée Cloudflare, commencez par les en-têtes Cloudflare et la joignabilité de l’origine.
  • Si les utilisateurs voient la page d’erreur de votre site ou un simple « 502 Bad Gateway » du serveur, commencez par les logs Nginx et PHP-FPM.

Étape 2 : Vérifier le log d’erreurs Nginx pour la chaîne d’erreur upstream

C’est le sérum de vérité le plus rapide. La formulation exacte vous indique s’il s’agit d’« upstream timed out », de « connect() failed », d’« upstream prematurely closed connection » ou de « no live upstreams ». Chacun correspond à une correction différente.

Étape 3 : Vérifier la santé et la capacité de PHP-FPM

Cherchez max_children reached, des entrées slowlog, ou un socket de pool mort/injoignable. Ne faites pas d’ajustements à l’aveugle. Confirmez la saturation et la marge mémoire disponible.

Étape 4 : Corréler par temps et chemin de requête

La plupart des 502 ne sont pas aléatoires. Ils se regroupent autour de points de terminaison spécifiques (wp-admin, wp-cron.php, /checkout, une action AJAX). Trouvez le chemin. Puis trouvez quel code il exécute.

Étape 5 : Décider : connectivité, capacité ou latence

  • Connectivité : permissions du socket, backlog d’écoute, SELinux/AppArmor, pare-feu, mauvaise adresse d’amont.
  • Capacité : trop peu de workers, CPU saturé, MySQL bloqué, waits IO, pression mémoire.
  • Latence : requêtes lentes, appels API externes, plugin défaillant, disque lent, blocages DNS.

Blague #1 : Un 502 est la façon dont votre serveur dit « j’ai essayé », ce que j’écris aussi dans les rétros d’incident quand les graphes manquent.

Suivre la requête : Cloudflare → Nginx → PHP-FPM → WordPress

Imaginez la requête comme une course de relais :

  1. Navigateurs → Cloudflare : l’utilisateur touche le bord. Cloudflare applique des règles WAF, du cache, des vérifications bot.
  2. Cloudflare → Origine (votre serveur) : Cloudflare se connecte à votre Nginx/Apache, généralement sur 443.
  3. Nginx → PHP-FPM : Nginx proxie les requêtes PHP vers un socket UNIX ou un port TCP.
  4. PHP-FPM → WordPress : PHP exécute le code, appelle MySQL, peut-être Redis, peut-être des API externes.
  5. La réponse remonte.

Un 502 survient quand un relayeur lâche le témoin. L’essentiel est de trouver quel relayeur et pourquoi. C’est pour ça que les logs et la corrélation temporelle comptent plus que l’intuition.

Faits et contexte intéressants (pour comprendre les erreurs)

  • Fait 1 : « Bad Gateway » est un statut HTTP défini pour des intermédiaires, pas pour le code applicatif. Votre application PHP ne génère généralement pas un 502 volontairement.
  • Fait 2 : Nginx est devenu populaire en partie grâce à son modèle événementiel qui gère beaucoup de connexions inactives efficacement — excellent pour les clients lents, pas une baguette magique pour des upstreams lents.
  • Fait 3 : PHP-FPM est le gestionnaire de processus de facto pour PHP car il isole l’exécution PHP et laisse le serveur web léger ; cette séparation est précisément la raison pour laquelle existent les erreurs de « passation ».
  • Fait 4 : Le « 522 » de Cloudflare est célèbre, mais le « 502/504 au bord » est souvent juste la lenteur de l’origine exprimée différemment — les timeouts sont des décisions de politique, pas des vérités universelles.
  • Fait 5 : Le point d’accès admin-ajax de WordPress peut devenir un hotspot de haute concurrence ; c’est effectivement un endpoint RPC que beaucoup de plugins abusent.
  • Fait 6 : Les paramètres keepalive et buffering ont historiquement été réglés pour des upstreams coûteux et des clients lents ; les stacks modernes héritent encore de ces réglages, et un mauvais réglage peut amplifier les défaillances.
  • Fait 7 : L’avertissement classique « max_children reached » dans PHP-FPM n’est pas une erreur en soi ; c’est une alarme de capacité qui corrèle souvent avec des 502/504 au proxy.
  • Fait 8 : Les sockets UNIX sont légèrement plus rapides que le loopback TCP pour Nginx→FPM, mais les erreurs de permission et de chemin sont plus courantes avec les sockets qu’avec le TCP.
  • Fait 9 : Beaucoup de 502 « aléatoires » surviennent lors de déploiements parce que les reloads PHP-FPM peuvent brièvement fermer les sockets ou réinitialiser les connexions ; la configuration de reloads gracieux compte.

Tâches pratiques : commandes, sorties, décisions (12+)

Voici les tâches que j’exécute réellement sous pression. Chacune a trois parties : une commande, ce que signifie la sortie, et la décision à prendre. Ajustez les noms de service (php8.2-fpm vs php-fpm) selon votre distro.

Task 1: Confirm where the 502 is generated (headers)

cr0x@server:~$ curl -sS -D - -o /dev/null https://example.com/ | sed -n '1,20p'
HTTP/2 502
date: Fri, 26 Dec 2025 14:03:12 GMT
content-type: text/html; charset=UTF-8
server: cloudflare
cf-ray: 88b12345abcd1234-FRA

Ce que cela signifie : server: cloudflare et un cf-ray indiquent que le bord renvoie le 502. Cela n’innocente pas votre origine.

Décision : Testez immédiatement l’origine directement (Task 2). Si l’origine va bien, regardez la configuration Cloudflare/WAF. Si l’origine échoue aussi, déboguez l’origine.

Task 2: Bypass Cloudflare to test origin response

cr0x@server:~$ curl -sS -D - -o /dev/null --resolve example.com:443:203.0.113.10 https://example.com/ | sed -n '1,20p'
HTTP/2 200
server: nginx
content-type: text/html; charset=UTF-8

Ce que cela signifie : Cela cible 203.0.113.10 tout en utilisant SNI pour example.com. Vous voyez la réponse de l’origine.

Décision : Si l’origine renvoie 200 alors que Cloudflare renvoie 502, suspectez la connectivité Cloudflare→origine, un mauvais mode TLS, un combat WAF/bot, ou un rate limiting qui cible des IP Cloudflare spécifiques. Si l’origine échoue aussi, poursuivez le débogage Nginx/PHP-FPM.

Task 3: See the exact Nginx upstream error string

cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/26 14:03:10 [error] 21344#21344: *99172 connect() to unix:/run/php/php8.2-fpm.sock failed (111: Connection refused) while connecting to upstream, client: 172.67.10.20, server: example.com, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"

Ce que cela signifie : Nginx a tenté de se connecter au socket PHP-FPM et a obtenu Connection refused. Ce n’est pas un plugin lent. C’est une passation cassée.

Décision : Vérifiez si PHP-FPM tourne et écoute ce socket (Task 4, Task 5). S’il tourne, cela peut être un mauvais chemin de socket, des permissions, ou une boucle de crash.

Task 4: Check PHP-FPM service health

cr0x@server:~$ systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled)
     Active: active (running) since Fri 2025-12-26 13:58:22 UTC; 4min 53s
       Docs: man:php-fpm8.2(8)
   Main PID: 20811 (php-fpm8.2)
     Status: "Processes active: 12, idle: 4, Requests: 983, slow: 3, Traffic: 0.8req/sec"

Ce que cela signifie : Le service est actif. Cela ne garantit pas qu’il écoute là où Nginx s’attend.

Décision : Vérifiez l’existence et la propriété du socket (Task 5) et la configuration du pool FPM pour listen.

Task 5: Verify the socket exists and permissions make sense

cr0x@server:~$ sudo ls -l /run/php/php8.2-fpm.sock
srw-rw---- 1 www-data www-data 0 Dec 26 13:58 /run/php/php8.2-fpm.sock

Ce que cela signifie : Le socket existe, appartient à www-data, mode 660. Nginx doit s’exécuter en tant que www-data ou appartenir au groupe www-data pour se connecter.

Décision : Si Nginx tourne sous l’utilisateur nginx, soit ajustez les groupes, soit définissez listen.owner, listen.group et listen.mode dans la config du pool FPM. Si le socket manque, PHP-FPM ne le crée pas — vérifiez les logs (Task 6).

Task 6: Read PHP-FPM logs and journal for pool errors

cr0x@server:~$ sudo journalctl -u php8.2-fpm -n 80 --no-pager
Dec 26 14:02:57 server php-fpm8.2[20811]: [WARNING] [pool www] server reached pm.max_children setting (20), consider raising it
Dec 26 14:03:01 server php-fpm8.2[20811]: [ERROR] [pool www] child 22109 exited on signal 11 (SIGSEGV) after 12.345678 seconds from start
Dec 26 14:03:01 server php-fpm8.2[20811]: [NOTICE] [pool www] child 22117 started

Ce que cela signifie : Deux problèmes séparés : saturation de capacité (max_children) et instabilité (segfault). Le segfault provient souvent d’une extension boguée, d’un problème OPcache, ou d’une corruption mémoire.

Décision : Si vous voyez des avertissements max_children, confirmez la corrélation avec des 502 et ajustez prudemment (Task 11). Si vous voyez des segfaults, désactivez les extensions suspectes, vérifiez les paquets PHP, et envisagez un rollback des changements récents. Traitez les segfaults comme un incident de fiabilité, pas comme « WordPress qui fait son truc ».

Task 7: Confirm Nginx is actually pointing at the right upstream

cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '/fastcgi_pass/,+3p'
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

Ce que cela signifie : Nginx attend ce chemin de socket. Si le pool FPM écoute sur un chemin différent ou sur un port TCP, vous avez construit un système distribué à l’intérieur d’un seul serveur.

Décision : Alignez fastcgi_pass avec le listen du pool. Évitez les « vérités multiples » (un socket dans un fichier, un port TCP dans un autre).

Task 8: Check for upstream timeouts vs connection errors

cr0x@server:~$ sudo grep -E "upstream timed out|prematurely closed|Connection refused|no live upstreams" -n /var/log/nginx/error.log | tail -n 8
41288:2025/12/26 14:01:21 [error] 21344#21344: *98811 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 162.158.90.12, server: example.com, request: "GET /wp-admin/ HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
41307:2025/12/26 14:03:10 [error] 21344#21344: *99172 connect() to unix:/run/php/php8.2-fpm.sock failed (111: Connection refused) while connecting to upstream, client: 172.67.10.20, server: example.com, request: "GET / HTTP/2.0", upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"

Ce que cela signifie : Vous avez à la fois des timeouts et des refus. Cela suggère une indisponibilité intermittente de FPM (reloads/crashes) et des réponses upstream lentes quand il est disponible.

Décision : Réparez d’abord la stabilité (éviter les refus), puis traitez la lenteur. Un système constamment lent est plus simple à ajuster qu’un système qui disparaît aléatoirement.

Task 9: Inspect active connections and listen backlog pressure

cr0x@server:~$ sudo ss -xlp | grep php8.2-fpm.sock
u_str LISTEN 0 4096 /run/php/php8.2-fpm.sock 113217 * 0 users:(("php-fpm8.2",pid=20811,fd=8))

Ce que cela signifie : Le socket écoute, le backlog est 4096. Si le backlog est minuscule et que vous subissez des pics, vous verrez des échecs de connexion sous charge.

Décision : Si le backlog est bas, ajustez listen.backlog (config du pool) et assurez-vous que les limites du noyau ne sont pas ridiculement faibles. Mais n’utilisez pas le backlog pour masquer un pool de workers sous-dimensionné.

Task 10: Check CPU, load, IO wait (is the box dying?)

cr0x@server:~$ uptime; mpstat -P ALL 1 3; vmstat 1 5
 14:03:33 up 36 days,  3:22,  2 users,  load average: 12.44, 10.81, 8.03
Linux 6.1.0 (server)  12/26/2025  _x86_64_  (4 CPU)

Average:     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
Average:     all   72.10    0.00   18.34    6.22    0.00    0.78    0.00    0.00    0.00    2.56
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
12  1      0  14200  12000 820000    0    0   120   980 4200 8800 74 19  2  5  0

Ce que cela signifie : Le CPU est saturé et il y a du IO wait. Si PHP-FPM a besoin de CPU et que la machine est saturée, les timeouts Nginx suivront.

Décision : Réduisez la concurrence (limitation de débit, cache), optimisez les chemins chauds, ou ajoutez de la capacité. Avant d’augmenter FPM, confirmez la marge mémoire (Task 11) afin de ne pas « corriger » les 502 en provoquant des OOM kills.

Task 11: Measure PHP-FPM worker memory to tune pm.max_children safely

cr0x@server:~$ ps -ylC php-fpm8.2 --sort:rss | head -n 8
S   UID   PID  PPID  C PRI NI   RSS    SZ WCHAN  TTY          TIME CMD
S    33 22117 20811  0  80  0 126432 210944 -     ?        00:00:01 php-fpm: pool www
S    33 22098 20811  0  80  0 118944 205312 -     ?        00:00:02 php-fpm: pool www
S    33 22077 20811  0  80  0 112880 198656 -     ?        00:00:02 php-fpm: pool www
S    33 22031 20811  0  80  0 108220 190112 -     ?        00:00:03 php-fpm: pool www
S    33 21990 20811  0  80  0 104112 186880 -     ?        00:00:04 php-fpm: pool www

Ce que cela signifie : RSS autour de ~110–125 Mo par worker dans cet instantané. C’est ce chiffre qui compte quand vous augmentez pm.max_children.

Décision : Si vous disposez de 2 Go pour les workers PHP, vous ne fixez probablement pas max_children à 80 « parce qu’il y a du trafic ». Vous le fixez en fonction de ce que votre RAM peut payer, avec une marge pour MySQL, le cache OS et les pics.

Task 12: Turn on and read PHP-FPM slowlog to catch the real offenders

cr0x@server:~$ sudo grep -nE "request_slowlog_timeout|slowlog" /etc/php/8.2/fpm/pool.d/www.conf | tail -n 5
277:request_slowlog_timeout = 5s
278:slowlog = /var/log/php8.2-fpm/www-slow.log
cr0x@server:~$ sudo tail -n 40 /var/log/php8.2-fpm/www-slow.log
[26-Dec-2025 14:01:22]  [pool www] pid 22031
script_filename = /var/www/example.com/public/wp-admin/admin-ajax.php
[0x00007f4c8b8f2a30] curl_exec() /var/www/example.com/public/wp-includes/Requests/Transport/cURL.php:204
[0x00007f4c8b8f28a0] request() /var/www/example.com/public/wp-includes/class-wp-http.php:420
[0x00007f4c8b8f2500] post() /var/www/example.com/public/wp-includes/class-wp-http.php:634
[0x00007f4c8b8f1d40] call_external_api() /var/www/example.com/public/wp-content/plugins/some-plugin/api.php:88

Ce que cela signifie : Un chemin précis (admin-ajax.php) est bloqué en attente d’un appel API externe via cURL.

Décision : Vous n’« augmentez » pas les timeouts. Vous isolez ou corrigez le comportement du plugin : mettez en cache, ajoutez des coupe-circuits, des jobs asynchrones, ou désactivez le plugin. Et fixez un timeout upstream raisonnable pour éviter l’accumulation de workers.

Task 13: Check MySQL/MariaDB stalls (common hidden upstream)

cr0x@server:~$ mysql -e "SHOW PROCESSLIST\G" | sed -n '1,40p'
*************************** 1. row ***************************
     Id: 18821
   User: wpuser
   Host: localhost
     db: wordpress
Command: Query
   Time: 42
  State: Sending data
   Info: SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'

Ce que cela signifie : Une requête s’exécute depuis 42 secondes. Cela peut bloquer des workers PHP assez longtemps pour provoquer des timeouts upstream Nginx et des 5xx Cloudflare.

Décision : Trouvez pourquoi c’est lent : bloat de table, index manquants, contention disque, ou un plugin stockant de gros options autoloadées. Corrigez le goulet d’étranglement DB sinon vous déplacez les chaises sur le pont.

Task 14: Validate disk pressure and filesystem health (yes, storage matters)

cr0x@server:~$ df -hT /var /var/www; iostat -xz 1 3
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4  120G  111G  3.6G  97% /
/dev/nvme0n1p2 ext4  120G  111G  3.6G  97% /
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          68.12    0.00   17.43    9.87    0.00    4.58

Device            r/s     w/s    rkB/s    wkB/s  await  svctm  %util
nvme0n1          45.0    60.0   3200.0   5400.0  18.2   0.7   92.0

Ce que cela signifie : Le système de fichiers root est à 97 % utilisé et le disque est à 92 % d’utilisation avec un await significatif. Les disques presque pleins provoquent des défaillances en cascade : écriture des logs échoue, fichiers temporaires échouent, DB ralentit, PHP bloque.

Décision : Libérez de l’espace maintenant (logs, anciennes sauvegardes), puis corrigez le plan de stockage sous-jacent. Ne tentez pas d’ajuster PHP-FPM sur un disque qui hurle.

Task 15: Confirm Cloudflare-to-origin connectivity isn’t being blocked

cr0x@server:~$ sudo iptables -S | sed -n '1,60p'
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT ACCEPT
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -s 198.51.100.0/24 -j ACCEPT

Ce que cela signifie : Seul un /24 spécifique est autorisé à atteindre le port 443. Si les IPs edge de Cloudflare ne sont pas dans cette allowlist, vous verrez des erreurs au bord selon le PoP qui vous atteint.

Décision : Soit ouvrez le 443 de façon appropriée, soit maintenez une allowlist correcte. « Nous n’autorisons que les IPs Cloudflare » est une stratégie valide — si vous la tenez à jour.

Modes de défaillance Nginx qui produisent 502

1) “connect() failed (111: Connection refused)”

Cela signifie que Nginx a tenté de se connecter à l’amont (socket FPM ou port TCP) et que l’OS a refusé. Causes courantes :

  • PHP-FPM est arrêté ou en boucle de crash.
  • Nginx pointe vers le mauvais chemin de socket ou port.
  • Un reload a brièvement supprimé le socket, et Nginx a couru contre le redémarrage.

Correctif : Stabilisez d’abord l’amont. Vérifiez le chemin du socket dans nginx -T, assurez-vous de la santé du service PHP-FPM, et évitez les boucles de reload agressives lors des déploiements.

2) “upstream timed out (110: Connection timed out) while reading response header from upstream”

Nginx s’est connecté à PHP-FPM, a envoyé la requête, puis a attendu trop longtemps les en-têtes. C’est le cas classique « PHP est lent ou saturé ». Coupables probables :

  • Tous les workers sont occupés (pm.max_children reached), donc les requêtes font la queue.
  • Un ou plusieurs endpoints sont lents (admin-ajax, checkout, cron).
  • La latence base de données ou stockage bloque PHP, donc FPM ne peut pas répondre.
  • Les appels API externes restent bloqués et immobilisent des workers.

Correctif : Utilisez le slowlog pour identifier le chemin de code. Puis corrigez la raison de la lenteur. Augmenter les timeouts Nginx sans résoudre l’accumulation des workers ne fait que transformer des 502 en 504 et rend vos graphiques de latence artistiques modernes.

3) “upstream prematurely closed connection”

L’amont a accepté la connexion, puis est mort ou l’a fermée sans envoyer de réponse complète. Causes courantes :

  • Worker PHP crash (segfault, erreur fatale, OOM kill).
  • Problèmes de buffer FastCGI avec des en-têtes ou réponses énormes (moins courant avec HTML WordPress, plus courant avec plugins étranges).
  • Mauvaise configuration fastcgi.

Correctif : Vérifiez les logs PHP-FPM pour des crashes et les logs système pour des OOM kills. Traitez les crashes comme un vrai bug. Si ce sont les en-têtes, cherchez des cookies surdimensionnés ou des en-têtes générés par un plugin.

4) Permission denied on the socket

Nginx ne peut pas ouvrir le socket FPM à cause des permissions filesystem ou de SELinux/AppArmor. Le log d’erreur dira (13: Permission denied).

Correctif : Assurez-vous que le socket est lisible/écrivable par l’utilisateur worker de Nginx. Si SELinux est en mode enforcing, vous avez besoin du bon contexte — pas d’une prière.

Modes de défaillance PHP-FPM qui produisent 502

1) pm.max_children reached (plafond de capacité)

C’est le scénario le plus courant « ça marche jusqu’à ce que ça ne marche plus ». Quand tous les enfants sont occupés, les nouvelles requêtes font la queue. Si la queue attend plus longtemps que le timeout de Nginx, vous obtenez des 502/504.

Que faire :

  • Mesurez la mémoire et le CPU des workers avant d’augmenter les limites.
  • Corrigez d’abord les requêtes lentes. Ajouter des workers peut augmenter la charge DB et empirer la situation.
  • Envisagez des pools séparés pour l’admin et le trafic public si vous êtes sérieux sur la disponibilité.

2) Slow requests pin workers (le tueur silencieux)

Un endpoint plugin lent n’affecte pas seulement cet endpoint. Il consomme un worker. Sous concurrence, les requêtes lentes deviennent un problème d’ordonnancement, puis une panne.

Correctif : Utilisez le slowlog. Identifiez la stack trace. Corrigez le chemin de code. Ajoutez du caching, réduisez les appels, faites en sorte que les appels externes expirent rapidement avec des repliques.

3) Crashes : SIGSEGV, OOM, erreurs fatales

Les crashes provoquent « prematurely closed » ou « connection refused » selon le timing. Les OOM kills sont particulièrement sournois car silencieux si vous ne vérifiez pas les logs du noyau.

cr0x@server:~$ dmesg -T | tail -n 20
[Fri Dec 26 14:03:02 2025] Out of memory: Killed process 22077 (php-fpm8.2) total-vm:812340kB, anon-rss:256144kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:680kB oom_score_adj:0

Décision : Si OOM a tué des workers PHP, n’augmentez pas max_children. Réduisez la mémoire par requête (plugins, tuning OPcache), ajoutez de la RAM, ou déplacez des services hors de la machine.

4) Bad listen/backlog and thundering herds

Sous des pics soudains, un backlog trop petit ou des limites noyau strictes peuvent transformer la charge en refus de connexion. Cela survient souvent lors d’un pic de trafic, d’une tempête de cron, ou de purge de cache.

Correctif : Assurez-vous que le backlog d’écoute FPM est raisonnable, et que les réglages noyau ne sont pas antiques. Mais encore : le backlog n’est pas la capacité.

WordPress et plugins/thèmes coupables

WordPress lui-même est généralement correct. WordPress plus son écosystème de plugins est un bazar animé où la qualité varie. Les 502 proviennent souvent de :

  • abuse d’admin-ajax.php : sondage fréquent, actions longues, boucles non bornées.
  • tempêtes wp-cron.php : le « pseudo-cron » déclenché par des requêtes web peut s’accumuler sous charge ou quand des visiteurs arrivent après des périodes de calme.
  • appels API externes : marketing, CRM, paiement, expédition, analytics. Les appels externes sans timeouts stricts collent les workers.
  • gonflement des options autoload : gros blobs sérialisés autoloadés à chaque requête. C’est comme apporter tout votre grenier à chaque réunion.
  • traitement d’images à la volée : redimensionnement et optimisation synchrones en PHP.

Utilisez le slowlog comme détecteur de plugin

La stack trace du slowlog est votre amie car elle pointe vers un chemin de fichier sous wp-content/plugins/ ou un thème. C’est une preuve. Elle change les conversations.

Tri WP-CLI sans cabrioles

Si le site fond et que vous devez isoler rapidement, désactivez les plugins non essentiels de manière contrôlée. Mieux : faites-le sur un environnement de staging. Mais en incident réel, il faut parfois couper l’alimentation d’un plugin et s’excuser ensuite.

cr0x@server:~$ cd /var/www/example.com/public
cr0x@server:~$ sudo -u www-data wp plugin list --status=active
+-----------------------+----------+--------+---------+
| name                  | status   | update | version |
+-----------------------+----------+--------+---------+
| woocommerce           | active   | none   | 8.5.1   |
| some-plugin           | active   | none   | 2.9.0   |
| cache-plugin          | active   | none   | 1.3.2   |
+-----------------------+----------+--------+---------+

Décision : Si le slowlog pointe vers some-plugin, désactivez-le d’abord et retestez. Si la panne s’arrête, vous avez un candidat cause racine. Ensuite faites la vraie correction : config, mise à jour, remplacement, ou escalade fournisseur.

Cloudflare : quand c’est « eux », quand c’est vous

Cloudflare est un reverse proxy avec des opinions. Il impose des timeouts et retournera volontiers une erreur pendant que votre origine réfléchit encore. Causes Cloudflare-adjacentes courantes d’un 502 :

  • surcharge de l’origine : Cloudflare augmente la concurrence en rendant votre site plus accessible et en réessayant parfois. Votre origine doit quand même avoir la capacité.
  • mismatch de mode TLS : « Full » vs « Full (strict) » et problèmes de validation de certificat peuvent se manifester comme des problèmes de connexion.
  • combat WAF et bots : les requêtes bloquées peuvent ressembler à des échecs partiels si seuls certains endpoints/utilisateurs sont affectés.
  • erreurs d’allowlist IP : le pare-feu n’autorise que d’anciennes plages Cloudflare.

Approche pratique :

  1. Prouvez la santé de l’origine en contournant Cloudflare (Task 2).
  2. Vérifiez les logs d’origine pour les IP Cloudflare et les motifs de requêtes aux heures d’erreur.
  3. Confirmez que votre pare-feu ne bloque pas les IPs edge (Task 15).

Blague #2 : Cloudflare n’est pas « en panne », il exerce juste des limites saines avec votre serveur d’origine.

Erreurs fréquentes : symptôme → cause racine → fix

1) Symptom: 502 spikes during traffic peaks

Cause racine : PHP-FPM max_children atteint ; les requêtes font la queue ; Nginx timeoute en attendant les en-têtes.

Fix : Mesurez le RSS des workers, fixez max_children selon la RAM, activez le slowlog, corrigez les endpoints lents, ajoutez du cache. Si besoin, augmentez la capacité (CPU/RAM, DB séparée).

2) Symptom: 502 only for wp-admin or admin-ajax

Cause racine : Plugin effectuant des appels externes lents, ou endpoints admin contournant le cache et exécutant des requêtes plus lourdes.

Fix : Utilisez le slowlog, identifiez le fichier du plugin, ajoutez des timeouts HTTP stricts, mettez en cache les réponses externes, ou désactivez/remplacez le plugin.

3) Symptom: 502 after deploy/restart, then “recovers”

Cause racine : Reload PHP-FPM qui ferme brièvement le socket ; Nginx attrape ça en pleine transition ; ou warm-up OPcache qui crée un pic CPU.

Fix : Utilisez des reloads gracieux, étalez les redémarrages, gardez des health checks, et évitez de redémarrer Nginx+FPM simultanément. Envisagez de pré-warm les endpoints courants.

4) Symptom: Cloudflare shows 502, origin direct is fine

Cause racine : Pare-feu bloquant certaines IPs Cloudflare ; routage edge→origine intermittent ; mismatch TLS.

Fix : Corrigez les allowlists, confirmez les réglages TLS, assurez-vous que l’origine peut gérer la concurrence Cloudflare. Validez avec des tests --resolve.

5) Symptom: Nginx error shows Permission denied on FPM socket

Cause racine : Propriété du socket ne correspondant pas à l’utilisateur worker Nginx ; contexte SELinux incorrect.

Fix : Alignez utilisateurs/groupes ou définissez listen.owner/listen.group/listen.mode. Pour SELinux, appliquez la politique/contexte approprié (ne désactivez pas SELinux en production sauf si vous aimez les audits).

6) Symptom: 502 appears with “upstream prematurely closed connection”

Cause racine : Crash PHP (segfault) ou OOM kill ; parfois erreurs fatales terminant le worker.

Fix : Vérifiez le journal et dmesg. Restaurez les extensions PHP ou changements récents. Réduisez la pression mémoire. Ajoutez du swap en dernier recours, et ne le confondez pas avec une vraie correction.

7) Symptom: 502 after enabling a “performance” plugin

Cause racine : Plugin de cache agressif provoquant une ruée de cache, purges trop fréquentes, ou augmentation de la charge admin-ajax ; parfois mauvaise configuration des en-têtes/buffering.

Fix : Désactivez pour confirmer. Réintroduisez avec une configuration sensée : warm-up du cache, limitations de débit, stratégie de object cache, et règles de purge prudentes.

Checklists / step-by-step plan

Checklist A: First 10 minutes on-call

  1. Vérifiez si l’erreur est brandée Cloudflare ou origin.
  2. Exécutez curl -D - pour capturer les en-têtes et confirmer l’en-tête server.
  3. Contournez Cloudflare avec curl --resolve pour tester l’origine directement.
  4. Taillez le log d’erreurs Nginx et cherchez la chaîne d’erreur upstream.
  5. Vérifiez l’état PHP-FPM et le journal pour max_children, crashes et erreurs de pool.
  6. Contrôle rapide système : CPU, RAM, IO wait, disque plein.
  7. Si nécessaire, appliquez une mitigation : désactiver temporairement le plugin connu pour être mauvais, limiter les endpoints abusifs, ou augmenter les timeouts seulement comme mesure d’urgence.

Checklist B: Identify if it’s capacity vs latency vs connectivity

  • Connectivité : socket manquant, permission refusée, connexions refusées, pare-feu bloquant. Corrigez la config et la politique de sécurité d’abord.
  • Capacité : max_children atteint, CPU saturé, pression mémoire. Corrigez avec dimensionnement, cache, plus de compute.
  • Latence : slowlog pointe vers DB/API/plugin. Corrigez le chemin de code lent et les performances des dépendances.

Checklist C: Hardening changes that prevent repeat incidents

  1. Activez le slowlog PHP-FPM avec un seuil bas (ex. 3–5s) et faites une rotation des logs.
  2. Ajoutez des IDs de requête aux logs d’accès Nginx et propagez-les (pour pouvoir tracer).
  3. Fixez des timeouts stricts pour les appels externes dans le code applicatif (plugins/themes).
  4. Séparez les responsabilités : base de données hors de la machine web pour les sites chargés ; isolez le trafic admin via un pool séparé.
  5. Mettez en place du caching intentionnel : cache page, cache objet, et stratégie d’invalidation correcte.
  6. Planifiez la capacité sur la base de mesures, pas d’intuitions.

Trois mini-histoires du monde de l’entreprise (réalistes et douloureuses)

Mini-story 1: The incident caused by a wrong assumption

Le site était derrière Cloudflare, et l’équipe a supposé que cela protégeait l’origine des pics de trafic. Le marketing a lancé une campagne. Cloudflare a fait son travail : il a accepté une vague de connexions et a tenté d’atteindre l’origine pour les cache misses.

L’origine, une VM unique, avait PHP-FPM configuré il y a des années avec un pm.max_children conservateur. Sous charge, les workers se sont saturés. Les requêtes ont fait la queue. Nginx a commencé à logger upstream timed out while reading response header. Cloudflare a commencé à retourner des 502 parce qu’il ne recevait pas de réponses en temps utile.

L’appel initial fut classique : « Cloudflare est en panne. » Ce n’était pas le cas. Le bord rapportait justement que l’origine ne suivait pas.

Ce qui a réglé le problème n’a pas été un ticket héroïque chez Cloudflare. L’équipe a activé le slowlog PHP-FPM et a trouvé un endpoint dominant : des appels admin-ajax.php d’un widget frontend d’un plugin. Le widget appelait une API externe à chaque vue de page sans cache.

Ils ont mis en cache la réponse de l’API, défini un timeout court, et réduit la fréquence des appels du widget. Puis ils ont redimensionné FPM sur la base des mesures mémoire. L’hypothèse est morte ; le site a survécu.

Mini-story 2: The optimization that backfired

Un ingénieur bien intentionné a « optimisé » la pile en augmentant fortement pm.max_children. L’objectif était simple : plus de workers, moins de requêtes en queue, moins de 502.

Ça a fonctionné pendant environ une heure. Puis la machine a commencé à swapper. MySQL est devenu plus lent. L’IO wait a grimpé. Les workers PHP ont mis plus de temps, pas moins. Les timeouts Nginx ont augmenté. Les erreurs Cloudflare ont suivi. L’incident s’est aggravé parce que maintenant chaque requête se battait pour le disque et la mémoire.

Le postmortem fut humiliant : ils avaient traité PHP-FPM comme un pool de threads. Ce n’est pas le cas. Chaque worker est un processus avec un coût mémoire réel. Augmenter la concurrence sans augmenter les ressources augmente souvent la contention et la latence tail.

La correction fut ennuyeuse : limiter les workers à ce que la RAM supporte, déplacer MySQL sur un hôte séparé, et ajouter du cache pour que moins de requêtes nécessitent PHP. L’« optimisation » a été annulée, et tout le monde a discrètement accepté d’arrêter de tuner la production avec de la caféine et de l’espoir.

Mini-story 3: The boring but correct practice that saved the day

Une autre entreprise avait l’habitude : chaque incident commençait par la corrélation des logs, pas par la spéculation. Ils avaient aussi une petite convention utile : chaque ligne du log d’accès Nginx incluait un ID de requête, et cet ID était passé à PHP via un fastcgi param et loggé par l’application.

Quand les 502 sont arrivés, ils ont extrait un échantillon de requêtes en échec et ont immédiatement vu le même chemin : un endpoint de checkout qui déclenchait une recherche de tarif d’expédition. Le slowlog PHP-FPM montrait la pile d’appels ; l’ID de requête correspondait à l’entrée Nginx ; le timing concordait.

Ils n’ont pas perdu de temps à débattre Cloudflare vs Nginx vs PHP-FPM. Ils n’en avaient pas besoin. La chaîne de preuves était propre. L’API d’expédition externe était lente et parfois bloquante. Leur code avait un timeout généreux et aucun fallback.

Parce qu’ils pratiquaient cette discipline ennuyeuse, la mitigation fut rapide : réduire le timeout, ajouter un fallback en cache pour les tarifs, et dégrader gracieusement. L’incident s’est terminé sans drame, ce qui est le meilleur type d’incident.

Operational guidance that holds up under pressure

Timeouts: use them as guardrails, not a lifestyle

Il y a des timeouts à tous les niveaux : Cloudflare, navigateur, Nginx, FastCGI, exécution PHP, API externes, base de données. Un 502 est souvent une politique de timeout appliquée. Si vous vous contentez d’augmenter les timeouts, vous convertissez souvent un échec rapide en un échec lent. Les utilisateurs perdent toujours ; vous gaspillez juste plus de compute en perdant.

Fixez des timeouts de sorte que :

  • les appels externes échouent rapidement (secondes, pas minutes),
  • votre timeout upstream soit légèrement au-dessus du 95e centile pour les requêtes lentes légitimes, et
  • votre application se dégrade de façon prévisible lorsque les dépendances dysfonctionnent.

One quote worth keeping in your head

Idée paraphrasée, attribuée à John Allspaw : « L’absence de blâme vous aide à comprendre les vraies raisons pour lesquelles les systèmes échouent. »

FAQ

1) Why do I get 502 sometimes and 200 other times?

Les 502 intermittents signifient généralement saturation ou instabilité. Saturation : le pool de workers est parfois plein. Instabilité : reloads/crashes PHP-FPM, OOM kills, ou dépendances instables.

2) Is a 502 always PHP-FPM’s fault?

Non. Un 502 est produit par la passerelle (Cloudflare/Nginx) quand l’amont échoue. L’amont peut être PHP-FPM, mais aussi votre origine du point de vue Cloudflare, ou une adresse d’amont mal configurée.

3) What’s the fastest way to know if Cloudflare is involved?

Vérifiez les en-têtes : server: cloudflare et cf-ray. Puis contournez Cloudflare avec curl --resolve pour tester l’origine directement.

4) Should I switch Nginx→PHP-FPM from UNIX socket to TCP?

Si vous luttez avec les permissions et les outils de déploiement, TCP peut être plus simple à raisonner. Les sockets UNIX sont corrects et légèrement plus efficaces, mais le vrai gain est la fiabilité et la clarté, pas des micro-optimisations.

5) Why does wp-cron.php correlate with 502s?

Parce que WP-Cron exécute des tâches planifiées sur des requêtes web normales. Sous certains schémas de trafic, les tâches s’accumulent. Si les tâches sont lourdes (envoi d’e-mails, sync API), elles immobilisent des workers PHP et provoquent des files d’attente.

6) Can caching alone eliminate 502s?

Le cache peut réduire dramatiquement la probabilité en diminuant la charge sur l’origine. Mais si vos endpoints non mis en cache sont encore lents ou si votre pool de workers est instable, le cache ne sauvera pas les endpoints admin, checkout et API.

7) I see “max_children reached” but no 502s. Do I care?

Oui. C’est un avertissement précoce. Vous pourriez masquer le problème avec des timeouts généreux ou un faible trafic. Quand le trafic augmente ou qu’une dépendance ralentit, vous le paierez.

8) Why do I get 502 only on large uploads or image operations?

Les uploads et le traitement d’images peuvent être IO et CPU intensifs. Si le temps d’exécution PHP est long ou si le disque temporaire est plein/lent, les workers se bloquent. Corrigez en déplaçant le traitement hors requête, en augmentant les ressources, et en assurant de l’espace disque performant.

9) What if Nginx says “upstream timed out” but PHP-FPM seems idle?

Alors « idle » peut être trompeur : les workers peuvent être coincés en IO non interrompable, ou le goulot d’étranglement est ailleurs (verrous DB, stalls DNS, appels externes). Utilisez le slowlog et vérifiez le processlist MySQL et l’IO wait système.

10) Should I restart PHP-FPM when I see 502?

Redémarrer peut être une mitigation si FPM est bloqué, mais cela détruit des preuves. Prenez d’abord les logs, capturez l’état courant (status du service, logs d’erreur, slowlog), puis redémarrez si nécessaire.

Conclusion : prochaines étapes pour éviter la récidive

Les 502 ne sont pas mystérieux. Ils sont spécifiques : un proxy n’a pas obtenu une réponse valide de l’amont. Votre travail est de nommer la passation défaillante, puis de corriger la raison de l’échec.

Prochaines étapes que je ferais réellement en production :

  1. Instrumenter pour des preuves : activer le slowlog PHP-FPM, garder les logs d’erreur Nginx propres et archivés, et logger des IDs de requête.
  2. Construire une routine de triage rapide : en-têtes → contournement du bord → erreur upstream Nginx → santé FPM → pression système → slowlog.
  3. Corriger le vrai goulet : chemins plugin lents, verrous base de données, pression disque, ou extensions PHP instables.
  4. Tuner en dernier lieu, sur la base de mesures : fixer les workers FPM selon le RSS et le CPU, pas selon l’espoir.
  5. Rendre les pannes sûres : timeouts stricts pour les API externes, caching, dégradation gracieuse, et limites de débit sensées sur les endpoints abusifs.
← Précédent
Ubuntu 24.04 : CIFS trop lent — options de montage qui améliorent généralement le débit (cas n°22)
Suivant →
Debian 13 : APT cassé (« dépendances non satisfaites ») — réparez-le sans réinstaller

Laisser un commentaire