nginx 504 Gateway Time-out oplossen: oorzaak vinden in plaats van de termijn verhogen

Gepubliceerd op 19 min leestijd

Bij een 504 was de backend gewoon bereikbaar, hij antwoordde alleen te traag. Hoe u met het timinglog bepaalt waar de tijd blijft, welke van de vele termijnen werkelijk grijpt en waarom een hogere termijn de storing meestal alleen verschuift.

Een 504 Gateway Time-out is de geduldigste van alle foutpagina's. nginx heeft de aanvraag aangenomen, doorgegeven aan de backend en daarna gewacht tot een intern ingestelde termijn verstreek. De backend was de hele tijd bereikbaar, hij heeft alleen niet op tijd geantwoord. Precies daarin zit het verschil met de buurcode: bij de 502 Bad Gateway antwoordt de backend verkeerd of helemaal niet, bij de 504 antwoordt hij te traag. Deze handleiding laat zien hoe u meet waar de tijd werkelijk blijft, welke van de vele tijdlimieten daadwerkelijk grijpt, en waarom het ophogen van die limiet bijna altijd het slechtste van de beschikbare antwoorden is.

Alle gegevens hebben betrekking op Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS en Ubuntu 22.04 LTS. De commando's zijn geschreven voor gebruik als root, als gewone gebruiker zet u er sudo voor. In de voorbeelden staat PHP 8.4, vervang het versienummer door dat van uw eigen systeem:

SysteemPHPDienstConfiguratie
Debian 13 (trixie)8.4php8.4-fpm/etc/php/8.4/fpm/
Debian 12 (bookworm)8.2php8.2-fpm/etc/php/8.2/fpm/
Ubuntu 24.04 LTS8.3php8.3-fpm/etc/php/8.3/fpm/
Ubuntu 22.04 LTS8.1php8.1-fpm/etc/php/8.1/fpm/
ls /etc/php/

De nginx-directives heten op alle vier de systemen hetzelfde. De verschillen zitten aan de PHP- en de databasekant en staan op de betreffende plaatsen vermeld.

Wie het opgaf, bepaalt waar u gaat zoeken

Beantwoord eerst één vraag voordat u een bestand opent: welke laag heeft afgebroken? De statuscode verraadt dat al.

CodeWat er gebeurd isWaar u zoekt
500 Internal Server ErrorDe backend heeft geantwoord, het antwoord was een foutLog van de applicatie
502 Bad GatewayVerbinding kwam niet tot stand of brak afDienst, socket, rechten, crashes
504 Gateway Time-outVerbinding stond, het antwoord kwam niet binnen de termijnUitvoeringstijd in de backend
408 Request TimeoutDe bezoeker heeft zijn eigen aanvraag niet op tijd afgemaaktUploads, trage verbindingen
499 (alleen in het log)De bezoeker heeft afgebroken voordat nginx klaar wasTe traag, maar onder de termijn

De regel met 499 is de meest onderschatte. Het is geen fout, maar een vroegtijdig waarschuwingssysteem: de bezoeker heeft het tabblad gesloten omdat de pagina hem te lang duurde. Verdwijnt een 504 nadat u de termijn hebt opgehoogd en verschijnen er in plaats daarvan 499's, dan is er niets opgelost.

awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

Veld 9 geldt voor het standaardformaat combined. Veel 504's bij weinig 499's wijzen op enkele zware pagina's, het omgekeerde beeld op een applicatie die overal traag is.

Voordat u iets wijzigt: de weg terug

De diagnose in de volgende twee secties is puur lezend. Riskant wordt het pas zodra u configuraties aanraakt, en dat kan de dienstverlening op drie manieren stilleggen: een foutieve nginx-configuratie verhindert de start van de webserver, een foutief poolbestand de start van PHP-FPM, en royaal opgehoogde grenswaarden kunnen het werkgeheugen opsouperen. Dat laatste geval is het vervelendste, want dan beëindigt de kernel processen, en dat treft niet per se de veroorzaker. Wordt de SSH-dienst geraakt, dan is de server via het netwerk niet meer te bedienen.

Maak daarom eerst kopieën, onder /root en nooit in de webmap. Het tijdstempel in de naam is belangrijk, omdat u zelden maar één keer ingrijpt en cp -a een bestaande kopie zonder waarschuwing overschrijft:

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)

De weg terug bestaat uit drie regels, en de volgorde is met opzet zo gekozen:

cp -a /root/backups/example.com.2026-09-03-1030 /etc/nginx/sites-available/example.com
nginx -t
systemctl reload nginx

Gebruik reload in plaats van restart zolang dat kan. reload neemt de nieuwe configuratie alleen over als die foutloos is. Een restart beëindigt eerst het lopende proces en laat u bij een fout zonder webserver achter.

Antwoordt de server helemaal niet meer, open dan bij de KVM-rootservers en dedicated servers van KernelHost de VNC-console in het klantenpaneel. Die hangt aan de virtualisatielaag respectievelijk aan de aansluiting zelf en niet aan de netwerkstack van het gastsysteem, en werkt dus ook nog wanneer geen enkele dienst meer bereikbaar is. Log daar vooraf één keer in en vergewis u ervan dat u het root-wachtwoord kent. Controlecommando na elke ingreep:

systemctl is-active nginx php8.4-fpm
free -m

De regel in het foutlog die de zaak beslist

Een 504 laat altijd een spoor na:

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:"

Doorslaggevend is niet het foutnummer 110, dat bij elke 504 hetzelfde luidt, maar de fase die erachter staat:

  • while connecting to upstream: de verbindingsopbouw kwam niet tot stand. Bij een backend op afstand is dat vrijwel altijd een pakketfilter dat pakketten weggooit in plaats van ze te weigeren, want een weigering zou meteen terugkomen en een 502 opleveren.
  • while sending request to upstream: nginx kon de body van de aanvraag niet kwijt, typisch bij grote uploads.
  • while reading response header from upstream: het normale geval. De backend heeft alles ontvangen en rekent, zonder ook maar de eerste headerregel te sturen.
  • while reading upstream: de headers kwamen binnen, daarna stokte de body. Dat ziet u bij streamingantwoorden en exports.
grep -n "upstream timed out" /var/log/nginx/error.log | tail -20

Veel virtuele hosts schrijven naar een eigen foutlog. Waar dat bestand staat en waarom bij het zoeken in sites-enabled de hoofdletter -R nodig is, leest u in het artikel over de 502. Levert het zoeken niets op terwijl de browser wel een 504 toont, dan komt de fout niet van deze nginx.

Waar de tijd blijft: meten in plaats van gokken

nginx kan per aanvraag vastleggen hoe lang de backend erover heeft gedaan. Dat is de belangrijkste stap in de foutzoektocht, omdat hij de vraag "applicatie of verbinding" zonder enige gissing beantwoordt. In het http-blok van /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"';

In het betreffende serverblok voegt u een tweede logregel toe. Het bestaande toegangslog blijft onaangeroerd, nginx schrijft ze allebei:

access_log /var/log/nginx/timing.log kh_timing;
nginx -t
systemctl reload nginx
tail -n 5 /var/log/nginx/timing.log

Na een paar minuten draaien haalt u de traagste aanvragen naar boven:

awk '{ t=$3; sub(/^rt=/, "", t); print t, $0 }' /var/log/nginx/timing.log | sort -rn | head -20
WaarnemingInterpretatieVolgende stap
uct hoog bij een lokale backendDe verbindingsopbouw hapertVolle wachtrij op de socket, trage naamresolutie
uht en urt vrijwel gelijk, allebei hoogDe backend rekent voordat hij de eerste headerregel stuurtApplicatie, database, externe interface
uht klein, urt hoogDe header kwam snel, de body druppelt binnenStreaming, exports, lussen over veel records
urt klein, rt hoogDe backend was snel, de tijd ging daarna verlorenVerbinding van de bezoeker, zeer groot antwoord
Koppelteken in plaats van een getalEr was geen backend bij betrokkenStatisch bestand of afbreken vóór het doorgeven

Twee details besparen veel tijd. Meerdere door komma's gescheiden waarden in één veld betekenen dat de aanvraag naar meer dan één doel is gegaan, er was dus een nieuwe poging. En de scherpste aanwijzing van allemaal: komt de gemeten duur op de seconde overeen met de ingestelde waarde, bijvoorbeeld 60,001 seconden bij een termijn van 60, dan heeft de termijn toegeslagen en heeft de backend het niet uit zichzelf opgegeven. Onronde waarden zoals 43,7 seconden laten zien dat iets anders heeft geremd.

Welke tijdlimiet daadwerkelijk grijpt

nginx kent voor tijdsoverschrijdingen een goede twaalf directives, en het vaakst verspilde uur ontstaat doordat iemand de verkeerde aanpakt. Welke er grijpt, hangt af van de module die het location-blok afhandelt.

DirectiveStandaardGrijpt in blokken metEffect bij verstrijken
proxy_connect_timeout60sproxy_pass504, "while connecting to upstream"
proxy_send_timeout60sproxy_pass504, "while sending request to upstream"
proxy_read_timeout60sproxy_pass504, de doorslaggevende termijn bij proxy-backends
fastcgi_connect_timeout60sfastcgi_pass504, zoals hierboven, voor PHP-FPM
fastcgi_send_timeout60sfastcgi_pass504, zoals hierboven
fastcgi_read_timeout60sfastcgi_pass504, de doorslaggevende termijn bij PHP
send_timeout60soveralgeen 504, de verbinding met de bezoeker wordt gesloten
client_body_timeout60soveral408, geen 504

De termijn geldt tussen twee leesbewerkingen, niet voor het hele antwoord. Een download die tien minuten duurt en daarbij onafgebroken data levert, loopt gewoon door. Een backend die 61 seconden zwijgt, vliegt eruit. Bij haperende exports helpt het daarom vaak meer om de applicatie regelmatig iets te laten uitsturen dan om de termijn te verhogen.

send_timeout doet niets tegen een 504. Die termijn betreft de overdracht naar de bezoeker. Verstrijkt hij, dan krijgt u geen foutpagina maar een afgebroken download. Relevant wordt hij pas wanneer u de buffering met proxy_buffering off; uitschakelt, want dan remt een trage bezoeker helemaal door tot in de backend.

nginx accepteert ook directives die in het betreffende blok geen enkel effect hebben. Een proxy_read_timeout 300s; in een PHP-blok met fastcgi_pass is syntactisch onberispelijk, nginx -t meldt syntax is ok, en de pagina breekt gewoon na 60 seconden af. Andersom net zo. Dat is de meest voorkomende oorzaak wanneer een verhoogde termijn zonder effect blijft. Wat er werkelijk geldt, laat de samengestelde configuratie zien:

nginx -T | grep -E "read_timeout|send_timeout|fastcgi_pass|proxy_pass"

Moet één afzonderlijk pad werkelijk langer draaien, zet de termijn dan precies daar en nergens anders. Het gelijkteken maakt er een exacte overeenkomst van, en die wint het van het algemene blok location ~ \.php$ dat de overige PHP-bestanden afhandelt:

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: waarom max_execution_time hier zelden grijpt

De voor de hand liggende aanname is dat PHP een hangend script uit zichzelf beëindigt. Precies in dit geval klopt dat meestal niet. Eerst de meetval: php -i bevraagt de commandoregelvariant. Die gebruikt een eigen configuratie onder /etc/php/8.4/cli/ en draait sowieso zonder uitvoeringslimiet, en zegt dus niets over FPM. Bepalend zijn deze twee plaatsen:

grep -n "^max_execution_time" /etc/php/8.4/fpm/php.ini
grep -rn "max_execution_time" /etc/php/8.4/fpm/pool.d/

In het poolbestand kan de waarde met php_value[max_execution_time] of php_admin_value[max_execution_time] zijn overschreven. Een waarde die met php_admin_value is gezet, valt vanuit de applicatie via ini_set() niet meer te wijzigen. Als uw framework de uitvoeringstijd zelf ophoogt en dat opeens geen effect meer heeft, ligt het daaraan.

En nu het eigenlijke punt: onder Linux telt wachttijd in systeemaanroepen niet mee. De klok loopt alleen zolang het script zelf rekent. Wacht het op een databasequery, op een externe interface of op het bestandssysteem, dan staat de klok stil. Een script kan daardoor tien minuten aan een hangende query blijven plakken zonder dat de uitvoeringslimiet ooit toeslaat. Beëindigd wordt het dan door de termijn van nginx, en het resultaat is de 504.

Daaruit volgt een ongemakkelijke regel: max_execution_time beschermt tegen oneindige lussen in uw eigen code, niet tegen wachten. De enige harde grens aan PHP-kant is request_terminate_timeout in het poolbestand, die het werkproces opruimt ongeacht waar het op hangt. Die levert echter een 502 op en geen 504. Zet termijnen oplopend van binnen naar buiten, zodat de laag als eerste grijpt die nog een begrijpelijke melding kan produceren. Voor rekenende scripts werkt dat, voor wachtende om de zojuist genoemde reden niet. Daar rest alleen de wachttijd zelf te begrenzen.

Waarom het ophogen van de termijn meestal het verkeerde antwoord is

Reken even mee. Een pool met pm.max_children = 10 heeft tien werkprocessen. Een pagina heeft 90 seconden nodig. Tien gelijktijdige aanroepen bezetten daarmee anderhalve minuut lang elk van die processen. In die tijd krijgt niemand nog een PHP-pagina geserveerd, ook de startpagina niet. Uit een trage subpagina is een storing geworden. Drie mechanismen versterken dat:

  • De bezoeker herlaadt de pagina. Dat levert een extra aanvraag op, maar geeft de oude niet vrij. PHP merkt een afgehaakte bezoeker pas op wanneer het script de volgende keer iets uitstuurt, en een rekenend script stuurt lang niets uit.
  • nginx probeert het uit zichzelf opnieuw. Bij een upstream-blok met meerdere doelen staat proxy_next_upstream in de standaardtoestand op error timeout. Een verlopen aanvraag gaat naar de volgende server, de dure query draait een tweede keer. Schrijvende aanvragen zijn uitgezonderd, lezende niet. Uitschakelen met proxy_next_upstream error;.
  • Monitoring herhaalt eveneens. Een controle-interval van 60 seconden op een pagina die 90 seconden nodig heeft, levert een permanente belasting op die nooit wordt weggewerkt.

Daar komt bij dat niemand vijf minuten op een webpagina wacht. Een termijn van 300 seconden maakt van een probleem van één minuut er een van vijf minuten, en de bezoeker is allang weg terwijl het werkproces doorrekent.

Hoe vol het werkelijk zit, laat de statuspagina van PHP-FPM zien. Zet in het poolbestand pm.status_path = /fpm-status en maak in het serverblok een uitsluitend lokaal bereikbare toegang aan:

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

Let op active processes, listen queue en max active processes. Staat listen queue permanent boven nul, dan is het aantal werkprocessen niet toereikend voor de huidige uitvoeringstijd van de pagina's. Dat is het moment waarop u de uitvoeringstijd moet verlagen en niet de termijn moet verhogen.

De drie gebruikelijke tijdvreters

Database

In de meeste gevallen zit de tijd hier. Kijk eerst wat er op dit moment draait. Op alle vier de systemen loopt de root-toegang in de standaardtoestand via de Unix-socket, het commando heeft dus geen wachtwoord nodig:

mysql -e "SHOW FULL PROCESSLIST;"

Interessant zijn de kolommen Time en State. Waarden als Sending data of Waiting for table metadata lock bij tweecijferige secondewaarden zijn precies uw geval. Systematisch zoekt u met het log van trage query's, dat u tijdens bedrijf kunt inschakelen:

mysql -e "SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 1;"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log_file';"

De bestandsnaam leest u af uit de tweede uitvoer, want die verschilt: Debian zet doorgaans in op MariaDB en schrijft naar /var/log/mysql/mariadb-slow.log, onder Ubuntu heet het bestand anders, afhankelijk van de geïnstalleerde server. Evalueer na enkele minuten en schakel het weer uit, want dit log kost schrijfbelasting:

mysqldumpslow -s t /var/log/mysql/mariadb-slow.log | head -30
mysql -e "SET GLOBAL slow_query_log = 0;"

De schakelaar -s t sorteert op totale tijd. De duurste query controleert u met EXPLAIN, meestal ontbreekt er een index op precies die kolom waarop wordt gefilterd of gesorteerd. SET GLOBAL werkt meteen, maar overleeft geen herstart van de database. Ook aan de databasekant bestaat een bovengrens per query: MariaDB kent max_statement_time in seconden, MySQL max_execution_time in milliseconden, dat laatste alleen voor lezende query's. Daarmee krijgt uw applicatie een nette fout in plaats van een bezet werkproces.

Externe interfaces

Bevraagt uw pagina bij elke aanroep een betaaldienstverlener of een licentieserver, dan wordt hun storing uw 504. Meet die aanroep apart, vanaf de server:

curl -o /dev/null -s -w "dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total}\n" https://api.example.com/status

Valt dns al op, dan ligt het aan de naamresolutie en niet aan de tegenpartij, tegenproef met time getent hosts api.example.com. In de code heeft elke externe aanroep een eigen, krappe termijn nodig: bij cURL zijn dat CURLOPT_CONNECTTIMEOUT en CURLOPT_TIMEOUT. Wie in plaats daarvan file_get_contents() op een adres loslaat, komt uit bij default_socket_timeout uit de php.ini, en daar staat in de standaardtoestand 60 seconden:

grep -n "default_socket_timeout" /etc/php/8.4/fpm/php.ini

De vuistregel: de som van alle externe termijnen binnen één aanvraag moet onder fastcgi_read_timeout blijven. Anders kapt nginx af voordat uw code een begrijpelijke foutpagina kan tonen.

Bestandssysteem en werkgeheugen

Een volle schijf maakt schrijfbewerkingen traag of onmogelijk, en dat treft sessiebestanden, caches en logbestanden tegelijk. Controleer allebei, de vrije ruimte en de inodes:

df -h
df -i

Het tweede commando wordt vaak vergeten. Een partitie kan op 40 procent bezetting staan en toch geen bestand meer opnemen wanneer de inodes op zijn. De tweede kandidaat is geheugengebrek: gaat het systeem swappen, dan wordt elke aanvraag stroperig zonder dat een bepaalde query de schuldige is.

vmstat 1 5

Staan de kolommen si en so permanent boven nul, dan wisselt de kernel onafgebroken geheugen uit en in, en hebt u een geheugenprobleem en geen tijdprobleem. Hoe u daar netjes mee omgaat, staat in Swap inrichten en crashes door geheugengebrek voorkomen. De derde kandidaat zijn netwerkschijven: een hangend NFS-aankoppelpunt blokkeert elk proces dat het aanraakt, en daar hoort ook uw diagnosecommando bij. Zet er daarom een termijn voor:

timeout 5 df -h

Langdurige taken horen niet in de aanvraag thuis

Sommige taken duren nu eenmaal lang: een jaarverslag, een import met 200.000 regels, een beeldconversie. De fout zit niet in het feit dat ze lang duren, maar in het feit dat een webserver erop staat te wachten. De nette vorm bestaat uit drie delen:

  1. De aanvraag maakt een taak aan, in een tabel of in een wachtrij, en antwoordt meteen. De passende statuscode is 202, samen met een adres waarop de status is op te vragen.
  2. Een werkproces buiten nginx haalt de taken op en handelt ze af. Het heeft geen termijn in de nek, omdat niemand erop wacht.
  3. De interface vraagt de status op. Die opvraging is altijd snel, hoe lang de taak zelf ook duurt.

Dat werkproces draait u als een eigen dienst, zodat het na een crash en na een herstart uit zichzelf terugkomt. Hoe zo'n unit eruitziet, laat een systemd-service aanmaken zien. Voor taken op vaste tijdstippen volstaat een cronjob, en daar hebt u een vergrendeling nodig zodat twee runs elkaar niet inhalen. Met -n breekt de tweede run meteen af in plaats van te wachten:

flock -n /run/lock/kh-worker.lock /usr/bin/php /var/www/html/worker.php

Voor de gangbare frameworks is de wachtrij kant-en-klaar aanwezig, ze moet alleen draaiend gehouden worden: bij Laravel php artisan queue:work, bij Symfony php bin/console messenger:consume met de naam van uw transport. Beide horen in een systemd-unit thuis, niet in een terminalvenster.

Eén bijzonder geval verdient uitdrukkelijk vermelding: WordPress start geplande taken in de standaardtoestand binnen bezoekersaanvragen. Een bezoeker betaalt dus met zijn wachttijd voor een updatecontrole die op de achtergrond draait. De regel define('DISABLE_WP_CRON', true); in wp-config.php, boven de verwijzing naar wp-settings.php, schakelt dat uit. Daarna roept u de openstaande taken zelf regelmatig aan:

wp cron event run --due-now --path=/var/www/html

Wat per se synchroon moet blijven, krijgt een eigen location-blok met een eigen termijn en daarnaast een begrenzing, zodat dat ene pad niet alle werkprocessen bezet: limit_conn_zone $binary_remote_addr zone=export:10m; in het http-blok en limit_conn export 1; in het betreffende blok. Verdere pogingen krijgen dan een 503 in plaats van een bezette server.

Veelvoorkomende fouten en oplossingen

Melding letterlijkBetekenis en oplossing
upstream timed out (110: Connection timed out) while reading response header from upstreamHet normale geval. De backend rekent te lang. Evalueer het timinglog en bepaal de tijdvreter voordat u aan de termijn draait.
upstream timed out (110: Connection timed out) while connecting to upstreamDe verbindingsopbouw liep in de termijn. Bij een backend op afstand vrijwel altijd een pakketfilter dat weggooit in plaats van weigert, bij lokale PHP-FPM een volle wachtrij op de socket.
upstream timed out (110: Connection timed out) while reading upstreamDe headers kwamen binnen, daarna stokte de body langer dan de termijn. Typisch voor exports die tussendoor lang rekenen.
nginx: [emerg] "fastcgi_read_timeout" directive is not allowed here in /etc/nginx/nginx.conf:12De directive staat buiten http, server of location, meestal per ongeluk helemaal bovenaan in het bestand.
nginx: [emerg] unknown directive "proxy_read_timout" in /etc/nginx/sites-enabled/example.com:31Typefout. nginx controleert namen, geen bedoelingen. Het regelnummer staat in de melding.
PHP Fatal error: Maximum execution time of 30 seconds exceeded in /var/www/html/export.php on line 42Hier heeft de PHP-uitvoeringslimiet bij uitzondering wel gegrepen, het script heeft dus gerekend en niet gewacht. Levert een 500 of een lege pagina op, geen 504.
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transactionEen andere transactie houdt de rij vast, de termijn ligt in de standaardtoestand op 50 seconden. De oorzaak is vrijwel altijd een transactie die te lang openstaat.
cURL error 28: Operation timed out after 60000 millisecondsEen externe interface antwoordt niet. Zet in de code een eigen, kortere termijn zodat uw applicatie de controle houdt.
504 in de browser, maar het zoeken naar upstream timed out levert niets opDe 504 komt niet van deze nginx, maar van een voorgeschakelde dienst zoals een loadbalancer of een tweede proxy. Sommige daarvan melden daarvoor een eigen statuscode.
Nog steeds afbreken na exact 60 seconden, hoewel de termijn op 300 staatDe gewijzigde directive hoort bij de verkeerde module, of een ander blok wint. nginx -T laat zien wat er werkelijk geldt.

Waaraan u merkt dat het werkelijk opgelost is

Een commando zonder foutmelding bewijst niets, en een enkele geslaagde aanroep evenmin. Vier bewijzen die samen wel houvast geven:

  1. Statuscode en duur rechtstreeks op de server, zodat geen enkele cache het resultaat mooier maakt. Verwacht wordt 200 en een duur die duidelijk onder de termijn ligt. Een 200 na 58 seconden bij een termijn van 60 is geen succes, maar de volgende storing zodra de belasting iets toeneemt. Daarbij telt de slechtste van twintig aanroepen:
    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
  2. Het foutlog blijft stil. Kort het voor de test in met truncate -s 0 /var/log/nginx/error.log, activeer de aanroepen en kijk er opnieuw in. Een leeg bestand is het eigenlijke bewijs.
  3. De wachtrij van PHP-FPM staat op nul. Zolang er op de statuspagina onder listen queue nog iets wacht, is de oorzaak alleen maar verschoven.
  4. Een herstart verandert niets. Het vaakst overgeslagen punt. Waarden uit SET GLOBAL, met de hand gestarte processen en zelf aangemaakte mappen onder /run overleven die niet. Controleer systemctl is-enabled nginx php8.4-fpm en herstart de server één keer gecontroleerd, zolang u er nog naar kijkt.

Deze herstart inclusief consoletoegang voert u bij de KVM-rootservers en dedicated servers van KernelHost uit in het klantenpaneel, ook wanneer de webdienst op dat moment niets meer uitlevert. De servers staan in het datacenter maincubes in Frankfurt am Main (TÜV TIER3+ gecertificeerd), met filtering in het netwerk ervoor.

Korte checklist voor noodgevallen

  1. Tel de statuscodes in het toegangslog. 504, 499 en 502 naast elkaar zeggen meer dan elk van die codes afzonderlijk.
  2. grep "upstream timed out" /var/log/nginx/error.log, en noteer de fase in de melding.
  3. Activeer het timinglog en vergelijk uct, uht en urt. Past de duur op de seconde bij de ingestelde termijn, dan heeft de termijn toegeslagen.
  4. Bepaal de tijdvreter: database, externe interface, bestandssysteem of geheugen.
  5. Controleer met nginx -T welke termijn in dit blok geldt, voordat u er een wijzigt.
  6. Beslis pas daarna: verhelpen, naar een wachtrij verplaatsen of, als laatste keuze, de termijn voor precies dat ene pad ophogen.
  7. Na de fix: log inkorten, twintig aanroepen meten, server één keer gecontroleerd herstarten.

Veelgestelde vragen

Wat is het verschil tussen 502 Bad Gateway en 504 Gateway Time-out?
Bij een 502 komt de verbinding met de backend niet tot stand of breekt ze af, de backend antwoordt dus verkeerd of helemaal niet. Bij een 504 stond de verbinding wel, alleen kwam het antwoord niet binnen de termijn: de backend was de hele tijd bereikbaar en heeft te traag geantwoord. Een 500 betekent daarentegen dat de backend heeft geantwoord en dat het antwoord een fout was. Daaruit volgt waar u moet zoeken: bij een 502 controleert u dienst, socket en rechten, bij een 504 de uitvoeringstijd in de backend.
Ik heb fastcgi_read_timeout op 300 seconden gezet, toch breekt de pagina na 60 seconden af. Waar ligt dat aan?
Vrijwel altijd daaraan dat de gewijzigde directive bij de verkeerde module hoort of dat een ander blok wint. nginx accepteert ook directives die in het betreffende blok geen enkel effect hebben: een proxy_read_timeout 300s; in een PHP-blok met fastcgi_pass is syntactisch onberispelijk, nginx -t meldt "syntax is ok", en de pagina breekt gewoon na 60 seconden af. Andersom net zo. Wat er werkelijk geldt, laat de samengestelde configuratie zien met nginx -T en een zoekactie naar read_timeout, send_timeout, fastcgi_pass en proxy_pass. Moet één afzonderlijk pad langer draaien, zet de termijn dan in een eigen location-blok met gelijkteken, want een exacte overeenkomst wint het van het algemene PHP-blok.
Lost een hogere termijn het probleem op?
Meestal verschuift ze het alleen. Een pool met pm.max_children = 10 heeft tien werkprocessen. Heeft een pagina 90 seconden nodig, dan bezetten tien gelijktijdige aanroepen anderhalve minuut lang elk van die processen, en in die tijd krijgt niemand nog een PHP-pagina geserveerd, ook de startpagina niet. Uit een trage subpagina is daarmee een storing geworden. Bovendien wacht niemand vijf minuten op een webpagina. Verdwijnt de 504 nadat u de termijn hebt opgehoogd en verschijnen er in plaats daarvan 499's in het toegangslog, dan heeft de bezoeker zelf afgebroken en is er niets opgelost.
Welke regel in het foutlog hoort bij een 504?
Een 504 laat altijd een regel na van de vorm "upstream timed out (110: Connection timed out) while reading response header from upstream", te vinden met grep -n "upstream timed out" /var/log/nginx/error.log. Doorslaggevend is niet het foutnummer 110, dat bij elke 504 hetzelfde luidt, maar de fase die erachter staat: "while connecting to upstream" staat voor een verbindingsopbouw die niet tot stand kwam, "while sending request to upstream" ervoor dat nginx de body van de aanvraag niet kwijt raakte, "while reading response header from upstream" voor het normale geval waarin de backend rekent zonder ook maar de eerste headerregel te sturen, en "while reading upstream" ervoor dat na de headers de body stokte.
Waarom beëindigt max_execution_time mijn hangende PHP-script niet?
Omdat onder Linux wachttijd in systeemaanroepen niet meetelt. De klok loopt alleen zolang het script zelf rekent. Wacht het op een databasequery, op een externe interface of op het bestandssysteem, dan staat de klok stil, en het script kan tien minuten aan een hangende query blijven plakken zonder dat de uitvoeringslimiet ooit toeslaat. Beëindigd wordt het dan door de termijn van nginx, en het resultaat is de 504. Let daarnaast op de meetval: php -i bevraagt de commandoregelvariant, die een eigen configuratie gebruikt en sowieso zonder uitvoeringslimiet draait. Bepalend zijn de php.ini van FPM en het poolbestand. De enige harde grens aan PHP-kant is request_terminate_timeout, maar die levert een 502 op en geen 504.
Hoe zie ik of de termijn heeft toegeslagen of dat de backend het uit zichzelf heeft opgegeven?
Aan de gemeten duur. Leg via een eigen log_format de waarden $request_time, $upstream_connect_time, $upstream_header_time en $upstream_response_time vast in een extra bestand. Komt de duur op de seconde overeen met de ingestelde waarde, bijvoorbeeld 60,001 seconden bij een termijn van 60, dan heeft de termijn toegeslagen en heeft de backend het niet uit zichzelf opgegeven. Onronde waarden zoals 43,7 seconden laten zien dat iets anders heeft geremd. Meerdere door komma's gescheiden waarden in één veld duiden op een nieuwe poging naar een tweede doel, een koppelteken in plaats van een getal betekent dat er helemaal geen backend bij betrokken was.
De browser toont 504, maar het zoeken naar "upstream timed out" levert niets op. Waar komt de fout vandaan?
Dan komt de 504 niet van deze nginx, maar van een voorgeschakelde dienst zoals een loadbalancer of een tweede proxy, en sommige daarvan melden daarvoor een eigen statuscode. Controleer vooraf echter nog één ding: veel virtuele hosts schrijven naar een eigen foutlog, dus mogelijk zoekt u gewoon in het verkeerde bestand.
Wat doe ik met taken die van nature langer duren dan elke zinvolle termijn?
Haal ze uit de aanvraag. De aanvraag maakt een taak aan en antwoordt meteen, passend met statuscode 202 en een adres waarop de status is op te vragen. Een werkproces buiten nginx handelt de taak af, zonder termijn in de nek, en de interface vraagt alleen nog de status op. Draai dat werkproces als een eigen dienst, zodat het na een crash en na een herstart uit zichzelf terugkomt, en beveilig terugkerende runs met flock -n zodat twee runs elkaar niet inhalen. Wat per se synchroon moet blijven, krijgt een eigen location-blok met een eigen termijn en daarnaast een begrenzing via limit_conn, zodat dat ene pad niet alle werkprocessen bezet.

nginx PHP-FPM 504 Gateway Time-out Timeout Debian Ubuntu Probleemoplossing Linux-beheer