Je WordPress-site toont een lege pagina met de tekst "504 Gateway Timeout", of Chrome meldt "This page isn't working: HTTP ERROR 504". De response-header laat statuscode 504 zien. De fout komt terug bij bezoekers en bij jou, in elke browser, op mobiel en op desktop.
Wat een 504 echt betekent
RFC 9110 §15.6.5 definieert 504 als: "the server did not receive a timely response from an upstream server it needed to access in order to complete the request." In een WordPress-stack is die "upstream server" bijna altijd PHP-FPM, het lang draaiende PHP-proces dat WordPress-code uitvoert namens nginx of Apache. De webserver accepteerde de verbinding van je bezoeker, gaf het verzoek door aan PHP, wachtte op antwoord, en een timer liep af voordat PHP terugkwam met HTML. Vervolgens sloot de webserver de verbinding met een 504.
Een 504 is niet hetzelfde als de andere 5xx-fouten in deze categorie, en het verschil is belangrijk voor je diagnose:
- 504 Gateway Timeout: de upstream (PHP-FPM) was bereikbaar maar duurde te lang. Hierover gaat dit artikel.
- 502 Bad Gateway: de upstream gaf een ongeldig of leeg antwoord, vaak omdat de worker midden in het verzoek crashte.
- 503 Service Unavailable: de upstream geeft expliciet aan het verzoek nu niet aan te kunnen (overbelasting, onderhoudsmodus, rate limit).
- 500 Internal Server Error: de applicatie zelf is gecrasht en gaf een 500 terug aan de proxy.
Kortom: een 502 is een lijk, een 503 is een "ga weg", een 500 is een bekentenis en een 504 is stilte.
Veelvoorkomende oorzaken, op volgorde van waarschijnlijkheid
1. Eén PHP-verzoek duurt langer dan fastcgi_read_timeout
Dit is de oorzaak achter de meeste 504's op een gezonde site. nginx wacht op antwoord van PHP-FPM, en hoe lang nginx wacht staat in fastcgi_read_timeout. De nginx-documentatie zet de default op 60 seconden. Als je import-job, REST-endpoint, search-query of admin-ajax-call er meer dan 60 seconden over doet om de eerste byte te produceren, sluit nginx de verbinding met een 504 terwijl PHP op de achtergrond gewoon doorwerkt.
2. Eén PHP-verzoek raakt request_terminate_timeout
PHP-FPM heeft een eigen plafond. request_terminate_timeout is de tijd waarna FPM de worker doodt die het lange verzoek uitvoert. De default is 0 (uit), maar de meeste managed hosts zetten er een echte waarde in (vaak tussen 30 en 300 seconden). Wanneer FPM de worker doodt, ziet nginx de verbinding abrupt sluiten en meldt een 504 aan de bezoeker (in sommige configuraties wordt het een 502). Vanaf de bezoekerskant ziet dit er identiek uit aan oorzaak #1.
3. De PHP-FPM-pool zit helemaal vol en het nieuwe verzoek wacht te lang in de wachtrij
Elke WordPress-site draait op een vast aantal PHP-FPM workers. Het plafond zit in pm.max_children, en zodra elke worker bezig is, gaat het volgende verzoek de wachtrij in. Als die wachtrij lang genoeg wordt, geeft nginx het op en stuurt een 504 terug. Dit is de oorzaak die ik dieper behandel in het artikel over PHP workers. Het komt meestal naar boven bij verkeerspieken, trage database-queries of een plugin die workers gegijzeld houdt.
4. Een reverse proxy of CDN bereikt de origin niet op tijd
Als het verzoek door Cloudflare, een load balancer of een aparte reverse-proxy-laag gaat, wordt de timeout dáár afgedwongen, niet bij nginx. Cloudflare's gratis en Pro-plannen geven de origin bijvoorbeeld 100 seconden voordat ze hun eigen 504-pagina serveren. Een verkeerd ingestelde firewall-regel, een verzadigde origin of een trage TLS-handshake op de origin kan ieder afzonderlijk een 504 op de edge veroorzaken terwijl je origin nginx-logs niets vreemds tonen.
5. De upstream is onbereikbaar, niet traag
Komt minder vaak voor maar wel uitsluiten: de proxy krijgt nog wel TCP-verbinding met de upstream, maar de upstream stopt met data accepteren, of proxy_read_timeout (default 60s) loopt af voordat er een byte binnenkomt. Dit gebeurt tijdens een deploy, een PHP-FPM herstart of een netwerkprobleem tussen twee lagen van je stack.
Stel vast welke oorzaak het is
Doe deze checks voordat je ook maar één instelling wijzigt. Ze zijn niet-destructief en vertellen je precies welke van de vijf oorzaken jij hebt.
Check 1: lees het nginx error log. Dit is veruit de belangrijkste stap. Op een typische Linux-server vind je het log op /var/log/nginx/error.log (of in je hostingpaneel onder "error logs"). Voor een 504 zoek je naar drie patronen:
upstream timed out (110: Connection timed out) while reading response header from upstream
Dat is oorzaak #1 of #2: PHP gaf niet op tijd antwoord. Het stukje while reading response header zegt dat nginx prima verbinding had maar daarna op headers stond te wachten.
upstream timed out (110: Connection timed out) while connecting to upstream
Dat is oorzaak #5: nginx kreeg niet eens verbinding met PHP-FPM. PHP-FPM ligt plat, herstart of staat verkeerd geconfigureerd.
no live upstreams while connecting to upstream
Dat is oorzaak #5 met meerdere upstreams: alle workerpools staan als dood gemarkeerd.
Je weet dat het werkt als: je de exacte logregel ziet voor het mislukte verzoek en je het tijdstip kunt koppelen aan de bezoekersmelding.
Check 2: lees het PHP-FPM slowlog. PHP-FPM kan een backtrace dumpen van elk verzoek dat langer duurt dan een drempelwaarde. Op de meeste setups staat dat op /var/log/php8.3-fpm.slow.log. De directive die het regelt is request_slowlog_timeout. Bestaat het slowlog en zit er een entry rond het tijdstip van de 504, dan heb je goud in handen: je ziet het bestand, de functie en de regel waar het verzoek hing.
Je weet dat het werkt als: de backtrace wijst naar een specifiek pluginbestand, een themafunctie of een core-call. Veelvoorkomende boosdoeners zijn externe API-calls zonder timeout, te grote cron jobs en search-queries op niet-geïndexeerde meta_value-kolommen.
Check 3: tel actieve workers tijdens de incident. Draai tijdens een 504 het volgende op de server: ps -ef | grep 'php-fpm: pool' | grep -v grep | wc -l. Vergelijk dat aantal met de waarde van pm.max_children in je poolconfig (meestal /etc/php/8.3/fpm/pool.d/www.conf). Als de actieve workers gelijk zijn aan max_children, zit je in oorzaak #3. Zit je ruim onder het plafond, dan is de pool niet de bottleneck.
Je weet dat het werkt als: je met een getal kunt zeggen of de pool vol zat of nog ruimte had op het moment van de 504.
Check 4: omzeil de CDN. Gebruik je Cloudflare of een vergelijkbare edge proxy, hit dan direct het origin-IP (met een Host-header override via curl --resolve) en time de respons. Antwoordt de origin snel terwijl de edge een 504 teruggeeft, dan ligt de timeout op de edge, niet op de origin: oorzaak #4.
Je weet dat het werkt als: het directe origin-verzoek binnen 10 seconden slaagt en alleen de edge-URL nog 504 geeft.
Oplossingen, per oorzaak
Fix voor oorzaak #1: een lang PHP-verzoek raakte fastcgi_read_timeout
Twee paden. De juiste fix is het verzoek korter maken. Een WordPress-pagina hoort binnen een seconde te antwoorden, een admintaak ruim binnen 30 seconden, en alles daarboven hoort thuis in WP-Cron, Action Scheduler of een CLI-commando. Pak het trage verzoek uit het slowlog en fix de onderliggende code of query.
De tijdelijke fix is de timeout opkrikken zodat het werk klaar kan worden terwijl jij de echte oorzaak aanpakt. Bewerk het nginx-serverblock (of, op managed hosting, vraag support of zij dit kunnen toepassen en zeg duidelijk welk pad je bedoelt):
location ~ \.php$ {
# ... bestaande fastcgi_pass en params ...
fastcgi_read_timeout 300s; # van default 60s naar 300s
fastcgi_send_timeout 300s;
}
Herlaad nginx met nginx -t && systemctl reload nginx. Pas je nginx aan, dan moet je ook PHP-FPM in stap #2 hieronder verhogen, anders doodt FPM de worker voordat nginx opgeeft en ruil je een 504 in voor een 502.
Verificatie: trigger hetzelfde verzoek dat de 504 gaf. Het zou nu binnen het nieuwe tijdsbestek een 200 in het access log moeten geven. Het slowlog blijft het verzoek wel registreren zodat je niet vergeet om het écht op te lossen.
Fix voor oorzaak #2: de worker raakte request_terminate_timeout
Bewerk je FPM poolconfig (meestal /etc/php/8.3/fpm/pool.d/www.conf) en verhoog de waarde:
request_terminate_timeout = 300s
De PHP max_execution_time ini-waarde staat meestal lager en kun je op dezelfde plek aanpassen:
php_admin_value[max_execution_time] = 300
Herstart PHP-FPM met systemctl restart php8.3-fpm. Dezelfde waarschuwing als hierboven, maar dan andersom: laat FPM 300 seconden toe terwijl nginx nog steeds bij 60 afkapt, dan blijft de worker doorwerken maar krijgt de bezoeker alsnog een 504. Stem de twee getallen op elkaar af.
Verificatie: hetzelfde lange verzoek loopt nu wel tot het einde. Het FPM error log bevat geen regels meer als WARNING: [pool www] child 12345, script '...' executing too slow.
Fix voor oorzaak #3: de FPM-pool zat vol
De juiste fix is zelden "meer workers". Workers toevoegen aan een verzadigde pool verschuift het probleem meestal van een 504 naar een trage site, omdat de workers worden vastgehouden door dezelfde trage query of externe API. Lees het aparte artikel over PHP workers voor de structurele fix. De korte versie:
- Zoek uit wat de workers vasthoudt (slowlog plus een proces-snapshot tijdens het incident).
- Cache die pagina of dat endpoint op de edge zodat het PHP helemaal niet meer raakt.
- Verplaats lange jobs uit de request lifecycle naar Action Scheduler of WP-CLI.
- Pas dáárna verhoog je
pm.max_children, maar alleen als je CPU- en RAM-headroom dat toelaat.
Verificatie: de actieve worker-count tijdens piekmomenten zit duidelijk onder pm.max_children, en dezelfde URL die eerst 504 gaf, geeft nu een 200 binnen 2 seconden.
Fix voor oorzaak #4: de edge proxy timede uit
Zit je op Cloudflare, dan is de origin-respons je enige knop. De 100-seconden edge timeout staat vast op de plannen Free, Pro en Business. Ofwel maak je de origin sneller (de juiste fix), ofwel zet je het lange endpoint op een achtergrondjob. Moet een specifiek pad echt langer dan 100 seconden draaien, dan is de enige toegestane uitweg op Cloudflare om de proxy voor dat pad te omzeilen (zet het DNS-record op "DNS only" in plaats van proxied) of het endpoint naar een aparte hostnaam te verplaatsen met een andere proxy mode.
Verificatie: dezelfde URL die eerder via de edge een 504 gaf, geeft nu via de edge een 200, en het origin access log laat een matchende 200 zien met een responstijd onder 100 seconden.
Fix voor oorzaak #5: de upstream was onbereikbaar
Check of PHP-FPM überhaupt draait met systemctl status php8.3-fpm. Is hij masked, gecrasht of in een herstartloop, kijk dan in het FPM error log en in de OOM-killer log (dmesg | grep -i kill). Een veelvoorkomend patroon op kleine VPS'en is dat FPM is doodgemaakt door geheugendruk, daarna startte, verzoeken accepteerde en weer werd doodgemaakt. De fix dáár is pm.max_children verlágen zodat een volle pool wel in het RAM past, niet verhogen.
Verificatie: systemctl status php8.3-fpm toont active (running) en de uptime is ouder dan je laatste 504.
Wanneer je het moet escaleren
Lukt het je binnen 30 minuten niet om met de stappen hierboven de oorzaak te pinpointen, dan geef je het door aan je host of developer. Houd onderstaande lijst klaar, want dat is precies het eerste wat ze gaan vragen:
- De exacte URL of admin-actie die de 504 triggert.
- Het tijdstip waarop de fout optreedt, met tijdzone, en of het reproduceerbaar is of alleen onder load.
- Je hostingtier en stack (shared, VPS, managed, container; nginx of Apache; PHP-versie).
- De matchende regel uit het nginx error log (oorzaak #1 vs #5).
- De matchende backtrace uit het PHP-FPM slowlog als die er is.
- De waardes van
fastcgi_read_timeout,request_terminate_timeout,max_execution_timeenpm.max_children. - De lijst met actieve plugins op de site, vooral de plugins die in de afgelopen 48 uur zijn geüpdatet.
- Of de site achter Cloudflare of een vergelijkbare edge proxy zit, en of de 504 ook optreedt als je die omzeilt.
Stuur dat in het eerste bericht. Het scheelt een hele heen-en-weer-ronde en routeert het ticket meteen naar de juiste engineer.
Hoe je voorkomt dat het terugkomt
Een hardnekkige 504 is bijna altijd een verzoek dat sowieso nooit lang had moeten duren. Drie dingen houden de fout zeldzaam op een gezonde site:
- Verplaats traag werk uit de request. Imports, exports, hergeneratie van afbeeldingen en elke externe API-call horen thuis in Action Scheduler of WP-CRON, niet in een paginalaad. Als een verzoek meer dan vijf seconden nodig heeft, hoort het in een job queue.
- Cache niet-gecachete endpoints. Een 504 midden in de nacht is vaak een searchbot die een ongecachete zoekresultaatpagina of een
?s=-query raakt. Cache die URL's op de edge of blokkeer ze voor zoekrobots. - Monitor
pm.max_childrenen derequest_slowlog_timeoutoutput, niet alleen CPU. Hosts die "alles groen" tonen op CPU-dashboards kunnen toch 504's serveren, omdat PHP workers staan te wachten op een database, niet op CPU. Worker-saturatie en trage requests zijn de echte signalen.
Antwoordt een single request op je site cold binnen één seconde zonder edge cache, dan zou een 504 onder normale omstandigheden onmogelijk moeten zijn. Alles daarboven is het systeem dat je vertelt dat er iets structureel te traag is, en de timeout is gewoon de plek waar het symptoom naar boven komt.