Votre serveur Web fonctionnait. Vous n’avez rien déployé. Puis soudain : 502 Bad Gateway et 504 Gateway Timeout sur tout le site comme un front orageux. Les clients appuient sur F5. Les tableaux de bord virent au rouge. Quelqu’un propose de « redémarrer nginx » comme s’il s’agissait d’un rituel sacré.
Parfois un redémarrage est un pansement. Parfois c’est le moyen le plus rapide d’effacer les preuves. L’objectif ici est d’identifier le véritable goulot sur Ubuntu 24.04 — processus upstream, sockets, DNS, CPU, ou latence de stockage — et de le corriger sans deviner.
Ce que signifient réellement les 502/504 (et ce qu’ils ne signifient pas)
Quand un utilisateur voit un 502/504, il blâme « le serveur web ». En pratique, le serveur web est souvent juste le messager.
502 Bad Gateway : « J’ai parlé à mon upstream et il a répondu n’importe quoi ou rien. »
- Signification typique : Nginx/Apache a agi en proxy et la connexion upstream a échoué ou a renvoyé une réponse invalide.
- Causes réelles courantes : processus upstream planté, permissions de socket modifiées, port d’écoute upstream incorrect, incompatibilité TLS, incompatibilité de protocole, backend renvoyant des données pourries parce qu’il est en train de mourir.
504 Gateway Timeout : « J’ai attendu l’upstream et il n’a pas répondu à temps. »
- Signification typique : le proxy a établi une connexion (ou essayé) mais l’upstream n’a pas répondu dans le délai imparti.
- Causes réelles courantes : upstream surchargé, appels BD lents, latence de stockage, blocages DNS, épuisement des pools de connexion, interblocages, épuisement des ressources du noyau.
Voici la vérité opérationnelle : les 502/504 sont généralement des symptômes d’un goulot, pas des bugs dans nginx. Nginx est ennuyeux. Il doit être ennuyeux. Quand il ne l’est pas, c’est parce que quelque chose d’autre l’a rendu intéressant.
Blague n°1 : Redémarrer nginx pour corriger un 504, c’est comme éteindre votre radio pour résoudre un embouteillage. Vous réduisez le bruit, pas le problème.
Guide de diagnostic rapide (premier/deuxième/troisième)
Si vous ne faites rien d’autre, faites ceci. L’objectif est d’identifier le goulot en moins de 10 minutes, avec des preuves que vous pouvez transmettre sans honte à la personne suivante.
Premier : confirmez ce qui génère les 502/504 et capturez les journaux immédiatement
- Vérifiez les logs d’erreurs du proxy (nginx ou Apache) pour la chaîne d’erreur exacte de l’upstream.
- Corrélez les horodatages avec les logs du backend (php-fpm, gunicorn, node, etc.).
- Vérifiez le journal systemd pour des redémarrages, des kills OOM, des erreurs de permission.
Décision : si vous voyez « connect() failed (111: Connection refused) » c’est un problème d’atteignabilité upstream. Si vous voyez « upstream timed out » c’est un problème de performance ou d’interblocage. Si vous voyez « permission denied » c’est généralement un problème de socket/SELinux/AppArmor/mode de fichier.
Deuxième : déterminez s’il s’agit de calcul, mémoire, réseau ou stockage
- CPU + charge + iowait : si iowait monte, vous n’êtes pas lié au CPU ; vous attendez les disques.
- Pression mémoire : les kills OOM et les tempêtes de swap ressemblent à des timeouts aléatoires.
- Réseau et DNS : l’upstream peut être « lent » parce que le proxy ne peut pas résoudre ou se connecter rapidement.
Décision : si wa est élevé, allez vérifier le stockage. Si si/so (swap in/out) est occupé, allez vérifier la mémoire. Si vous voyez beaucoup de SYN-SENT ou TIME-WAIT, regardez le réseau/les ports.
Troisième : isolez le saut défaillant
- Testez depuis le proxy vers l’upstream (loopback, socket unix, port local, service distant).
- Testez depuis l’upstream vers ses dépendances (BD, cache, stockage objet).
- Si ce ne sont que certaines requêtes : vérifiez les timeouts, limites de workers, files d’attente et pools de connexions.
Décision : corrigez le point d’étranglement le plus étroit en premier. Ne « réglez pas tout » en panique. C’est comme ça que vous provoquez une deuxième panne plus créative que la première.
Pratique : 12+ tâches avec commandes, sorties et décisions
Voici les tâches que j’exécute en production quand des 502/504 apparaissent « de nulle part ». Chaque commande inclut : ce qu’elle vous dit, ce que la sortie signifie, et ce que vous faites ensuite.
Task 1: Verify whether nginx is the component emitting the errors
cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/28 09:41:12 [error] 3192#3192: *884 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.25, server: example.com, request: "GET /api/orders HTTP/1.1", upstream: "http://127.0.0.1:8000/api/orders", host: "example.com"
2025/12/28 09:41:15 [error] 3192#3192: *901 connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 198.51.100.9, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example.com"
Signification : Deux modes d’échec différents : l’un est un timeout depuis un upstream HTTP sur 127.0.0.1:8000 ; l’autre est un problème de permission sur un socket PHP-FPM unix.
Décision : Ne traitez pas cela comme un seul incident. Séparez les problèmes : corrigez les permissions du socket pour les chemins PHP et les performances/timeouts pour l’API upstream.
Task 2: Confirm active config and upstream targets (avoid debugging the wrong file)
cr0x@server:~$ sudo nginx -T | sed -n '1,120p'
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
Signification : Vous regardez la configuration active que nginx utilisera, incluant les fichiers inclus. Ça évite l’erreur classique : éditer un fichier qui n’est pas chargé.
Décision : Recherchez dans cette sortie votre stanza upstream ou les lignes fastcgi_pass, puis vérifiez qu’elles correspondent à la réalité (chemin du socket, port, nom d’hôte).
Task 3: Check whether upstream processes are alive and not restart-looping
cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Active: active (running) since Sun 2025-12-28 09:10:04 UTC; 33min ago
Docs: man:php-fpm8.3(8)
Process: 1258 ExecStartPre=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/8.3/fpm/pool.d/www.conf 83 (code=exited, status=0/SUCCESS)
Main PID: 1269 (php-fpm8.3)
Tasks: 21 (limit: 18841)
Memory: 230.5M
CPU: 1min 42.902s
Signification : php-fpm est en fonctionnement. Ça ne veut pas dire que nginx peut accéder à son socket. Ça signifie juste que le service n’a pas planté.
Décision : Si nginx affiche permission denied, inspectez la propriété/le mode du socket et l’utilisateur nginx (généralement www-data).
Task 4: Validate unix socket permissions for PHP-FPM
cr0x@server:~$ ls -l /run/php/php8.3-fpm.sock
srw-rw---- 1 root www-data 0 Dec 28 09:10 /run/php/php8.3-fpm.sock
Signification : Socket possédé par root:www-data avec le mode 660. nginx s’exécute par défaut en tant que www-data, donc ça devrait fonctionner.
Décision : Si vous voyez srw------- root root ou similaire, corrigez listen.owner, listen.group et listen.mode dans la configuration du pool, puis rechargez php-fpm.
Task 5: Confirm nginx worker user (don’t assume)
cr0x@server:~$ ps -o user,group,pid,cmd -C nginx | head
USER GROUP PID CMD
root root 3121 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data www-data 3192 nginx: worker process
www-data www-data 3193 nginx: worker process
Signification : Les workers tournent en tant que www-data, donc la propriété de groupe du socket doit inclure www-data ou les permissions doivent permettre l’accès.
Décision : Si les workers tournent sous un autre utilisateur (conteneurs, builds personnalisés), alignez les permissions du socket en conséquence.
Task 6: Find whether the upstream port is listening and what owns it
cr0x@server:~$ sudo ss -ltnp | grep -E ':8000|:8080|:9000'
LISTEN 0 4096 127.0.0.1:8000 0.0.0.0:* users:(("gunicorn",pid=2204,fd=5))
LISTEN 0 4096 127.0.0.1:9000 0.0.0.0:* users:(("php-fpm8.3",pid=1269,fd=8))
Signification : gunicorn écoute sur 127.0.0.1:8000. Si nginx timeoute encore, gunicorn est vivant mais lent ou bloqué, ou le chemin est bloqué à l’intérieur de l’application.
Décision : Passez aux logs backend et aux vérifications de contention des ressources. Si le port n’écoute pas, corrigez le service ou son adresse de bind.
Task 7: Reproduce the request from the proxy host (bypass the proxy layer)
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1:8000/api/orders
HTTP/1.1 200 OK
Server: gunicorn
Date: Sun, 28 Dec 2025 09:44:01 GMT
Connection: close
Content-Type: application/json
Signification : L’endpoint peut répondre rapidement pour l’instant. Cela suggère un enchaînement intermittent, un épuisement de workers ou des pics chez des dépendances en aval, pas une mauvaise configuration permanente.
Décision : Vérifiez les limites de concurrence et la latence pendant la fenêtre d’incident ; ne déclarez pas victoire parce qu’un simple curl fonctionne.
Task 8: Look for upstream timeouts and worker starvation in application service logs
cr0x@server:~$ sudo journalctl -u gunicorn --since "2025-12-28 09:35" --no-pager | tail -n 30
Dec 28 09:40:58 app1 gunicorn[2204]: [2025-12-28 09:40:58 +0000] [2204] [CRITICAL] WORKER TIMEOUT (pid:2311)
Dec 28 09:40:58 app1 gunicorn[2311]: [2025-12-28 09:40:58 +0000] [2311] [ERROR] Error handling request /api/orders
Dec 28 09:40:59 app1 gunicorn[2204]: [2025-12-28 09:40:59 +0000] [2204] [INFO] Worker exiting (pid: 2311)
Dec 28 09:41:00 app1 gunicorn[2204]: [2025-12-28 09:41:00 +0000] [2204] [INFO] Booting worker with pid: 2418
Signification : des workers gunicorn expirent et redémarrent. Nginx voit cela comme des timeouts upstream/502 selon le moment.
Décision : N’augmentez pas juste le timeout gunicorn. Trouvez pourquoi les requêtes se bloquent : verrous BD, latence de stockage, interblocages de threads, blocages DNS, appels externes lents.
Task 9: Check system load, CPU, and iowait during the event
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 812344 48216 923400 0 0 24 18 210 390 6 2 90 2 0
3 1 0 789120 48220 921900 0 0 120 2040 480 1200 8 4 58 30 0
5 2 0 760112 48220 920100 0 0 80 1990 520 1350 10 5 50 35 0
4 2 0 748000 48224 918500 0 0 60 2100 510 1300 9 4 52 31 0
2 1 0 770200 48224 920300 0 0 40 980 360 900 7 3 78 12 0
Signification : iowait (wa) monte à 30–35 %. Ce n’est pas un problème web. C’est le noyau qui vous dit que vos processus attendent le stockage.
Décision : Allez vérifier la latence disque, la saturation du système de fichiers, et qui martèle le disque (journald, logrotate, sauvegardes, checkpoints BD, antivirus, etc.).
Task 10: Identify which process is causing disk pressure
cr0x@server:~$ sudo iotop -oPa -n 3
Total DISK READ: 1.65 M/s | Total DISK WRITE: 38.90 M/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
2210 be/4 postgres 0.00 B/s 22.30 M/s 0.00 % 9.10 % postgres: checkpointer
3140 be/4 root 0.00 B/s 8.40 M/s 0.00 % 2.20 % systemd-journald
987 be/4 www-data 1.20 M/s 0.00 B/s 0.00 % 1.30 % gunicorn: worker [app]
Signification : Le checkpointer Postgres écrit beaucoup. journald écrit aussi. Ces écritures peuvent bloquer les lectures selon le stockage.
Décision : Si cela coïncide avec des timeouts, vérifiez la santé de Postgres et la latence du stockage. Vous pourriez devoir ajuster le checkpointing BD, déplacer les logs vers un disque plus rapide, ou arrêter un voisin bruyant.
Task 11: Check actual block device latency and saturation
cr0x@server:~$ sudo iostat -x 1 3
avg-cpu: %user %nice %system %iowait %steal %idle
7.82 0.00 3.21 28.44 0.00 60.53
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme0n1 32.0 1824.0 0.0 0.0 8.20 57.0 620.0 41280.0 0.0 0.0 42.10 66.6 6.20 98.0
Signification : Le périphérique est à 98 % d’utilisation et le w_await est à 42ms. Ce n’est pas « correct ». C’est une file d’attente.
Décision : Réduisez l’amplification d’écriture (logs, checkpoints), vérifiez que le stockage sous-jacent n’est pas bridgé, et confirmez que vous ne tapez pas dans des limites IOPS cloud.
Task 12: Check for OOM kills and memory pressure (silent assassin)
cr0x@server:~$ sudo journalctl -k --since "2025-12-28 09:00" | grep -i -E 'oom|killed process' | tail -n 20
Dec 28 09:38:11 app1 kernel: Out of memory: Killed process 2311 (gunicorn) total-vm:1452200kB, anon-rss:612400kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:2200kB oom_score_adj:0
Signification : Le noyau a tué un processus gunicorn. Nginx voit le backend disparaître en cours de requête : 502, 504, ou les deux.
Décision : Arrêtez l’hémorragie : réduisez la concurrence ou l’empreinte mémoire, ajoutez de la RAM, corrigez les fuites, et ajoutez des limites cgroup pour que le « kill » soit prévisible (redémarrage du service) plutôt que chaotique.
Task 13: Inspect connection states for port exhaustion or upstream congestion
cr0x@server:~$ ss -s
Total: 3087 (kernel 0)
TCP: 1987 (estab 412, closed 1450, orphaned 0, synrecv 0, timewait 1450/0), ports 0
Transport Total IP IPv6
RAW 0 0 0
UDP 12 10 2
TCP 537 509 28
INET 549 519 30
FRAG 0 0 0
Signification : TIME_WAIT est élevé. Ce n’est pas automatiquement un problème, mais ça peut l’être si vous créez beaucoup de connexions courtes upstream et que vous manquez de ports éphémères.
Décision : Si vous observez « cannot assign requested address » dans les logs, passez à keepalive/pooling, et examinez net.ipv4.ip_local_port_range et les paramètres de réutilisation avec précaution.
Task 14: Confirm DNS isn’t stalling upstream lookups
cr0x@server:~$ resolvectl status
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Link 2 (ens5)
Current Scopes: DNS
Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.0.0.2
DNS Servers: 10.0.0.2 10.0.0.3
Signification : systemd-resolved est en action. Si le serveur DNS est lent ou injoignable, la résolution des noms upstream peut bloquer le traitement des requêtes.
Décision : Si l’upstream utilise des noms d’hôte (pas des IP), vérifiez la vitesse et le cache de résolution. Pour nginx, envisagez de résoudre au démarrage avec des IP stables ou d’utiliser une directive resolver avec timeouts.
Task 15: Confirm AppArmor isn’t blocking socket/file access (Ubuntu loves AppArmor)
cr0x@server:~$ sudo dmesg | grep -i apparmor | tail -n 10
[ 8921.224911] audit: type=1400 audit(1766914872.112:188): apparmor="DENIED" operation="connect" profile="/usr/sbin/nginx" name="/run/php/php8.3-fpm.sock" pid=3192 comm="nginx" requested_mask="wr" denied_mask="wr" fsuid=33 ouid=0
Signification : nginx se voit refuser la connexion au socket PHP-FPM par AppArmor. Cela ressemble exactement à un « permission denied », mais chmod ne le résoudra pas.
Décision : Ajustez le profil AppArmor pour autoriser ce chemin de socket ou alignez-vous sur le chemin attendu. Rechargez ensuite AppArmor et nginx.
Les vraies raisons des 502/504 soudains sur Ubuntu 24.04
Les pannes « soudaines » sont généralement des défaillances lentes dont vous n’aviez pas la visibilité — jusqu’à ce que le proxy commence à émettre des codes HTTP honnêtes.
1) Le backend est vivant mais engorgé (file d’attente et head-of-line blocking)
La plupart des backends web ont une limite de concurrence : workers gunicorn, enfants PHP-FPM, boucle d’événements Node, pools de threads Java. Quand la concurrence est saturée, les nouvelles requêtes font la queue. Le proxy attend. Finalement il expire.
À quoi ça ressemble : le log d’erreur nginx dit « upstream timed out », les logs backend montrent des timeouts de workers, des requêtes lentes, ou rien du tout parce que les workers sont bloqués.
Que faire : trouvez ce qui bloque. Les coupables les plus rapides : verrous BD, appels d’API externes sans timeout, I/O disque lente pour templates/uploads/sessions, et logging synchrone sous pression.
2) La latence de stockage est l’upstream caché (et elle punit tout le monde pareil)
Si l’iowait monte, le système n’est pas « occupé ». Il est en attente. Vos threads applicatifs attendent des lectures/écritures disque : fichiers BD, stores de session, persistance de cache, écritures de logs, fichiers temporaires, chargement de roues Python, resets d’opcache PHP, bref n’importe quoi.
Sur Ubuntu 24.04, vous pouvez aussi voir des logs plus agressifs ou des valeurs par défaut différentes par rapport à d’anciennes installations. Pas parce qu’Ubuntu est méchant. Parce que votre charge l’est.
Que faire : prouvez la latence avec iostat -x, identifiez les écrivains avec iotop, et cherchez des jobs périodiques (sauvegardes, logrotate, maintenance BD). Corrigez en réduisant le volume d’écriture, en déplaçant les chemins chauds sur un stockage plus rapide, ou en augmentant les IOPS provisionnés sur les disques cloud.
3) Régressions de socket/permissions (sockets unix : rapides, excellents, parfois capricieux)
Les sockets unix sont excellents pour le proxy local : faible overhead, contrôle d’accès clair. Ils échouent aussi de façons très spécifiques — souvent après une mise à jour de paquet, une dérive de config, ou une nouvelle unité de service qui change les répertoires runtime.
À quoi ça ressemble : log d’erreur nginx : « connect() to unix:/run/… failed (13: Permission denied) » ou « No such file or directory ».
Que faire : vérifiez que le socket existe, vérifiez propriété/mode, vérifiez l’utilisateur worker nginx, et vérifiez les refus AppArmor. Ensuite corrigez la config du pool, pas le symptôme.
4) Boucles de redémarrage systemd et comportements « utile » du watchdog
Ubuntu 24.04 est sans complexe pro-systemd. Si votre backend plante, systemd peut le redémarrer rapidement. Ça crée des rafales d’échecs où nginx voit des resets/refus de connexion.
À quoi ça ressemble : « connection refused » dans les logs du proxy, journalctl -u montre des redémarrages fréquents, peut-être un ExitCode pointant vers des erreurs de config, des vars d’environnement manquantes ou des migrations non appliquées.
Que faire : arrêtez la tempête de redémarrages assez longtemps pour lire les logs. Utilisez systemctl edit pour ajouter des RestartSec et StartLimitIntervalSec raisonnables si nécessaire, puis corrigez le plantage sous-jacent.
5) Blocages de résolution DNS et le mythe du « c’est juste un nom d’hôte »
Certaines proxys résolvent les noms upstream au démarrage. D’autres résolvent périodiquement. D’autres s’appuient sur le résolveur OS et bloquent quand il est lent. Si le DNS s’embourbe, les connexions upstream s’embourbent. Les timeouts suivent.
À quoi ça ressemble : 504 intermittents, surtout après des changements réseau. Les logs peuvent montrer host not found in upstream ou rien d’évident sauf des temps de connexion accrus.
Que faire : validez la santé du résolveur, confirmez le cache, réduisez la dépendance au DNS en direct pour des upstreams critiques, ou configurez des resolvers explicites et des timeouts dans nginx quand c’est approprié.
6) Épuisement des ressources noyau/réseau : ports éphémères, conntrack, descripteurs
Quand le trafic monte, vous ne manquez pas seulement de CPU. Vous pouvez manquer de « choses » : descripteurs de fichiers, ports locaux, entrées conntrack, backlog d’écoute, file d’acceptation. Le noyau commence à refuser des requêtes. Nginx convertit ça en 502/504 selon l’endroit de l’échec.
À quoi ça ressemble : beaucoup de TIME_WAIT, erreurs comme « cannot assign requested address », « too many open files », ou avertissements sur la file d’accept. Les utilisateurs voient plus de timeouts que d’erreurs propres.
Que faire : inspectez les états de connexion, augmentez les limites avec prudence, activez keepalive quand c’est sûr, et évitez de créer une nouvelle connexion TCP par requête comme en 2009.
7) Timeouts mal alignés entre couches (proxy vs backend vs load balancer)
Les timeouts sont un sport d’équipe. Si votre load balancer timeoute à 60s, nginx à 30s, et gunicorn à 120s, vous aurez un cortège d’échecs partiels et de retries qui amplifient la charge.
À quoi ça ressemble : le client voit un 504 à 30s, le backend continue de travailler, puis écrit sur une socket fermée. Les logs montrent des broken pipes, reset by peer, longues durées de requêtes.
Que faire : choisissez un budget de timeout cohérent. Assurez-vous ensuite que les upstreams ont leurs propres timeouts internes pour BD et appels externes, plus courts que le timeout du proxy.
Une idée paraphrasée à garder au mur, attribuée à une voix ops notable : Tout échoue, tout le temps — votre travail est de rendre l’échec prévisible et visible.
— Werner Vogels
Blague n°2 : Un 504, c’est la façon dont votre serveur dit « Je ne t’ignore pas, je réfléchis beaucoup. »
Erreurs courantes : symptôme → cause → correction
1) Symptom: 502 with “connect() failed (111: Connection refused)”
Cause racine : le processus upstream est arrêté, bind sur une adresse différente, ou en boucle de crash ; vous pointez nginx vers le mauvais port.
Correction : vérifiez le listener avec ss -ltnp ; regardez systemctl status et journalctl -u ; corrigez l’adresse de bind (127.0.0.1 vs 0.0.0.0) et assurez-vous que le service démarre correctement.
2) Symptom: 502 with “Permission denied” to unix socket
Cause racine : mismatch propriétaire/groupe/mode du socket, ou AppArmor qui refuse.
Correction : ajustez listen.owner/listen.group/listen.mode du pool PHP-FPM et alignez l’utilisateur nginx ; si AppArmor refuse, mettez à jour le profil pour autoriser /run/php/php8.3-fpm.sock.
3) Symptom: 504 “upstream timed out while reading response header”
Cause racine : le backend accepte les connexions mais ne répond pas assez vite : épuisement de workers, lenteur BD, iowait de stockage, ou blocage d’API externe.
Correction : confirmez les timeouts des workers backend ; vérifiez l’iowait avec vmstat/iostat ; trouvez les dépendances lentes ; ajoutez des timeouts aux appels externes ; réduisez les files d’attente en scaleant ou en optimisant.
4) Symptom: Errors spike during logrotate/backup window
Cause racine : saturation du stockage, compression des logs, snapshotting, ou sauvegardes consommant I/O. Souvent caché sur des disques cloud partagés.
Correction : replanifiez les jobs lourds, limitez l’I/O, déplacez logs/BD sur des volumes séparés, ajustez les checkpoints BD, et assurez-vous que les sauvegardes sont incrémentales et pas « copier l’univers chaque nuit ».
5) Symptom: Only some endpoints 504, others fine
Cause racine : un chemin de code spécifique déclenche une requête BD lente, une contention de verrou, de grosses lectures de fichier, ou un appel synchrone dépendant.
Correction : instrumentez la latence par endpoint ; examinez les slow query logs ; ajoutez des index ou du cache ; streamez les grosses réponses ; isolez les tâches lourdes hors du chemin de requête.
6) Symptom: 502 after upgrading packages (Ubuntu 24.04 refresh)
Cause racine : changements d’unités de service, montée de version PHP, chemins de socket modifiés, config incompatible, ou modules désactivés.
Correction : revalidez les chemins upstream/fastcgi de nginx ; lancez nginx -T ; vérifiez le service php-fpm et les chemins de socket ; confirmez les sites et modules activés.
7) Symptom: 504 behind a corporate proxy/load balancer only
Cause racine : décalage de timeout au bord, checks de santé en échec, ou retards de handshake TLS. Parfois le LB réessaye et double la charge.
Correction : alignez les budgets de timeout LB/nginx/app ; validez que l’endpoint de santé est rapide et léger en dépendances ; assurez-vous que keepalive et TLS sont configurés correctement.
Trois mini-récits d’entreprise (anonymisés, plausibles, techniquement exacts)
Mini-story 1: The incident caused by a wrong assumption
Ils avaient une configuration propre : nginx sur Ubuntu, PHP-FPM sur la même machine, socket unix entre eux. Ça marchait depuis des années. Puis ils ont reconstruit l’image VM pour Ubuntu 24.04 et le site a commencé à renvoyer des 502 dans les minutes suivant le premier trafic.
Le responsable sur appel a fait ce que font les gens en alerte : il a redémarré nginx. Les erreurs ont baissé, puis sont revenues. Quelqu’un a accusé « un mauvais déploiement », mais rien n’avait changé dans le code applicatif. C’était vrai. Mais ça n’importait pas.
L’hypothèse erronée était subtile : ils supposaient que les permissions de fichiers étaient identiques qu’avant. Dans la nouvelle image, une étape de durcissement avait changé l’utilisateur master/worker nginx et durci les profils AppArmor. Le socket avait l’air correct à première vue. La propriété était correcte. Le mode était correct. Mais AppArmor refusait la connexion.
Une fois qu’ils ont regardé dmesg et vu le refus, la correction a été embarrassante de simplicité : autoriser le chemin du socket dans le profil nginx (ou aligner le socket sur le chemin attendu). Les 502 ont disparu instantanément.
Le postmortem n’a pas été « AppArmor est mauvais ». Le postmortem a été « nous avons supposé qu’un contrôle de sécurité n’affecterait pas l’exécution ». Cette supposition, voilà comment on se fait réveiller.
Mini-story 2: The optimization that backfired
Une autre équipe voulait réduire les temps de réponse et le CPU. Ils ont activé un logging d’accès plus agressif et ajouté des champs de timing par requête pour pouvoir tracer « où passe le temps ». Ça a marché. Leurs dashboards se sont embellis. Leur ego a grandi en conséquence.
Puis le trafic a augmenté, et des 504 sont apparus lors des pics. L’équipe applicative jurait que rien n’avait changé. L’équipe BD jurait que ce n’était pas eux. Les logs du proxy montraient des timeouts upstream, mais les métriques backend semblaient « plutôt correctes ».
Le coupable : l’optimisation a créé un nouveau chemin chaud. Le logging s’est transformé en écritures disque synchrones sur un volume cloud avec IOPS limités. Sous charge, l’utilisation disque a atteint le plafond. L’iowait a monté. Les threads workers se sont bloqués. Le proxy a attendu, puis a expiré. Classique 504.
Ils ont corrigé en rendant le logging moins coûteux : buffering, réduction de verbosité, et déplacement du logging lourd hors du disque principal. La correction la plus significative a été organisationnelle : traiter l’observabilité comme une charge de production, pas comme un bonbon gratuit.
Mini-story 3: The boring but correct practice that saved the day
Une entreprise pour laquelle j’ai travaillé avait une règle que tout le monde moquait : « Ne redémarrez pas les services avant d’avoir capturé 10 minutes de preuves. » Les gens se plaignaient que ça ralentissait la réponse aux incidents. La direction l’a maintenu quand même, parce qu’ils avaient déjà été brûlés.
Un après-midi, des 502 sont apparus sur un cluster web multi-tenant. Le geste facile aurait été de rebouter tout. Ils ne l’ont pas fait. Ils ont capturé les logs d’erreur nginx, les journaux backend, et un rapide snapshot iostat/vmstat depuis deux nœuds affectés.
Les preuves ont montré un motif : des pics d’iowait exactement quand un job planifié tournait. Ce job tournait la rotation et la compression de gros logs localement, en collision avec un burst de checkpoint BD. Le sous-système de stockage n’a pas tenu, et les requêtes upstream se sont accumulées derrière.
Parce qu’ils avaient des données, la correction a été rapide et chirurgicale : replanifier et brider le job, ajuster le checkpointing BD, et scinder les logs sur un volume différent. Pas de « on pense que ». Pas de guessing héroïque. Juste de la discipline ennuyeuse qui a empêché une panne récurrente.
Listes de vérification / plan étape par étape
Checklist A: Stop the bleeding without deleting evidence
- Capturez les 200 dernières lignes des logs d’erreur du proxy et des access logs pour la fenêtre défaillante.
- Capturez les logs des services backend pour les mêmes horodatages.
- Capturez
vmstat 1 5etiostat -x 1 3une fois, depuis un nœud affecté. - Si le site fond : réduisez la charge (limites de débit, désactiver temporairement des endpoints non critiques, réduire des fonctionnalités coûteuses) avant de redémarrer tout.
Checklist B: Confirm the failing hop
- Depuis l’hôte proxy,
curlez l’upstream directement (loopback/port/socket). - Depuis l’hôte upstream, testez les dépendances (BD/cache) avec de courts timeouts.
- Vérifiez les états de connexion (
ss -s) et les descripteurs si suspect. - Confirmez la vitesse de résolution DNS si l’upstream utilise des noms d’hôte.
Checklist C: Fix safely (minimum change that restores service)
- Si permission/socket/AppArmor : corrigez la politique/config ; rechargez les services ; vérifiez avec une seule requête.
- Si épuisement de workers : réduisez le trafic, augmentez la capacité de workers avec prudence, et corrigez la dépendance lente ou l’appel bloquant.
- Si saturation du stockage : arrêtez l’écrivain bruyant, replanifiez les jobs, ou déplacez l’I/O chaud vers un disque mieux adapté.
- Alignez les timeouts LB → nginx → app → dépendances pour ne pas créer de retry storms.
Checklist D: Prevent recurrence
- Ajoutez des alertes sur iowait, %util disque, et profondeur de file backend — pas seulement le taux d’erreurs HTTP.
- Suivez les percentiles de temps de réponse upstream et le nombre de requêtes expirées.
- Documentez la topologie upstream en production (ports, sockets, services) et maintenez-la à jour.
- Faites des tests de charge après des mises à jour OS et après des « améliorations d’observabilité ».
Faits intéressants et contexte historique
- HTTP 502 et 504 sont des erreurs de passerelle : elles ont été conçues pour des intermédiaires — proxies, gateways et load balancers — se plaignant des upstreams, pas d’eux-mêmes.
- Nginx est devenu un reverse proxy par défaut principalement parce qu’il gérait la haute concurrence avec une faible mémoire comparé aux anciens modèles process-per-connection.
- Les sockets de domaine Unix sont antérieurs aux stacks web modernes et restent l’un des mécanismes IPC les plus simples et rapides sur Linux pour les services locaux.
- systemd a normalisé le « restart on failure » dans les flottes Linux ; cela a amélioré la disponibilité mais a aussi rendu les boucles de crash plus faciles à masquer si vous ne surveillez pas les logs.
- AppArmor est un contrôle de sécurité mainstream sur Ubuntu ; il explique souvent un « permission denied » alors que les permissions filesystem semblent correctes.
- Les tempêtes TIME_WAIT sont anciennes : elles frappent les services à fort trafic depuis les débuts des architectures web intensives en TCP, surtout sans keepalive.
- Les disques cloud ont souvent des plafonds de performance (IOPS/débit). Les pics de latence peuvent sembler « soudains » quand vous franchissez un seuil.
- Des timeouts mal alignés amplifient les retries : une couche timeoute, des retries surviennent, la charge augmente, et le système s’effondre plus vite que si vous aviez attendu.
FAQ
1) Should I restart nginx when I see 502/504?
Seulement après avoir capturé les logs et les signaux systèmes de base. Redémarrer peut temporairement libérer des workers ou des sockets bloqués, mais efface aussi la piste. Si c’est un vrai goulot (BD/stockage), les erreurs reviendront.
2) What’s the fastest way to tell 502 vs 504 root cause?
Lisez la ligne du log d’erreur nginx. « Connection refused » pointe vers l’atteignabilité/service arrêté. « Upstream timed out » pointe vers un upstream lent/bloqué ou une dépendance. « Permission denied » pointe vers les perms socket ou AppArmor.
3) Why do I see 504 but the backend logs show 200 responses?
Parce que le backend a continué à travailler après que le proxy ait abandonné. Le timeout du proxy a expiré, a fermé la connexion, et le backend a plus tard écrit une réponse pour personne. Alignez les timeouts et ajoutez des timeouts internes pour les dépendances.
4) Can storage really cause 504s even if CPU is low?
Oui. Un CPU faible avec un iowait élevé est une signature classique. Vos threads sont bloqués dans le noyau en attente d’I/O disque. Le proxy attend aussi, jusqu’à son timeout.
5) Why did Ubuntu 24.04 “suddenly” start doing this?
Ubuntu 24.04 en soi n’est généralement pas la cause ; la mise à jour change souvent des versions et des valeurs par défaut (versions php-fpm, unités de service, profils de sécurité). Ces changements exposent des fragilités existantes : limites serrées, chemins de socket fragiles, ou marge de stockage qui n’était jamais réelle.
6) How do I know if AppArmor is involved?
Cherchez des messages AppArmor DENIED dans dmesg ou le journal du noyau. Si vous voyez des refus impliquant nginx, php-fpm sockets, ou fichiers backend, traitez cela comme un problème de politique, pas comme un problème de chmod.
7) Is increasing nginx proxy_read_timeout a real fix?
Parfois, mais c’est généralement un retard, pas une vraie correction. Augmentez les timeouts seulement quand le travail est légitimement long et contrôlé (exports, rapports), et seulement si le backend peut le supporter sans bloquer ses workers.
8) How do I distinguish backend overload from a single slow dependency?
L’épuisement backend montre une montée du temps de file, l’épuisement des workers, et une augmentation large de la latence. Une dépendance lente unique montre des endpoints spécifiques en échec, corrélés à des verrous BD, des cache misses, ou la latence d’une API externe. Utilisez des métriques par endpoint et corrélez les logs par ID de requête si vous en avez.
9) Why do 502/504 appear in bursts?
Parce que le système oscille : le trafic le pousse en saturation, les timeouts déclenchent des retries, les workers redémarrent, les caches se réchauffent/froidissent, les files disque se vident, et vous obtenez un motif d’échec répétitif. Corrigez le goulot et l’oscillation s’arrêtera.
Prochaines étapes à entreprendre
Si vous êtes en plein incident : prenez le guide de diagnostic rapide et exécutez-le de bout en bout. Ne « faites pas un peu de tout ». Collectez des preuves, identifiez le saut défaillant, et corrigez la contrainte la plus étroite en premier.
Si vous avez dépassé l’incident et que vous voulez qu’il ne se reproduise pas :
- Rendez le stockage visible : alertez sur le
%utildisque,awaitet l’iowait. Les timeouts web commencent souvent par la latence disque. - Alignez les timeouts : assurez-vous que LB, nginx, serveur d’application, et appels BD ont un budget de timeout cohérent.
- Codifiez les attentes sur les sockets et la sécurité : si vous dépendez de sockets unix, documentez les chemins et permissions ; incluez les vérifications AppArmor dans votre validation de déploiement.
- Réduisez les jobs surprises : trouvez les sauvegardes/rotations/maintenances périodiques et assurez-vous qu’elles ne partagent pas le même chemin I/O que la latence des requêtes.
- Pratiquez l’opération « preuve d’abord » : l’habitude ennuyeuse de capturer logs et état système avant tout redémarrage est ce qui arrête le folklore des 504 récurrents.
Les 502/504 ne sont pas des mystères. Ce sont le proxy qui vous dit exactement où regarder. Écoutez-le, collectez les preuves, et corrigez le système plutôt que le symptôme.