Résoudre l'erreur nginx 502 Bad Gateway : causes et solutions
502 Bad Gateway signifie que nginx n'a reçu aucune réponse valable du backend. Voici les cinq causes les plus fréquentes, la bonne ligne dans le journal d'erreurs et la façon de prouver que le correctif tient vraiment.
Ce que signifie vraiment « 502 Bad Gateway »
Une erreur 502 ne vient pas de votre application, elle vient de nginx. nginx a accepté la requête, l'a transmise à un backend (PHP-FPM, Node, Python, un autre serveur web) et n'en a reçu aucune réponse exploitable. C'est pour cette raison que la page d'erreur n'affiche rien d'utile.
Savoir distinguer les codes voisins fait gagner beaucoup de temps le jour où cela compte :
- 500 Internal Server Error : le backend a répondu, mais sa réponse était une erreur. La cause se trouve dans le code de l'application. Lisez le journal de l'application, pas celui de nginx.
- 502 Bad Gateway : la connexion vers le backend n'a pas pu être établie, ou elle a été coupée avant qu'une réponse complète soit arrivée.
- 504 Gateway Time-out : la connexion tenait, mais le backend est resté muet trop longtemps et nginx a perdu patience.
Cette distinction est le levier le plus important en cas de dépassement de délai, car la même page lente apparaît tantôt en 502, tantôt en 504, selon qui abandonne en premier. Nous y revenons plus bas.
L'état des paquets sur Debian 13, Debian 12, Ubuntu 24.04 et Ubuntu 22.04
nginx se comporte de façon identique face aux erreurs 502 sur les quatre systèmes, les directives portent les mêmes noms. Les différences se concentrent presque entièrement du côté de PHP, et c'est précisément de là que naissent la plupart des 502 après un changement de distribution.
| Système | nginx | PHP | Nom du service | Socket |
| Debian 13 (Trixie) | 1.26.3 | 8.4 | php8.4-fpm | /run/php/php8.4-fpm.sock |
| Debian 12 (Bookworm) | 1.22.1 | 8.2 | php8.2-fpm | /run/php/php8.2-fpm.sock |
| Ubuntu 24.04 LTS | 1.24.0 | 8.3 | php8.3-fpm | /run/php/php8.3-fpm.sock |
| Ubuntu 22.04 LTS | 1.18.0 | 8.1 | php8.1-fpm | /run/php/php8.1-fpm.sock |
Dans toutes les commandes qui suivent, remplacez le numéro de version par celui de votre système. Tous les exemples partent d'un shell root, sinon placez sudo devant. Pour savoir quelle version est installée, jetez un œil aux binaires FPM, même si le service ne démarre pas du tout :
ls /usr/sbin/php-fpm*
ls /etc/php/
D'abord le journal d'erreurs : trouver la bonne ligne
L'erreur de diagnostic la plus fréquente consiste à chercher dans le mauvais journal. nginx dispose d'un journal d'erreurs global et, souvent, d'un journal propre à chaque hôte virtuel. La configuration indique quel fichier s'applique :
grep -Rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-enabled/
Faites attention au -R majuscule. Sur Debian et Ubuntu, /etc/nginx/sites-enabled/ ne contient que des liens symboliques vers sites-available, et GNU grep ne suit aucun lien symbolique lors de sa descente récursive avec le -r minuscule. Avec -rn, vous n'obtenez donc que les résultats issus de nginx.conf, tandis que la ligne error_log propre au vhost reste invisible : justement celle que l'on cherche en cas de 502, car le journal global ne contient pas l'erreur FastCGI dès lors que le vhost redirige. Si vous préférez rester sur -r, greppez directement les répertoires source :
grep -rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-available/ /etc/nginx/conf.d/
Sans indication propre dans le bloc server, tout atterrit dans /var/log/nginx/error.log. Le chemin le plus fiable vers la bonne ligne passe par une capture en direct : gardez le journal ouvert dans un terminal, déclenchez la requête dans un second, puis regardez les lignes qui viennent de s'ajouter.
tail -f /var/log/nginx/error.log
Vous pouvez aussi filtrer sur l'horodatage. nginx écrit l'heure locale au format 2026/07/26 09:14:22, pas en UTC. Une comparaison avec une horloge réglée sur un autre fuseau horaire tourne régulièrement mal.
Une ligne 502 est toujours construite sur le même modèle. Exemple :
2026/07/26 09:14:22 [error] 812#812: *3 connect() to unix:/run/php/php8.2-fpm.sock
failed (2: No such file or directory) while connecting to upstream,
client: 203.0.113.7, server: example.com,
request: "GET /index.php HTTP/1.1",
upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "example.com"
Quatre éléments portent toute l'information :
- L'appel système :
connect(),recv(),send().connect()signifie qu'aucune connexion n'a jamais été établie.recv()signifie que la connexion tenait puis a été coupée. - Le numéro d'erreur entre parenthèses, voir le tableau plus bas. C'est là que se trouve le vrai diagnostic.
- La phase :
while connecting to upstreampar opposition àwhile reading response header from upstream. Le premier cas est un problème d'accessibilité, le second un problème d'exécution ou de plantage. - Le champ
upstream:. Il contient le chemin ou l'adresse que nginx a réellement utilisé. Pas ce que vous supposez dans la configuration, mais ce qui est effectivement chargé.
| Message | Signification | Section |
| 2: No such file or directory | Le fichier socket n'existe pas | Service arrêté ou chemin erroné |
| 13: Permission denied | Le socket existe, nginx n'a pas le droit d'y accéder | Droits |
| 111: Connection refused | Rien n'écoute sur cette adresse et ce port | Backend injoignable |
| 110: Connection timed out | Aucune réponse dans le délai imparti | Délai dépassé |
| 104: Connection reset by peer | Le processus backend est mort au milieu de la requête | Plantages et limites |
| 11: Resource temporarily unavailable | La file d'attente du socket est pleine | Plantages et limites |
Chez nginx, la ligne relative à 13: Permission denied porte souvent le niveau [crit] au lieu de [error]. Qui filtre uniquement sur [error] passe à côté. Filtrez plutôt sur le texte :
grep -n "upstream" /var/log/nginx/error.log
La seconde moitié de la vérité se trouve dans le journal de PHP-FPM, par défaut sous /var/log/php8.2-fpm.log. En cas de plantage ou de limite atteinte, la raison y figure, alors que nginx ne voit que le symptôme.
Cause 1 : PHP-FPM ne tourne pas
Le classique numéro d'erreur 2. Vérifiez d'abord l'état du service :
systemctl is-active php8.2-fpm
systemctl status php8.2-fpm --no-pager -l
is-active répond par un seul mot, ce qui suffit pour un script. Si vous obtenez inactive ou failed, allez chercher la raison dans le journal systemd, avec une fenêtre de temps plutôt que les dix dernières lignes :
journalctl -u php8.2-fpm --since "30 min ago" --no-pager
Très souvent, la cause est une configuration de pool cassée, restée en place après un reload. FPM possède son propre test de syntaxe, qui s'exécute sans redémarrage :
php-fpm8.2 -t
Les erreurs de démarrage typiques, dans leur formulation exacte, et ce qu'elles signifient :
ERROR: [pool www] cannot get uid for user 'webuser': l'utilisateur système indiqué dansuser =n'existe plus, par exemple après une migration.ERROR: unable to bind listening socket for address '/run/php/php8.2-fpm.sock': No such file or directory (2): le répertoire/run/phpmanque. Il se trouve sur un tmpfs et il est créé au démarrage du service. Si vous faites pointerlistenvers un chemin situé en dehors, vous devez faire créer ce répertoire vous-même.ERROR: An another FPM instance seems to already listen on ...: un processus issu d'un redémarrage raté est encore accroché au socket.
Comment savoir que le problème est vraiment résolu : pas parce que systemctl restart s'est terminé sans rien afficher. Un maître FPM démarre même lorsque aucun processus de travail n'est capable d'accepter une requête. Ce qui compte, c'est que le socket soit visible dans le système et que FPM y réponde.
ss -lx | grep php
Pour un véritable test de réponse, activez la ligne ping.path = /ping dans /etc/php/8.2/fpm/pool.d/www.conf, rechargez FPM et interrogez le socket directement, en contournant complètement nginx :
apt-get install -y libfcgi-bin
SCRIPT_NAME=/ping SCRIPT_FILENAME=/ping REQUEST_METHOD=GET \
cgi-fcgi -bind -connect /run/php/php8.2-fpm.sock
Si pong revient, le côté PHP est en ordre et l'erreur se situe entre nginx et le socket. Si rien ne revient, inutile de chercher plus loin du côté de nginx.
Cause 2 : mauvais chemin de socket
C'est de loin la cause la plus fréquente sur Debian et Ubuntu, parce que le socket porte le numéro de version de PHP dans son nom, que la configuration nginx inscrit ce nom en dur, et qu'une montée de version de la distribution sépare les deux.
Concrètement : une montée de Debian 12 vers Debian 13 fait passer PHP de 8.2 à 8.4. L'ancien socket /run/php/php8.2-fpm.sock disparaît, alors qu'il figure toujours dans le fichier vhost. Résultat : un 502 sur chacune des pages PHP, immédiatement après le redémarrage. La même chose se produit lors du passage d'Ubuntu 22.04 à 24.04 (8.1 vers 8.3).
Un deuxième piège se cache dans la configuration d'exemple livrée avec le paquet. /etc/nginx/sites-available/default contient un bloc commenté dont le fastcgi_pass pointe vers une version de PHP qui n'est plus d'actualité depuis des années. Décommenter simplement ces lignes, c'est créer le 502 soi-même.
Comparez les deux côtés. Ce que nginx veut utiliser :
grep -Rn "fastcgi_pass" /etc/nginx/
Ce que FPM propose réellement :
grep -n "^listen *=" /etc/php/*/fpm/pool.d/*.conf
L'ajout de *= dans le motif de recherche est volontaire : il exige un nombre quelconque d'espaces après listen, puis un signe égal. Un simple ^listen attrape en effet aussi listen.owner, listen.group et listen.mode, et la ligne recherchée se perd au milieu des résultats. Le joker /etc/php/*/ est en revanche correct tel quel, il couvre chaque version de PHP installée. Et voici ce qui existe réellement sur le système en fonctionnement :
ls -l /run/php/
Les trois sorties doivent afficher le même chemin. Important : lancez grep -R sur /etc/nginx/ et pas uniquement sur le fichier que vous soupçonnez. Les fragments inclus via include sont une cachette appréciée, tout comme les anciens fichiers de sites-available encore actifs à cause d'un lien symbolique oublié dans sites-enabled. Là encore : seul le -R majuscule suit ces liens symboliques et vous montre ainsi quel fichier est réellement actif.
ls -l /etc/nginx/sites-enabled/
Avant de modifier quoi que ce soit, faites une copie. Cela prend deux secondes et vous évite, le cas échéant, une restauration depuis la sauvegarde :
mkdir -p /root/backups
cp -a /etc/nginx/sites-available/default /root/backups/default.bak
Si vous intervenez plusieurs fois, ajoutez plutôt un horodatage (default.bak.$(date +%F-%H%M)), car cp -a écrase sans le moindre avertissement un .bak existant.
Après la correction, testez toujours d'abord, rechargez ensuite. reload plutôt que restart, pour que les connexions en cours ne soient pas coupées :
nginx -t
systemctl reload nginx
Attention : nginx -t ne vérifie que la syntaxe. Un chemin de socket qui n'existe pas constitue une configuration parfaitement valide. Un syntax is ok en vert ne prouve donc pas que le 502 a disparu.
Cause 3 : droits sur le socket
Numéro d'erreur 13. Le socket est bien là, nginx n'a simplement pas le droit de l'ouvrir. Sur Debian et Ubuntu, nginx tourne sous l'utilisateur www-data, et le pool par défaut de FPM crée le socket en conséquence. Cela se lit dans le fichier de pool :
grep -n "listen.owner\|listen.group\|listen.mode" /etc/php/*/fpm/pool.d/*.conf
Avec listen.owner = www-data, listen.group = www-data et le mode 0660, l'ensemble fonctionne sans rien faire. Trois situations peuvent mal tourner :
- Un pool propre à chaque projet. Si
useretgrouppointent vers un utilisateur de projet,listen.groupdoit malgré tout être un groupe dont nginx fait partie. L'usage courant estlisten.owner = utilisateurprojetaveclisten.group = www-data. - Socket en dehors de /run. Le fichier socket n'est pas seul concerné : chaque répertoire du chemin qui y mène doit accorder le droit d'exécution à nginx. Un socket placé dans un répertoire personnel en mode 0700 n'est accessible à personne d'autre que son propriétaire.
- nginx avec un
usermodifié dans/etc/nginx/nginx.conf.
Vous confirmez le soupçon sans deviner, en tentant l'accès exactement sous l'utilisateur qui en a besoin en production :
id www-data
sudo -u www-data test -w /run/php/php8.2-fpm.sock && echo "Accès autorisé" || echo "Aucun accès"
L'appel cgi-fcgi de la section précédente est encore plus parlant, lui aussi précédé de sudo -u www-data. Si FPM répond en root mais pas en tant que www-data, le diagnostic est sans ambiguïté.
Définissez les valeurs dans le fichier de pool, pas avec chmod sur le fichier socket. Un chmod 666 tient exactement jusqu'au prochain redémarrage de FPM : celui-ci recrée alors le socket avec les droits configurés, et l'erreur revient, en général au moment le plus défavorable.
Sur Debian et Ubuntu, AppArmor est actif. Un profil pour nginx est livré, mais il n'est pas appliqué en configuration d'origine ; des modèles de durcissement peuvent toutefois l'avoir activé. Si un message 13 persiste malgré des droits corrects, un coup d'œil à aa-status et à journalctl -k | grep DENIED vaut la peine.
Cause 4 : délai dépassé sur les requêtes longues
C'est ici que la méthode se sépare des devinettes, car le simple épuisement du délai de nginx produit un 504, pas un 502. Quand une requête longue se termine par un 502, c'est presque toujours que PHP-FPM a supprimé le processus de travail avant, et nginx n'a plus vu qu'une connexion coupée. Le journal contient alors typiquement :
recv() failed (104: Connection reset by peer) while reading response header from upstream
Trois délais agissent en même temps, et leur ordre décide du code de statut :
max_execution_timedansphp.ini, 30 secondes par défaut en mode FPM. Ne compte que le temps d'exécution du script. Sous Linux, l'attente dans les appels système, par exemple sur une requête de base de données bloquée, n'est pas comptabilisée. C'est pourquoi cette valeur ne vous sauve pas dans le cas précis où vous l'attendez.request_terminate_timeoutdans le fichier de pool, désactivé en configuration d'origine. Met fin brutalement au processus de travail, quelle que soit la raison du blocage. C'est cette valeur qui produit des 502.fastcgi_read_timeoutdans nginx, 60 secondes par défaut. S'il expire, vous obtenez un 504.
L'ordre utile va en croissant, de l'intérieur vers l'extérieur, pour que ce soit toujours la couche encore capable de produire un message d'erreur compréhensible qui agisse en premier. Par exemple 60, puis 75, puis 90 secondes. Dans l'ordre inverse, vous récoltez des 502 au lieu d'erreurs PHP lisibles.
grep -rn "request_terminate_timeout" /etc/php/*/fpm/pool.d/*.conf
Ce que FPM a supprimé figure en clair dans son propre journal :
WARNING: [pool www] child 1234, script '/var/www/html/import.php'
(request: "POST /import.php") execution timed out (76.271849 sec), terminating
Avant de relever les délais, faites-vous montrer où passe le temps. FPM apporte pour cela un journal dédié, qui écrit une pile d'appels PHP complète en cas de dépassement. À activer dans le fichier de pool :
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s
Après un systemctl reload php8.2-fpm, la prochaine requête lente y inscrit la fonction qui bloque, numéro de ligne compris. En pratique, dans quatre cas sur cinq, il s'agit d'une requête de base de données sans index ou d'un appel vers une interface de programmation externe sans délai propre. Relever les délais ne fait alors que rallonger le temps d'attente avant l'erreur et bloque en plus des processus de travail.
Cause 5 : backend injoignable
Concerne tout backend contacté par TCP : FPM sur le port 9000, une application Node, un service Java, un conteneur. Le message directeur est le numéro d'erreur 111.
connect() to 127.0.0.1:3000 failed (111: Connection refused) while connecting to upstream
Vérifiez d'abord si quelque chose écoute, et surtout sur quoi :
ss -ltnp
Cette commande liste exclusivement les sockets TCP, d'où une idée fausse très répandue : un pool PHP-FPM en configuration d'origine écoute sur un socket Unix sous /run/php/ et n'apparaît pas du tout dans cette liste, alors qu'il fonctionne parfaitement. Il ne devient visible que de cette façon :
ss -lxn | grep php-fpm
ls -l /run/php/
Pour FPM, ss -ltnp n'est donc parlant que si le pool a été basculé volontairement sur TCP via listen = 127.0.0.1:9000. Pour des backends Node, Java ou en conteneur, c'est en revanche exactement la bonne commande.
Trois pièges que l'on trouve rarement dans les tutoriels :
- localhost se résout d'abord en ::1. Si nginx contient
proxy_pass http://localhost:3000;alors que l'application n'écoute que sur127.0.0.1, nginx tente l'adresse IPv6 et reçoit « Connection refused ». Le service tourne, le port est ouvert, et pourtant vous avez un 502. Solution : écrire127.0.0.1en toutes lettres dans nginx, ou faire écouter l'application sur les deux familles d'adresses. - nginx résout les noms une seule fois, au chargement. Si le
proxy_passcontient un nom de machine, nginx retient l'adresse. Quand le backend change d'adresse IP, par exemple un conteneur qui vient de redémarrer, les requêtes tournent dans le vide jusqu'au prochainreload. - Pare-feu sur le chemin du retour. Avec un backend hébergé sur un autre serveur, vous voyez apparaître
113: No route to hostou un dépassement de délai au lieu de « Connection refused ». Vérifiez avecufw statuset avec un test de connexion direct depuis le serveur nginx.
Si un bloc upstream comportant plusieurs cibles est utilisé, un message supplémentaire s'y ajoute :
no live upstreams while connecting to upstream
Cela signifie que nginx a retiré toutes les cibles de la circulation pour la durée de fail_timeout, après des tentatives infructueuses répétées. Même une fois le backend réparé, il faut donc attendre l'expiration de ce délai avant que les requêtes repassent. Un systemctl reload nginx réinitialise cet état immédiatement.
Le 502 qui ne correspond à aucune des cinq causes
Deux cas ressemblent à une panne sans en être une, et coûtent pour cette raison un temps supérieur à la moyenne.
En-tête de réponse trop grand. L'application fonctionne parfaitement, seules certaines requêtes renvoient un 502 :
upstream sent too big header while reading response header from upstream
Les déclencheurs sont de gros cookies ou des attributs de session placés dans les en-têtes, le tampon de nginx est trop petit. Dans le bloc server ou location :
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 64k;
Avec proxy_pass, les directives s'appellent proxy_buffer_size et proxy_buffers. Typiquement, seuls les utilisateurs connectés sont touchés, tandis que la page d'accueil se charge sans le moindre problème.
Plus de processus de travail disponibles. Sous charge, le journal de FPM affiche :
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Les nouvelles requêtes patientent alors dans la file d'attente du socket. Si celle-ci est pleine elle aussi, nginx signale 11: Resource temporarily unavailable. Avant d'augmenter pm.max_children, faites un rapide calcul : la mémoire disponible divisée par la consommation réelle d'un processus de travail. Une valeur trop élevée échange les 502 contre un état système où la mémoire vient à manquer, ce qui touche ensuite la base de données également.
Plantages. Des lignes contenant exited on signal 11 (SIGSEGV) indiquent une extension PHP défectueuse, souvent après un changement de version de PHP avec des modules restés en place depuis la version précédente.
Quand l'intervention ne donne rien : le retour en arrière
Deux règles limitent les dégâts. Premièrement : une modification à la fois, avec une copie du fichier d'origine sous /root/backups, jamais dans le répertoire web. Deuxièmement : contrôlez après chaque étape, au lieu de changer trois choses en même temps et de ne plus savoir ensuite laquelle a aidé.
Si nginx tombe complètement après une modification, restaurez la copie et rechargez :
cp -a /root/backups/default.bak /etc/nginx/sites-available/default
nginx -t
systemctl reload nginx
Si nginx ne démarre plus après un restart, systemctl status nginx en dit rarement assez. Plus parlant :
journalctl -u nginx --since "10 min ago" --no-pager
La raison la plus fréquente d'un restart qui échoue alors que la configuration est syntaxiquement irréprochable est un port 80 ou 443 déjà occupé, le plus souvent par un processus issu de l'exécution précédente. ss -ltnp | grep ':80' montre le coupable.
Comment savoir que le problème est vraiment corrigé
Une commande sans message d'erreur ne prouve rien du tout. systemctl reload reste tout aussi silencieux quand rien n'a changé sur le fond, et nginx -t ne vérifie que la syntaxe. Seules ces quatre preuves sont solides :
- Interroger le code de statut directement sur le serveur, pour qu'aucun cache ni service placé en amont ne fausse le résultat :
Le résultat attendu est 200, pas 502. Avec plusieurs hôtes virtuels, indiquez le nom :curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1/curl -H "Host: example.com" ... - Le journal d'erreurs reste muet. Videz-le avant le test avec
truncate -s 0 /var/log/nginx/error.log, déclenchez plusieurs requêtes, puis regardez à nouveau dedans. Un fichier vide, voilà la vraie preuve. - FPM répond sur le socket, en contournant nginx, via
cgi-fcgiet en tant quewww-data. La question des droits et celle du chemin sont ainsi réglées en une seule étape. - Un redémarrage ne change rien. Le point le plus important, et le plus souvent sauté. Beaucoup de mesures d'urgence (droits posés à la main, répertoires créés manuellement sous
/run, un service démarré mais pas activé) ne survivent pas à un redémarrage. Vérifiezsystemctl is-enabled php8.2-fpm nginxet redémarrez le serveur une fois de façon contrôlée, tant que vous êtes encore devant, plutôt que de laisser cela à la prochaine fenêtre de maintenance.
Sur les serveurs root KVM et les serveurs dédiés de KernelHost, vous effectuez ce redémarrage, accès console compris, depuis l'espace client, même quand le service web n'est plus joignable. Les serveurs se trouvent dans le datacenter maincubes à Francfort (TÜV TIER3+), raccordés à un réseau qui nous appartient et doté d'une protection DDoS. Pour aller plus loin : résoudre l'erreur nginx 504 Gateway Time-out et calculer correctement pm.max_children pour PHP-FPM.
Aide-mémoire pour les situations d'urgence
tail -f /var/log/nginx/error.log, déclenchez la requête, notez le numéro d'erreur.- Numéro 2 ou 111 : le service tourne-t-il, le chemin est-il correct.
ss -lx | grep phpface àgrep -Rn "fastcgi_pass" /etc/nginx/. - Numéro 13 : les droits dans le fichier de pool, pas par
chmod. - Numéro 104 ou 110 : lisez le journal FPM et le
slowlog, ensuite seulement parlez des délais. - Message « too big header » : augmentez les tailles de tampon.
- Après le correctif : videz le journal, testez à nouveau, redémarrez le serveur une fois.
Questions fréquentes
Pourquoi est-ce que j'obtiens un 502 et non un 504, alors que la page est simplement lente ?
Après la montée vers Debian 13, toutes les pages PHP renvoient un 502. Que dois-je changer ?
Est-ce que « nginx -t » suffit à prouver que l'erreur est corrigée ?
Comment trouver dans le journal d'erreurs la ligne qui correspond à mon 502 ?
Le 502 ne touche que les utilisateurs connectés, la page d'accueil se charge normalement. D'où cela vient-il ?
J'ai corrigé les droits du socket avec chmod, après un redémarrage l'erreur est revenue. Pourquoi ?
2026 KernelHost GmbH. Tous droits réservés. Ce guide est protégé par le droit d'auteur. Sa republication sur d'autres sites web, même partielle ou sous une forme modifiée, n'est pas autorisée sans notre accord écrit. Les citations accompagnées de la source et d'un lien sont expressément les bienvenues.

