502 Bad Gateway in WordPress

Een 502 Bad Gateway betekent dat de proxy voor je WordPress-site een ongeldig antwoord (of helemaal geen antwoord) kreeg van de applicatieserver, meestal omdat een PHP-FPM worker midden in het verzoek crashte. Dit artikel legt het mechanisme uit, behandelt de vijf echte oorzaken, helpt je ze in de logs uit elkaar te halen en geeft per oorzaak de fix.

Je WordPress-site toont een lege pagina met de tekst "502 Bad Gateway", of Chrome meldt "This page isn't working: HTTP ERROR 502". De response-inspector laat statuscode 502 zien. De fout komt terug bij bezoekers en bij jou, in elke browser, op mobiel en op desktop. Soms verdwijnt hij na een refresh, soms blijft hij hangen tot je ingrijpt.

Wat een 502 echt betekent

RFC 9110 §15.6.3 definieert 502 als de status die de gateway terugstuurt zodra hij een ongeldig antwoord kreeg van een upstream server. Het sleutelwoord is ongeldig. Niet traag. Niet "uiteindelijk geen antwoord". De upstream stuurde wél iets terug, maar dat was afgekapt, kapot, een lege headerblock, of de verbinding viel midden in een zin dood. In een WordPress-stack is die "gateway" nginx (of Apache met mod_proxy_fcgi) en de upstream is PHP-FPM. De webserver gaf je verzoek door aan PHP, PHP begon met antwoorden en daarna ging er iets mis voordat PHP klaar was. De webserver gaf het op en vertaalde de kapotte upstream naar een 502 voor de bezoeker.

Een 502 is niet hetzelfde als de andere 5xx-fouten in deze categorie, en het verschil is belangrijk voor je diagnose:

  • 502 Bad Gateway: de upstream gaf een ongeldig of afgekapt antwoord (of helemaal niets). Meestal is de worker gecrasht of klopt het socketpad niet. Hierover gaat dit artikel.
  • 504 Gateway Timeout: de upstream was bereikbaar maar duurde te lang om te antwoorden. PHP leeft nog, het is gewoon traag.
  • 503 Service Unavailable: de upstream geeft expliciet aan het verzoek nu niet te kunnen aannemen (overbelasting, onderhoudsmodus, rate limit).
  • 500 Internal Server Error: de applicatie zelf is netjes vastgelopen en gaf een 500 terug aan de proxy.

Kortom: een 502 is een lijk, een 504 is stilte, een 503 is een "ga weg" en een 500 is een bekentenis.

Veelvoorkomende oorzaken, op volgorde van waarschijnlijkheid

1. Een PHP-FPM child crashte midden in het verzoek

Dit is de oorzaak achter de meeste 502's op een drukke site. PHP-FPM draait een pool van workerprocessen; één van die workers pakte je verzoek op, begon WordPress-code uit te voeren en ging dood voordat het antwoord af was. Dat doodgaan is bijna altijd een segfault, een out-of-memory kill door de Linux-kernel of een fatale fout in een extensie die je vanuit PHP niet kunt opvangen. nginx ziet de verbinding sluiten voordat de headers compleet zijn, logt upstream prematurely closed connection while reading response header from upstream en serveert een 502. PHP-FPM heeft emergency_restart_threshold en emergency_restart_interval precies hiervoor: als er te veel children met SIGSEGV of SIGBUS afsterven binnen een korte periode, herstart de master zelf.

Een normale PHP fatal (het soort waar "Er heeft zich een kritieke fout voorgedaan op deze website" uit voortkomt) levert meestal géén 502 op, omdat PHP de fout opvangt en netjes een 500 met body terugstuurt. Een 502 betekent dat PHP het antwoord niet eens af kreeg, en dat is een sterker signaal: native code crashte, de OOM-killer sloeg toe, of de worker werd van buitenaf gedood.

2. Het upstream-socketpad klopt niet

nginx praat met PHP-FPM over een TCP-poort (127.0.0.1:9000) of een Unix socket (unix:/run/php/php8.3-fpm.sock). Als het pad in fastcgi_pass niet matcht met de listen-directive in de FPM-poolconfig, krijgt nginx überhaupt geen verbinding en geeft elk verzoek een 502. Dit komt het vaakst voor vlak na een PHP-versieupgrade: de oude socket stond op /run/php/php8.2-fpm.sock, de nieuwe pool luistert op php8.3-fpm.sock, en nginx wijst nog naar het oude pad.

3. PHP-FPM draait niet

De pool ligt plat. Misschien stopte een configfout het opstarten na een herstart. Misschien blijft de OOM-killer de master zelf doden. Misschien zette een deploy-script hem uit en vergat ie hem terug aan te zetten. nginx probeert verbinding te maken, faalt direct, logt connect() failed (111: Connection refused) while connecting to upstream en geeft een 502 terug. Dit is de makkelijkste oorzaak om te bevestigen en de makkelijkste om te fixen.

4. Een reverse proxy vóór nginx kan de origin niet bereiken

Heb je een extra proxy-laag (een load balancer, een aparte cache-laag, een ingress controller) en is nginx de origin, dan gebruikt die proxy proxy_pass en geldt dezelfde logica één laag hoger. De proxy krijgt geen verbinding met het origin-IP, de origin-poort wordt door een firewall-regel geblokkeerd, of het upstream-block in de proxyconfig wijst naar een verouderd adres. Vanaf de bezoekerskant ziet het er identiek uit aan het lokale FPM-geval, maar het probleem zit op het netwerk tussen de twee lagen.

5. Cloudflare (of een andere edge) bereikt de origin niet

Loopt je verkeer via Cloudflare en kan de edge je origin niet bereiken, dan stuurt Cloudflare zijn eigen 502-pagina terug. Cloudflare's documentatie maakt onderscheid tussen Cloudflare-branded fouten (de edge maakte het antwoord zelf) en unbranded fouten (de origin stuurde een 502 die Cloudflare doorgaf). Veelvoorkomende edge-oorzaken zijn een origin die gzip-content stuurt met een verkeerde Content-Length-header, een origin die maar half HTTP/2 spreekt en een Cloudflare Tunnel waar cloudflared de upstream service niet kan bereiken. Een firewall-regel die Cloudflare's IP-ranges blokkeert hoort hier ook bij.

Stel vast welke oorzaak het is

Deze checks zijn niet-destructief. Doe ze in volgorde, voordat je ook maar één configwaarde aanpast.

Check 1: lees het nginx error log. Dit is veruit de belangrijkste stap. Op een typische Linux-server staat het log op /var/log/nginx/error.log (of in je hostingpaneel onder "error logs"). Voor een 502 vertelt de logregel zelf je welke oorzaak je hebt:

upstream prematurely closed connection while reading response header from upstream

Dat is oorzaak #1: een worker begon te antwoorden en ging dood. De smoking gun.

connect() failed (111: Connection refused) while connecting to upstream

Dat is oorzaak #3: PHP-FPM luistert helemaal niet op die socket. Of hij ligt plat, of zijn listen-pad wijkt af van fastcgi_pass.

connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory)

Dat is oorzaak #2: het socketpad in je nginx-config matcht niet met waar FPM daadwerkelijk luistert. De 2: No such file or directory is het verraadje.

Je weet dat het werkt als: je de exacte logregel kunt citeren voor het mislukte verzoek en je het tijdstip kunt koppelen aan een bezoekersmelding.

Check 2: zoek de kill in dmesg en het FPM error log. Lijkt oorzaak #1 plausibel, kruisverify dan met de kernel- en FPM-logs. Draai dmesg -T | grep -i 'killed process' op de server. Als de OOM-killer toesloeg, zie je een regel met php-fpm en een geheugentotaal. Dat bevestigt dat de worker door de kernel werd gedood vanwege geheugendruk, niet door PHP zelf. Het PHP-FPM error log (/var/log/php8.3-fpm.log op de meeste setups) bevat dan ook WARNING: [pool www] child 12345 exited on signal 11 (SIGSEGV) of iets vergelijkbaars. Staat emergency_restart_threshold aan en herstartte de FPM-master zichzelf, dan zie je failed processes threshold ... reached, initiating reload in datzelfde log.

Je weet dat het werkt als: je het exacte signaal (SIGSEGV, SIGKILL, SIGBUS) kunt benoemen of de OOM-kill kunt bevestigen met een tijdstip dat matcht met de 502.

Check 3: bevestig dat PHP-FPM draait en op het juiste pad luistert. Draai systemctl status php8.3-fpm om te zien of de service leeft. Check vervolgens waar hij op luistert met ss -lnp | grep php-fpm (TCP) of ls -la /run/php/ (Unix socket). Vergelijk dat met de fastcgi_pass-regel in je nginx site-config. Mismatches vangen oorzaken #2 en #3 in één keer.

Je weet dat het werkt als: de FPM-service active (running) toont, zijn listen-pad in ss of als echt bestand in /run/php/ staat, en dat exacte pad matcht met wat nginx gebruikt.

Check 4: omzeil de edge. Gebruik je Cloudflare of een vergelijkbare edge proxy, hit dan direct het origin-IP (met curl --resolve jouwsite.nl:443:1.2.3.4 https://jouwsite.nl/) en kijk wat terugkomt. Geeft de origin een nette 200 en alleen de edge-URL een 502, dan ligt het probleem op de edge-laag: oorzaak #5. Geeft de origin ook een 502, dan zit het in je eigen stack en zit je terug bij oorzaken #1 tot #3.

Je weet dat het werkt als: je met zekerheid kunt zeggen of het directe origin-verzoek slaagt of faalt, en je beide responses in handen hebt.

Oplossingen, per oorzaak

Fix voor oorzaak #1: een worker crashte midden in het verzoek

De worker ging dood om een reden, en die reden staat ofwel in het OOM-log ofwel in het FPM error log. Twee paden:

Sloeg de OOM-killer toe, dan vroeg de worker meer geheugen dan het systeem kon geven. De juiste fix is uitzoeken welk verzoek zoveel allocateert (vaak een grote image-processing call, een ontspoorde plugin of een export), het geheugengebruik terugbrengen of het werk uit de request lifecycle halen. De verkeerde fix is PHP's memory_limit opkrikken tot het systeem nóg agressiever OOM gaat. Heeft de host te weinig RAM voor de huidige pm.max_children, verlaag dan pm.max_children zodat een volle pool wel in het geheugen past. Zie het artikel over PHP workers voor de structurele rekensom.

Was het een segfault, dan zit de fout in native code: PHP zelf, of vaker een PHP-extensie (imagick, gd, redis, xdebug in productie). Het FPM error log noemt de pool en de PID. Vanaf daar wil je de kernel core dump als je core_pattern hebt ingesteld, anders reproduceer je het verzoek met xdebug uit en met één extensie tegelijk uit tot de segfault stopt. Update PHP naar de laatste patch release in jouw branch en update de kapotte extensie. Zet emergency_restart_threshold = 10 en emergency_restart_interval = 1m aan, zodat een ontspoorde crashloop een automatische FPM-master herstart triggert in plaats van een muur van 502's.

Verificatie: dezelfde URL die eerst 502 gaf, geeft nu een 200 in het access log, het FPM error log bevat geen nieuwe exited on signal-regels meer, en dmesg toont geen verdere OOM-kills voor php-fpm.

Fix voor oorzaak #2: verkeerd socketpad

Open je FPM poolconfig (meestal /etc/php/8.3/fpm/pool.d/www.conf) en lees de listen-regel. Open je nginx site-config en lees de fastcgi_pass-regel. Maak ze exact gelijk. Na een PHP-versieupgrade moeten beide bestanden vaak van php8.2-fpm.sock naar php8.3-fpm.sock. Herlaad nginx met nginx -t && systemctl reload nginx en herstart FPM met systemctl restart php8.3-fpm.

Verificatie: curl -I https://jouwsite.nl/ geeft een 200 vanaf de origin, en het nginx error log bevat geen No such file or directory-regels meer voor het socketpad.

Fix voor oorzaak #3: PHP-FPM ligt plat

Start hem met systemctl start php8.3-fpm en check het resultaat met systemctl status php8.3-fpm. Weigert hij te starten, dan benoemt de status-output het kapotte configbestand. De vaakst voorkomende boot-fouten zijn een dubbele listen-directive over pools heen, een user-waarde die niet bestaat of een ongeldige pm-waarde. Fix de config en probeer opnieuw. Start FPM wel maar valt hij meteen weer om, kijk dan in journalctl -u php8.3-fpm -n 100 voor de exit-reden en in dmesg voor een OOM-kill op het master-proces.

Verificatie: systemctl status php8.3-fpm toont active (running), de uptime is ouder dan je laatste 502, en de site geeft een 200 terug.

Fix voor oorzaak #4: een proxy-laag bereikt de origin niet

Check het upstream-block in de proxyconfig. Het IP of de hostnaam moet resolven, de poort moet open zijn vanaf de proxy-host, en elke firewall-regel ertussen moet verkeer toelaten vanaf het bron-IP van de proxy. Draai vanaf de proxy-host curl -v http://<origin-ip>:<port>/ om bereikbaarheid te bevestigen. Hangt de verbinding, dan blokkeert de firewall. Wordt hij geweigerd, dan luistert de origin nginx niet op die interface. Gebruikt het upstream-block een DNS-naam die net van IP wisselde, herstart dan de proxy zodat hij opnieuw resolved, of zet DNS-based health checks aan.

Verificatie: curl -v van de proxy-host naar de origin slaagt, en de bezoekers-URL geeft een 200 via de proxy.

Fix voor oorzaak #5: Cloudflare bereikt de origin niet

Begin bij de Cloudflare-specifieke oorzaken: check de Content-Length-header van je origin tegen de daadwerkelijke gzip-body als je origin-compressie doet, zet HTTP/2 naar de origin uit als je backend dat slecht spreekt, en bevestig dat je origin-firewall Cloudflare's IP-ranges toelaat. Bij Cloudflare Tunnel-setups check je de cloudflared-logs op "unable to reach the origin service" en bevestig je dat het tunneldoel wijst naar een service die ook echt draait. Geven alleen sommige paden 502 via de edge en andere wel een 200, dan is het bijna altijd een per-pad origin-issue (een lang endpoint dat kort een worker laat omvallen, een upload-pad dat over Cloudflare's request body limit gaat) en geen edge-configuratiekwestie.

Verificatie: dezelfde URL die eerst via de edge een 502 gaf, geeft nu via de edge een 200, en het origin access log laat een matchende 200 zien zonder verdere afwijkingen.

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 502 triggert.
  • Het tijdstip van de fout, met tijdzone, en of het reproduceerbaar is of alleen sporadisch.
  • Je hostingtier en stack (shared, VPS, managed, container; nginx of Apache; PHP-versie).
  • De matchende regel uit het nginx error log (oorzaak #1 vs #2 vs #3 vs #4).
  • De output van dmesg -T | grep -i killed rond het tijdstip van het incident.
  • De matchende WARNING: [pool www] child ... exited on signal-regel uit het PHP-FPM error log als die er is.
  • Of pm.max_children keer de gemiddelde worker RSS boven het beschikbare RAM uitkomt.
  • Of de site achter Cloudflare of een andere edge proxy zit, en of de 502 ook optreedt als je die omzeilt.
  • De lijst met actieve plugins op de site, vooral plugins die native extensies laden of plugins die in de afgelopen 48 uur zijn geüpdatet.

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 502 is bijna altijd geheugendruk of een buggy native extensie. Drie dingen houden de fout zeldzaam op een gezonde site:

  • Maak pm.max_children zo dat een volle pool in het RAM past. Pak de gemiddelde worker RSS tijdens piek (niet idle), vermenigvuldig met pm.max_children, tel er 25% buffer bij op, en het resultaat moet binnen het host-RAM passen min de database, het OS en de cache. Past het niet, dan krijg je OOM-kills, en OOM-kills worden 502's. Verlaag pm.max_children tot de som klopt.
  • Zet emergency_restart_threshold aan. Op 10 over 1m. Als één buggy extensie workers in een loop laat crashen, herstart de master zichzelf in plaats van een muur van 502's te serveren. Dit is een vangnet, geen excuus om de onderliggende crash te negeren.
  • Houd PHP en je extensies bij. De meeste native crashes die ik op managed WordPress hosting heb gezien, zijn oude versies van imagick of gd tegen een nieuwere libc, of xdebug die per ongeluk in productie aan blijft staan. Update de patch release van PHP regelmatig, en draai xdebug nooit op een productiepool.

Kan een single request op je site netjes afronden en gaat de FPM-worker daarna heelhuids terug de pool in, dan zou een 502 onder normale omstandigheden onmogelijk moeten zijn. Alles daarboven is het systeem dat je vertelt dat er ergens een worker doodgaat, en de 502 is gewoon de plek waar die dood naar boven komt.

Wil je dat dit niet steeds jouw probleem is?

Als storingen blijven terugkomen, is de 'fix' vaak consistent beheer: updates, backups en monitoring die niet versloffen.

Bekijk WordPress onderhoud

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.