Résoudre l'erreur nginx 504 Gateway Time-out : trouver la cause au lieu de relever le délai
Avec un 504, le backend était joignable, il a simplement répondu trop lentement. Comment déterminer avec le journal des temps où le temps se consomme, lequel des nombreux délais s'applique vraiment et pourquoi un délai plus long ne fait le plus souvent que repousser la panne.
Une erreur 504 Gateway Time-out est la plus patiente de toutes les pages d'erreur. nginx a accepté la requête, l'a transmise au backend, puis il a attendu jusqu'à l'expiration d'un délai fixé en interne. Le backend est resté joignable pendant tout ce temps, il n'a simplement pas répondu à temps. C'est exactement ce qui distingue ce code de son voisin : avec le 502 Bad Gateway, le backend répond mal ou pas du tout, avec le 504, il répond trop lentement. Ce guide montre comment mesurer où le temps se consomme réellement, laquelle des nombreuses limites de temps s'applique vraiment, et pourquoi relever cette limite est presque toujours la moins bonne des réponses disponibles.
Toutes les indications valent pour Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS et Ubuntu 22.04 LTS. Les commandes sont écrites pour une exécution en tant que root ; en utilisateur normal, placez sudo devant. Les exemples utilisent PHP 8.4, remplacez le numéro de version par celui de votre système :
| Système | PHP | Service | Configuration |
|---|---|---|---|
| Debian 13 (trixie) | 8.4 | php8.4-fpm | /etc/php/8.4/fpm/ |
| Debian 12 (bookworm) | 8.2 | php8.2-fpm | /etc/php/8.2/fpm/ |
| Ubuntu 24.04 LTS | 8.3 | php8.3-fpm | /etc/php/8.3/fpm/ |
| Ubuntu 22.04 LTS | 8.1 | php8.1-fpm | /etc/php/8.1/fpm/ |
ls /etc/php/
Les directives nginx portent les mêmes noms sur les quatre systèmes. Les différences se situent du côté de PHP et du côté de la base de données, et elles sont signalées aux endroits concernés.
Savoir qui a abandonné indique où chercher
Avant d'ouvrir le moindre fichier, répondez à une question : quelle couche a abandonné ? Le code de statut le dit déjà.
| Code | Ce qui s'est passé | Où chercher |
|---|---|---|
| 500 Internal Server Error | Le backend a répondu, sa réponse était une erreur | Journal de l'application |
| 502 Bad Gateway | La connexion n'a pas pu être établie ou elle a été coupée | Service, socket, droits, plantages |
| 504 Gateway Time-out | La connexion tenait, la réponse n'est pas arrivée dans le délai | Temps d'exécution dans le backend |
| 408 Request Timeout | Le visiteur n'a pas fini d'envoyer sa propre requête à temps | Envois de fichiers, raccordements lents |
| 499 (seulement dans le journal) | Le visiteur a abandonné avant que nginx ait terminé | Trop lent, mais sous le délai |
La ligne du 499 est la plus sous-estimée. Ce n'est pas une erreur, c'est un système d'alerte précoce : le visiteur a fermé l'onglet parce que la page mettait trop de temps. Si un 504 disparaît après que vous avez relevé le délai et que des 499 apparaissent à la place, rien n'est résolu.
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
Le champ 9 correspond au format standard combined. Beaucoup de 504 pour peu de 499 indiquent quelques pages lourdes isolées, l'image inverse trahit une application poussive de bout en bout.
Avant toute modification : le chemin du retour
Le diagnostic des deux sections suivantes est purement en lecture. Le risque commence seulement là où vous touchez aux configurations, et cela peut arrêter l'exploitation de trois façons : une configuration nginx erronée empêche le démarrage du serveur web, un fichier de pool erroné celui de PHP-FPM, et des limites relevées trop généreusement peuvent épuiser la RAM. Le dernier cas est le plus désagréable, car le noyau met alors fin à des processus, et ce n'est pas forcément le fautif qui est touché. Si le service SSH y passe, le serveur n'est plus pilotable par le réseau.
Commencez donc par créer des copies, sous /root et jamais dans le répertoire web. L'horodatage dans le nom compte, car on intervient rarement une seule fois et cp -a écrase une copie existante sans le moindre avertissement :
mkdir -p /root/backups
cp -a /etc/nginx/nginx.conf /root/backups/nginx.conf.$(date +%F-%H%M)
cp -a /etc/nginx/sites-available/example.com /root/backups/example.com.$(date +%F-%H%M)
cp -a /etc/php/8.4/fpm/php.ini /root/backups/php.ini.$(date +%F-%H%M)
cp -a /etc/php/8.4/fpm/pool.d/www.conf /root/backups/www.conf.$(date +%F-%H%M)
Le chemin du retour tient en trois lignes, et l'ordre n'a rien d'accidentel :
cp -a /root/backups/example.com.2026-09-03-1030 /etc/nginx/sites-available/example.com
nginx -t
systemctl reload nginx
Utilisez reload plutôt que restart tant que c'est possible. reload ne reprend la nouvelle configuration que si elle est exempte d'erreur. Un restart arrête d'abord le processus en cours et vous laisse sans serveur web en cas d'erreur.
Si le serveur ne répond plus du tout, ouvrez la console VNC dans l'espace client, sur les serveurs root KVM et les serveurs dédiés de KernelHost. Elle est rattachée à la couche de virtualisation, ou au raccordement lui-même, et non à la pile réseau du système invité : elle fonctionne donc encore quand plus aucun service n'est joignable. Connectez-vous-y une fois à l'avance et assurez-vous de connaître le mot de passe root. Commande de contrôle après chaque intervention :
systemctl is-active nginx php8.4-fpm
free -m
La ligne du journal d'erreurs qui tranche le cas
Un 504 laisse toujours une trace :
2026/09/03 10:12:33 [error] 812#812: *5 upstream timed out (110: Connection timed out)
while reading response header from upstream, client: 203.0.113.7, server: example.com,
request: "GET /report.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.4-fpm.sock:"
Ce qui est décisif n'est pas le numéro d'erreur 110, identique pour tous les 504, mais la phase indiquée juste après :
while connecting to upstream: la connexion n'a pas pu être établie. Avec un backend distant, c'est presque toujours un filtre de paquets qui jette les paquets au lieu de les rejeter, car un rejet reviendrait immédiatement et donnerait un 502.while sending request to upstream: nginx n'a pas réussi à écouler le corps de la requête, typique des envois de fichiers volumineux.while reading response header from upstream: le cas normal. Le backend a tout reçu et calcule sans envoyer la moindre ligne d'en-tête.while reading upstream: les en-têtes sont arrivés, puis le corps s'est bloqué. Vous voyez cela sur les réponses en streaming et sur les exports.
grep -n "upstream timed out" /var/log/nginx/error.log | tail -20
Beaucoup d'hôtes virtuels écrivent dans un journal d'erreurs qui leur est propre. Où se trouve ce fichier et pourquoi le -R majuscule est nécessaire pour chercher dans sites-enabled, c'est expliqué dans l'article consacré au 502. Si la recherche reste vide alors que le navigateur affiche un 504, l'erreur ne vient pas de ce nginx.
Où le temps se consomme : mesurer au lieu de deviner
nginx sait consigner, pour chaque requête, le temps qu'a pris le backend. C'est l'étape la plus importante du diagnostic, parce qu'elle répond sans supposition à la question « application ou liaison ». Dans le bloc http de /etc/nginx/nginx.conf :
log_format kh_timing '$time_iso8601 $status rt=$request_time '
'uct=$upstream_connect_time uht=$upstream_header_time '
'urt=$upstream_response_time "$request"';
Dans le bloc server concerné, ajoutez une seconde ligne de journalisation. Le journal d'accès existant reste intact, nginx écrit dans les deux :
access_log /var/log/nginx/timing.log kh_timing;
nginx -t
systemctl reload nginx
tail -n 5 /var/log/nginx/timing.log
Après quelques minutes d'exploitation, faites remonter les requêtes les plus lentes :
awk '{ t=$3; sub(/^rt=/, "", t); print t, $0 }' /var/log/nginx/timing.log | sort -rn | head -20
| Observation | Interprétation | Étape suivante |
|---|---|---|
| uct élevé avec un backend local | L'établissement de la connexion coince | File d'attente pleine sur le socket, résolution de noms lente |
| uht et urt presque identiques, tous deux élevés | Le backend calcule avant d'envoyer la première ligne d'en-tête | Application, base de données, interface tierce |
| uht faible, urt élevé | L'en-tête est arrivé vite, le corps arrive au goutte-à-goutte | Streaming, exports, boucles sur de nombreux enregistrements |
| urt faible, rt élevé | Le backend a été rapide, le temps s'est perdu ensuite | Liaison du visiteur, réponse très volumineuse |
| Un tiret au lieu d'un nombre | Aucun backend n'est intervenu | Fichier statique ou abandon avant la transmission |
Deux subtilités font gagner beaucoup de temps. Plusieurs valeurs séparées par des virgules dans un même champ signifient que la requête est partie vers plus d'une cible, il y a donc eu une nouvelle tentative. Et l'indice le plus parlant de tous : si la durée mesurée correspond à la seconde près à la valeur configurée, par exemple 60,001 secondes pour un délai de 60, c'est le délai qui a frappé et non le backend qui a renoncé de lui-même. Des valeurs bancales comme 43,7 secondes montrent qu'autre chose a freiné.
Quelle limite de temps s'applique réellement
nginx propose une bonne douzaine de directives pour les dépassements de délai, et l'heure la plus souvent perdue vient de ce que quelqu'un a touché la mauvaise. Celle qui s'applique dépend du module qui traite le bloc location.
| Directive | Valeur par défaut | S'applique dans les blocs avec | Effet à l'expiration |
|---|---|---|---|
| proxy_connect_timeout | 60s | proxy_pass | 504, « while connecting to upstream » |
| proxy_send_timeout | 60s | proxy_pass | 504, « while sending request to upstream » |
| proxy_read_timeout | 60s | proxy_pass | 504, le délai décisif avec les backends en proxy |
| fastcgi_connect_timeout | 60s | fastcgi_pass | 504, comme ci-dessus, pour PHP-FPM |
| fastcgi_send_timeout | 60s | fastcgi_pass | 504, comme ci-dessus |
| fastcgi_read_timeout | 60s | fastcgi_pass | 504, le délai décisif avec PHP |
| send_timeout | 60s | partout | pas de 504, la connexion vers le visiteur est fermée |
| client_body_timeout | 60s | partout | 408, pas 504 |
Le délai s'applique entre deux lectures, pas à la réponse entière. Un téléchargement qui dure dix minutes en livrant des données sans interruption passe sans problème. Un backend qui se tait pendant 61 secondes est éjecté. Sur les exports qui marquent des pauses, il est donc souvent plus utile de faire écrire régulièrement quelque chose à l'application que d'augmenter le délai.
send_timeout ne fait rien contre un 504. Ce délai concerne la transmission vers le visiteur. À son expiration, il n'y a pas de page d'erreur mais un téléchargement interrompu. Il ne devient pertinent que si vous désactivez la mise en tampon avec proxy_buffering off;, car un visiteur lent freine alors jusque dans le backend.
nginx accepte aussi des directives qui restent sans effet dans le bloc concerné. Un proxy_read_timeout 300s; dans un bloc PHP avec fastcgi_pass est syntaxiquement irréprochable, nginx -t répond syntax is ok, et la page continue de s'interrompre au bout de 60 secondes. L'inverse est tout aussi vrai. C'est la cause la plus fréquente d'un délai relevé qui reste sans effet. Ce qui s'applique réellement, la configuration assemblée le montre :
nginx -T | grep -E "read_timeout|send_timeout|fastcgi_pass|proxy_pass"
Si un chemin isolé doit réellement s'exécuter plus longtemps, réglez le délai exactement là et nulle part ailleurs. Le signe égal en fait une correspondance exacte, qui l'emporte sur le bloc général location ~ \.php$ chargé des autres fichiers PHP :
location = /admin/export.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_read_timeout 300s;
}
nginx -t
systemctl reload nginx
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" -H "Host: example.com" http://127.0.0.1/admin/export.php
PHP : pourquoi max_execution_time s'applique rarement ici
L'hypothèse la plus naturelle veut que PHP mette fin de lui-même à un script bloqué. Dans ce cas précis, c'est le plus souvent faux. D'abord le piège de mesure : php -i interroge la variante en ligne de commande. Celle-ci utilise sa propre configuration sous /etc/php/8.4/cli/ et tourne de toute façon sans limite d'exécution, elle ne dit donc rien de FPM. Les deux endroits qui font foi sont ceux-ci :
grep -n "^max_execution_time" /etc/php/8.4/fpm/php.ini
grep -rn "max_execution_time" /etc/php/8.4/fpm/pool.d/
Dans le fichier de pool, la valeur peut être écrasée par php_value[max_execution_time] ou par php_admin_value[max_execution_time]. Une valeur posée avec php_admin_value ne peut plus être modifiée depuis l'application avec ini_set(). Si votre framework relève lui-même la durée d'exécution et que cela reste soudain sans effet, l'explication est là.
Et maintenant le vrai point : sous Linux, le temps d'attente passé dans les appels système ne compte pas. L'horloge ne tourne que tant que le script calcule lui-même. S'il attend une requête de base de données, une interface tierce ou le système de fichiers, elle s'arrête. Un script peut ainsi rester collé dix minutes à une requête bloquée sans que la limite d'exécution intervienne jamais. C'est alors le délai de nginx qui y met fin, et le résultat est le 504.
Il en découle une règle inconfortable : max_execution_time protège des boucles infinies dans votre propre code, pas de l'attente. La seule limite dure du côté de PHP est request_terminate_timeout dans le fichier de pool, qui balaie le processus de travail quelle que soit la raison de son blocage. Elle produit toutefois un 502 et non un 504. Ordonnez les délais en croissant, de l'intérieur vers l'extérieur, pour que la couche qui peut encore produire un message compréhensible intervienne en premier. Cela fonctionne pour les scripts qui calculent, mais pas pour ceux qui attendent, exactement pour la raison qui vient d'être donnée. Là, il ne reste qu'à borner l'attente elle-même.
Pourquoi relever le délai est le plus souvent la mauvaise réponse
Faisons rapidement le calcul. Un pool avec pm.max_children = 10 dispose de dix processus de travail. Une page met 90 secondes. Dix appels simultanés occupent donc chacun d'eux pendant une minute et demie. Pendant ce temps, plus personne ne reçoit de page PHP, pas même la page d'accueil. Une sous-page lente s'est transformée en panne. Trois mécanismes amplifient le phénomène :
- Le visiteur recharge. Cela crée une requête supplémentaire sans libérer l'ancienne. PHP ne remarque le départ d'un visiteur qu'à la sortie suivante du script, et un script qui calcule ne produit rien pendant longtemps.
- nginx retente de lui-même. Dans un bloc
upstreamcomportant plusieurs cibles,proxy_next_upstreamvauterror timeoutpar défaut. Une requête expirée part vers le serveur suivant et la requête coûteuse s'exécute une seconde fois. Les requêtes en écriture sont exclues, celles en lecture non. On désactive cela avecproxy_next_upstream error;. - La supervision retente elle aussi. Un intervalle de contrôle de 60 secondes sur une page qui met 90 secondes crée une charge permanente qui n'est jamais résorbée.
S'y ajoute que personne n'attend cinq minutes devant une page web. Un délai de 300 secondes transforme un problème d'une minute en un problème de cinq minutes, et le visiteur est parti depuis longtemps pendant que le processus de travail continue de calculer.
Le niveau de saturation réel, c'est la page d'état de PHP-FPM qui le montre. Posez pm.status_path = /fpm-status dans le fichier de pool et créez dans le bloc server un accès joignable uniquement en local :
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
systemctl reload php8.4-fpm
nginx -t
systemctl reload nginx
curl -s -H "Host: example.com" http://127.0.0.1/fpm-status
Surveillez active processes, listen queue et max active processes. Si listen queue reste durablement au-dessus de zéro, le nombre de processus de travail ne suffit pas pour le temps d'exécution actuel des pages. C'est le moment de réduire ce temps d'exécution, pas d'augmenter le délai.
Les trois dévoreurs de temps habituels
Base de données
Dans la majorité des cas, le temps se cache ici. Regardez d'abord ce qui tourne en ce moment. Sur les quatre systèmes, l'accès root passe par défaut par le socket Unix, la commande se passe donc de mot de passe :
mysql -e "SHOW FULL PROCESSLIST;"
Les colonnes Time et State sont les plus parlantes. Des valeurs comme Sending data ou Waiting for table metadata lock avec des durées à deux chiffres en secondes, c'est votre cas. Pour chercher de façon systématique, servez-vous du journal des requêtes lentes, activable sans interrompre le service :
mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"
Le nom du fichier se lit dans la seconde sortie, car il varie : Debian mise habituellement sur MariaDB et écrit dans /var/log/mysql/mariadb-slow.log, tandis que sous Ubuntu le fichier porte un autre nom selon le serveur installé. Analysez au bout de quelques minutes puis désactivez de nouveau, car ce journal coûte des écritures :
mysqldumpslow -s t /var/log/mysql/mariadb-slow.log | head -30
mysql -e "SET GLOBAL slow_query_log = 0;"
L'option -s t trie par temps total. Vérifiez la requête la plus coûteuse avec EXPLAIN : il manque le plus souvent un index sur la colonne même qui sert au filtrage ou au tri. SET GLOBAL prend effet immédiatement, mais ne survit pas à un redémarrage de la base de données. Une limite haute par requête existe aussi du côté de la base : MariaDB connaît max_statement_time en secondes, MySQL max_execution_time en millisecondes, ce dernier uniquement pour les requêtes en lecture. Votre application reçoit ainsi une erreur propre au lieu d'un processus de travail occupé.
Interfaces tierces
Si votre site interroge un prestataire de paiement ou un serveur de licences à chaque appel, leur panne devient votre 504. Mesurez cet appel séparément, depuis le serveur :
curl -o /dev/null -s -w "dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n" https://api.example.com/status
Si dns sort déjà du lot, c'est la résolution de noms qui est en cause et non le correspondant ; contre-épreuve avec time getent hosts api.example.com. Dans le code, chaque appel externe a besoin de son propre délai, court : avec cURL, ce sont CURLOPT_CONNECTTIMEOUT et CURLOPT_TIMEOUT. Qui applique plutôt file_get_contents() à une adresse atterrit sur default_socket_timeout du php.ini, qui vaut 60 secondes par défaut :
grep -n "default_socket_timeout" /etc/php/8.4/fpm/php.ini
La règle empirique : la somme de tous les délais externes d'une requête doit rester sous fastcgi_read_timeout. Sinon nginx coupe avant que votre code ait pu afficher une page d'erreur compréhensible.
Système de fichiers et RAM
Un disque plein rend les écritures lentes ou impossibles, et cela touche en même temps les fichiers de session, les caches et les journaux. Vérifiez les deux, l'espace et les inodes :
df -h
df -i
La seconde commande est celle qu'on oublie souvent. Une partition peut être occupée à 40 % et refuser malgré tout le moindre fichier supplémentaire dès que les inodes sont épuisés. Le deuxième candidat est le manque de mémoire : si le système déporte sur le disque, chaque requête devient poussive sans qu'une requête précise en soit responsable.
vmstat 1 5
Si les colonnes si et so restent durablement au-dessus de zéro, le noyau écrit et relit sans arrêt sur le disque, et vous avez un problème de mémoire et non un problème de temps. La bonne façon de traiter cela est décrite dans configurer le swap et éviter les plantages par manque de mémoire. Le troisième candidat, ce sont les lecteurs réseau : un point de montage NFS bloqué fige tout processus qui y touche, et votre commande de diagnostic en fait partie. Placez donc un délai devant :
timeout 5 df -h
Les tâches longues n'ont pas leur place dans la requête
Certaines tâches prennent du temps, c'est ainsi : un rapport annuel, un import de 200 000 lignes, une conversion d'images. L'erreur n'est pas qu'elles durent longtemps, mais qu'un serveur web les attende. La forme propre comporte trois parties :
- La requête crée une tâche, dans une table ou dans une file d'attente, et répond immédiatement. Le code de statut approprié est 202, accompagné d'une adresse où l'état peut être consulté.
- Un processus de travail situé en dehors de nginx récupère les tâches et les exécute. Aucun délai ne lui souffle dans le cou, puisque personne ne l'attend.
- L'interface interroge l'état. Cette requête est toujours rapide, quelle que soit la durée de la tâche.
Faites tourner ce processus de travail comme un service à part entière, pour qu'il revienne de lui-même après un plantage et après un redémarrage. À quoi ressemble une telle unit, c'est ce que montre créer son propre service systemd. Pour des tâches à intervalles fixes, un cron job suffit ; il vous y faut un verrou pour que deux exécutions ne se doublent pas. Avec -n, la seconde exécution s'arrête immédiatement au lieu d'attendre :
flock -n /run/lock/kh-worker.lock /usr/bin/php /var/www/html/worker.php
Pour les frameworks courants, la file d'attente existe déjà, il ne reste qu'à la faire tourner : chez Laravel php artisan queue:work, chez Symfony php bin/console messenger:consume avec le nom de votre transport. Les deux ont leur place dans une unit systemd, pas dans une fenêtre de terminal.
Un cas particulier mérite une mention explicite : par défaut, WordPress lance les tâches planifiées à l'intérieur des requêtes des visiteurs. Un visiteur paie donc de son temps d'attente le fait qu'une vérification de mises à jour tourne en arrière-plan. La ligne define('DISABLE_WP_CRON', true); dans wp-config.php, au-dessus de l'appel à wp-settings.php, désactive ce comportement. Ensuite, appelez vous-même les tâches dues à intervalles réguliers :
wp cron event run --due-now --path=/var/www/html
Ce qui doit impérativement rester synchrone reçoit son propre bloc location avec son propre délai, et en plus une limite pour que ce seul chemin n'occupe pas tous les processus de travail : limit_conn_zone $binary_remote_addr zone=export:10m; dans le bloc http et limit_conn export 1; dans le bloc concerné. Les tentatives supplémentaires reçoivent alors un 503 au lieu d'un serveur saturé.
Erreurs fréquentes et solutions
| Message mot pour mot | Signification et remède |
|---|---|
upstream timed out (110: Connection timed out) while reading response header from upstream | Le cas normal. Le backend calcule trop longtemps. Analysez le journal des temps et identifiez le dévoreur de temps avant de toucher au délai. |
upstream timed out (110: Connection timed out) while connecting to upstream | L'établissement de la connexion est allé jusqu'au bout du délai. Avec un backend distant, c'est presque toujours un filtre de paquets qui jette au lieu de rejeter, avec un PHP-FPM local, une file d'attente pleine sur le socket. |
upstream timed out (110: Connection timed out) while reading upstream | Les en-têtes sont arrivés, puis le corps s'est bloqué plus longtemps que le délai. Typique des exports qui calculent longuement entre deux envois. |
nginx: [emerg] "fastcgi_read_timeout" directive is not allowed here in /etc/nginx/nginx.conf:12 | La directive se trouve en dehors de http, server ou location, le plus souvent par mégarde tout en haut du fichier. |
nginx: [emerg] unknown directive "proxy_read_timout" in /etc/nginx/sites-enabled/example.com:31 | Faute de frappe. nginx vérifie des noms, pas des intentions. Le numéro de ligne figure dans le message. |
PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/export.php on line 42 | Ici, la limite d'exécution de PHP a exceptionnellement fonctionné : le script calculait donc, il n'attendait pas. Cela donne un 500 ou une page vide, pas un 504. |
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction | Une autre transaction retient la ligne, le délai vaut 50 secondes par défaut. La cause est presque toujours une transaction restée ouverte trop longtemps. |
cURL error 28: Operation timed out after 60000 milliseconds | Une interface tierce ne répond pas. Posez dans le code un délai à vous, plus court, pour que votre application garde la main. |
504 dans le navigateur, mais la recherche de upstream timed out reste vide | Le 504 ne vient pas de ce nginx mais d'un service placé en amont, comme un load balancer ou un second proxy. Certains signalent cela avec un code de statut qui leur est propre. |
| Interruption toujours au bout de 60 secondes exactement, alors que le délai est réglé sur 300 | La directive modifiée appartient au mauvais module, ou un autre bloc l'emporte. nginx -T montre ce qui s'applique réellement. |
Comment reconnaître que le problème est réglé
Une commande sans message d'erreur ne prouve rien, un appel réussi isolé non plus. Quatre preuves qui, ensemble, tiennent :
- Code de statut et durée directement sur le serveur, pour qu'aucun cache n'embellisse le résultat. On attend un 200 et une durée nettement inférieure au délai. Un 200 au bout de 58 secondes pour un délai de 60 n'est pas un succès, c'est la prochaine panne dès que la charge monte un peu. Ce qui compte, c'est le pire de vingt appels :
for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code} %{time_total}\n" -H "Host: example.com" http://127.0.0.1/report.php; done | sort -k2 -n | tail -3 - Le journal d'erreurs reste muet. Videz-le avant le test avec
truncate -s 0 /var/log/nginx/error.log, déclenchez les appels, puis regardez de nouveau dedans. Un fichier vide, voilà la vraie preuve. - La file d'attente de PHP-FPM est à zéro. Tant que quelque chose attend sous
listen queuesur la page d'état, la cause n'est que déplacée. - Un redémarrage ne change rien. C'est le point le plus souvent sauté. Les valeurs issues de
SET GLOBAL, les processus lancés à la main et les répertoires créés soi-même sous/runn'y survivent pas. Vérifiezsystemctl is-enabled nginx php8.4-fpmet redémarrez le serveur une fois de façon contrôlée, tant que vous êtes encore devant.
Sur les serveurs root KVM et les serveurs dédiés de KernelHost, ce redémarrage et l'accès console se font depuis l'espace client, même quand le service web ne livre plus rien. Les serveurs se trouvent dans le datacenter maincubes à Francfort-sur-le-Main (certifié TÜV TIER3+), avec un filtrage placé en amont dans le réseau.
Liste de contrôle rapide en cas d'urgence
- Compter les codes de statut dans le journal d'accès. Vus côte à côte, 504, 499 et 502 en disent plus que chacun pris isolément.
grep "upstream timed out" /var/log/nginx/error.log, noter la phase indiquée dans le message.- Activer le journal des temps, comparer
uct,uhteturt. Si la durée colle à la seconde près au délai configuré, c'est le délai qui a frappé. - Identifier le dévoreur de temps : base de données, interface tierce, système de fichiers ou mémoire.
- Vérifier avec
nginx -Tquel délai s'applique dans ce bloc avant d'en modifier un. - Décider seulement ensuite : corriger, déplacer vers une file d'attente ou, en dernier recours, relever le délai pour ce seul chemin.
- Après le correctif : vider le journal, mesurer vingt appels, redémarrer une fois le serveur de façon contrôlée.
Questions fréquentes
Quelle est la différence entre 502 Bad Gateway et 504 Gateway Time-out ?
J'ai réglé fastcgi_read_timeout sur 300 secondes, la page s'interrompt malgré tout au bout de 60 secondes. D'où cela vient-il ?
Un délai plus long résout-il le problème ?
Quelle ligne du journal d'erreurs correspond à un 504 ?
Pourquoi max_execution_time ne met-il pas fin à mon script PHP bloqué ?
Comment savoir si c'est le délai qui a frappé ou le backend qui a renoncé de lui-même ?
Le navigateur affiche un 504, mais la recherche de « upstream timed out » reste vide. D'où vient l'erreur ?
Que faire des tâches qui durent par nature plus longtemps que n'importe quel délai raisonnable ?
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.

