nginx 502 Bad Gateway oplossen: oorzaken en oplossingen
502 Bad Gateway betekent: nginx heeft van de backend geen geldig antwoord gekregen. De vijf meest voorkomende oorzaken, de juiste regel in het foutlog en hoe u aantoont dat de fix werkelijk werkt.
Wat "502 Bad Gateway" werkelijk betekent
Een 502 komt niet van uw applicatie, hij komt van nginx. nginx heeft de aanvraag aangenomen, doorgegeven aan een backend (PHP-FPM, Node, Python, een andere webserver) en daarvandaan geen bruikbaar antwoord gekregen. Daarom staat er op de foutpagina niets nuttigs.
Het onderscheid met de aangrenzende statuscodes bespaart in een noodgeval veel tijd:
- 500 Internal Server Error: de backend heeft geantwoord, maar het antwoord was een fout. De oorzaak ligt in de applicatiecode. Lees het log van de applicatie, niet dat van nginx.
- 502 Bad Gateway: de verbinding met de backend kwam niet tot stand of brak af voordat er een volledig antwoord was.
- 504 Gateway Time-out: de verbinding stond, maar de backend zweeg te lang en nginx verloor zijn geduld.
Dit onderscheid is de belangrijkste hefboom bij time-outs, want dezelfde trage pagina verschijnt de ene keer als 502 en de andere keer als 504, afhankelijk van wie het eerst afbreekt. Verderop meer daarover.
De pakketsituatie op Debian 13, Debian 12, Ubuntu 24.04 en Ubuntu 22.04
nginx gedraagt zich bij 502-fouten op alle vier de systemen hetzelfde, de directives heten identiek. De verschillen zitten bijna volledig aan de PHP-kant, en precies daaruit ontstaan de meeste 502's na een distributiewissel.
| Systeem | nginx | PHP | Servicenaam | 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 |
Vervang in alle volgende commando's het versienummer door dat van uw eigen systeem. Alle voorbeelden gaan uit van een root-shell, zet er anders sudo voor. Welke versie geïnstalleerd is, verraadt een blik op de FPM-binaries, ook als de service helemaal niet start:
ls /usr/sbin/php-fpm*
ls /etc/php/
Eerst het foutlog: de juiste regel vinden
De meest gemaakte fout bij de diagnose is zoeken in het verkeerde log. nginx heeft een globaal foutlog en per virtuele host vaak nog een eigen log. Welk bestand geldt, staat in de configuratie:
grep -Rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-enabled/
Let op de hoofdletter -R. In /etc/nginx/sites-enabled/ staan op Debian en Ubuntu uitsluitend symlinks naar sites-available, en GNU grep volgt met de kleine -r bij het recursief aflopen geen enkele symlink. Met -rn krijgt u daarom alleen de treffers uit nginx.conf, terwijl de vhost-eigen error_log-regel onzichtbaar blijft: precies die regel waar u bij een 502 naar zoekt, want het globale log bevat de FastCGI-fout niet zodra de vhost omleidt. Wie liever bij -r blijft, greppt de bronmappen rechtstreeks:
grep -rn "error_log" /etc/nginx/nginx.conf /etc/nginx/sites-available/ /etc/nginx/conf.d/
Zonder eigen opgave in het serverblok komt alles in /var/log/nginx/error.log terecht. De betrouwbaarste weg naar de juiste regel loopt via een live-opname: houd het log in één terminal open, activeer de aanvraag in een tweede en bekijk de regels die er daarbij nieuw bij komen.
tail -f /var/log/nginx/error.log
Als alternatief filtert u op het tijdstempel. nginx schrijft lokale tijd in het formaat 2026/07/26 09:14:22, niet in UTC. Een vergelijking met een klok in een andere tijdzone gaat geregeld mis.
Een 502-regel is altijd volgens hetzelfde patroon opgebouwd. Voorbeeld:
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"
Vier onderdelen dragen alle informatie:
- De systeemaanroep:
connect(),recv(),send().connect()betekent dat er nooit een verbinding tot stand kwam.recv()betekent dat de verbinding stond en daarna afbrak. - Het foutnummer tussen haakjes, zie de tabel hieronder. Dat is de eigenlijke diagnose.
- De fase:
while connecting to upstreamtegenoverwhile reading response header from upstream. Het eerste is een bereikbaarheidsprobleem, het tweede een runtime- of crashprobleem. - Het veld
upstream:. Daar staat het pad of het adres dat nginx werkelijk heeft gebruikt. Niet wat u in de configuratie vermoedt, maar wat er actief geladen is.
| Melding | Betekenis | Sectie |
| 2: No such file or directory | Socketbestand bestaat niet | Service dood of pad verkeerd |
| 13: Permission denied | Socket bestaat, nginx mag er niet bij | Rechten |
| 111: Connection refused | Niets luistert op adres en poort | Backend niet bereikbaar |
| 110: Connection timed out | Geen antwoord binnen de termijn | Time-out |
| 104: Connection reset by peer | Backendproces is midden in de aanvraag gestorven | Crashes en limieten |
| 11: Resource temporarily unavailable | Wachtrij van de socket vol | Crashes en limieten |
De regel bij 13: Permission denied krijgt bij nginx vaak het niveau [crit] in plaats van [error]. Wie alleen op [error] filtert, ziet hem over het hoofd. Filter daarom liever op de tekst:
grep -n "upstream" /var/log/nginx/error.log
De tweede helft van de waarheid staat in het log van PHP-FPM, standaard onder /var/log/php8.2-fpm.log. Bij crashes en limieten staat daar de reden, terwijl nginx alleen het symptoom ziet.
Oorzaak 1: PHP-FPM draait niet
Klassiek foutnummer 2. Controleer eerst de toestand van de service:
systemctl is-active php8.2-fpm
systemctl status php8.2-fpm --no-pager -l
is-active antwoordt met één woord, dat volstaat voor een script. Komt er inactive of failed uit, haal de reden dan uit de journal, en wel met een tijdvenster in plaats van de laatste tien regels:
journalctl -u php8.2-fpm --since "30 min ago" --no-pager
Heel vaak is de oorzaak een kapotte poolconfiguratie die na een reload is blijven liggen. FPM heeft een eigen syntaxtest die zonder herstart draait:
php-fpm8.2 -t
Typische startfouten, letterlijk, en wat ze betekenen:
ERROR: [pool www] cannot get uid for user 'webuser': de systeemgebruiker die bijuser =is ingevuld, bestaat niet meer, bijvoorbeeld na een migratie.ERROR: unable to bind listening socket for address '/run/php/php8.2-fpm.sock': No such file or directory (2): de map/run/phpontbreekt. Die staat op een tmpfs en wordt bij het starten van de service aangemaakt. Wielistennaar een pad daarbuiten laat wijzen, moet de map zelf laten aanmaken.ERROR: An another FPM instance seems to already listen on ...: een proces uit een mislukte herstart hangt nog aan de socket.
Waaraan u merkt dat het werkelijk opgelost is: niet aan het feit dat systemctl restart zonder uitvoer doorliep. Een FPM-master start ook als er geen enkel werkproces aanvragen kan aannemen. Veelzeggend is dat de socket zichtbaar is in het systeem en dat FPM erop antwoordt.
ss -lx | grep php
Voor de echte antwoordtest activeert u in /etc/php/8.2/fpm/pool.d/www.conf de regel ping.path = /ping, laadt u FPM opnieuw en bevraagt u de socket rechtstreeks, volledig langs nginx heen:
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
Komt er pong terug, dan is de PHP-kant in orde en zit de fout tussen nginx en de socket. Komt er niets terug, dan hoeft u bij nginx helemaal niet verder te zoeken.
Oorzaak 2: verkeerd socketpad
Dit is op Debian en Ubuntu veruit de meest voorkomende oorzaak, omdat de socket de naam van de PHP-versie draagt, de nginx-configuratie die naam hard vastlegt, en een distributie-upgrade beide uit elkaar trekt.
Concreet: een upgrade van Debian 12 naar Debian 13 tilt PHP van 8.2 naar 8.4. De oude socket /run/php/php8.2-fpm.sock verdwijnt, in het vhost-bestand staat hij nog steeds. Het resultaat is een 502 op elke afzonderlijke PHP-pagina, meteen na de herstart. Hetzelfde gebeurt bij Ubuntu 22.04 naar 24.04 (8.1 naar 8.3).
Een tweede valkuil zit in de meegeleverde voorbeeldconfiguratie. In /etc/nginx/sites-available/default staat een uitgecommentarieerd blok waarvan de fastcgi_pass naar een PHP-versie wijst die al jaren niet meer actueel is. Wie die regels alleen maar uit commentaar haalt, heeft de 502 daarmee juist zelf veroorzaakt.
Vergelijk beide kanten. Wat nginx wil gebruiken:
grep -Rn "fastcgi_pass" /etc/nginx/
Wat FPM werkelijk aanbiedt:
grep -n "^listen *=" /etc/php/*/fpm/pool.d/*.conf
De toevoeging *= in het zoekpatroon is opzet, die eist achter listen een willekeurig aantal spaties en dan een isgelijkteken. Een kaal ^listen raakt namelijk ook listen.owner, listen.group en listen.mode, en dan gaat de gezochte regel tussen de treffers verloren. De wildcard /etc/php/*/ is daarentegen juist zo bedoeld, die dekt elke geïnstalleerde PHP-versie af. En wat er op het draaiende systeem bestaat:
ls -l /run/php/
De drie uitvoeren moeten hetzelfde pad tonen. Belangrijk: grep -R over /etc/nginx/ in plaats van alleen over dat ene bestand dat u verdenkt. Via include ingevoegde fragmenten zijn een geliefde schuilplaats, net als oude bestanden in sites-available die via een vergeten symlink in sites-enabled nog actief zijn. Ook hier geldt: alleen de hoofdletter -R volgt die symlinks en laat u daarmee zien welk bestand werkelijk actief is.
ls -l /etc/nginx/sites-enabled/
Voordat u iets wijzigt, maakt u een kopie. Dat kost twee seconden en bespaart u in het ergste geval een herstel uit de back-up:
mkdir -p /root/backups
cp -a /etc/nginx/sites-available/default /root/backups/default.bak
Wie meerdere keren ingrijpt, hangt er beter een tijdstempel aan (default.bak.$(date +%F-%H%M)), want cp -a overschrijft een bestaande .bak zonder enige waarschuwing.
Na de correctie altijd eerst testen, dan laden. reload in plaats van restart, zodat bestaande verbindingen niet wegvallen:
nginx -t
systemctl reload nginx
Let op: nginx -t controleert uitsluitend de syntaxis. Een socketpad dat niet bestaat, geldt als volkomen geldige configuratie. Een groen syntax is ok is daarom geen bewijs dat de 502 weg is.
Oorzaak 3: rechten op de socket
Foutnummer 13. De socket is er, nginx mag hem alleen niet openen. nginx draait op Debian en Ubuntu als gebruiker www-data, en de standaardpool van FPM maakt de socket daar passend bij aan. Dat is zichtbaar in het poolbestand:
grep -n "listen.owner\|listen.group\|listen.mode" /etc/php/*/fpm/pool.d/*.conf
Met listen.owner = www-data, listen.group = www-data en modus 0660 werkt het samenspel vanzelf. Het kan in drie situaties misgaan:
- Een eigen pool per project. Worden
userengroupop een projectgebruiker gezet, dan moetlisten.grouptoch een groep zijn waar nginx lid van is. Gebruikelijk islisten.owner = projectgebruikersamen metlisten.group = www-data. - Socket buiten /run. Niet alleen het socketbestand moet toegankelijk zijn, ook elke map onderweg ernaartoe heeft het uitvoerrecht voor nginx nodig. Een socket in een homedirectory met modus 0700 is voor niemand bereikbaar behalve voor de eigenaar.
- nginx met een gewijzigde
userin/etc/nginx/nginx.conf.
U bevestigt de verdenking zonder te gokken door de toegang precies te proberen als de gebruiker die hem in bedrijf nodig heeft:
id www-data
sudo -u www-data test -w /run/php/php8.2-fpm.sock && echo "Toegang aanwezig" || echo "geen toegang"
Nog veelzeggender is de cgi-fcgi-aanroep uit de vorige paragraaf, eveneens met sudo -u www-data ervoor. Antwoordt FPM als root, maar niet als www-data, dan is de diagnose eenduidig.
Zet de waarden in het poolbestand, niet met chmod op het socketbestand. Een chmod 666 houdt precies stand tot de volgende herstart van FPM, dan maakt FPM de socket opnieuw aan met de geconfigureerde rechten, en is de fout terug, meestal op het ongelegenste moment.
Op Debian en Ubuntu is AppArmor actief. Een meegeleverd profiel voor nginx wordt in de standaardtoestand niet afgedwongen, maar kan door hardeningsjablonen scherp gezet zijn. Blijft een melding met nummer 13 bestaan ondanks correcte rechten, dan loont een blik in aa-status en in journalctl -k | grep DENIED.
Oorzaak 4: time-out bij lange aanvragen
Hier scheidt een nette werkwijze zich van giswerk, want het puur verstrijken van de nginx-termijn levert een 504 op, geen 502. Eindigt een lange aanvraag als 502, dan heeft bijna altijd PHP-FPM het werkproces daarvoor al opgeruimd, en zag nginx alleen nog een afgebroken verbinding. In het log staat dan typisch:
recv() failed (104: Connection reset by peer) while reading response header from upstream
Drie termijnen werken tegelijk, en hun volgorde bepaalt de statuscode:
max_execution_timeinphp.ini, standaard 30 seconden in FPM-bedrijf. Telt alleen de looptijd van het script. Wachttijd in systeemaanroepen, bijvoorbeeld op een hangende databasequery, telt onder Linux niet mee. Daarom redt deze waarde u juist niet bij het probleem waarbij u dat verwacht.request_terminate_timeoutin het poolbestand, in de standaardtoestand uit. Beëindigt het werkproces hard, ongeacht waar het op hangt. Dit is de waarde die 502's produceert.fastcgi_read_timeoutin nginx, standaard 60 seconden. Verstrijkt die, dan volgt een 504.
De bruikbare volgorde loopt oplopend van binnen naar buiten, zodat altijd eerst de laag ingrijpt die nog een begrijpelijke foutmelding kan produceren. Bijvoorbeeld 60, dan 75, dan 90 seconden. Andersom gesorteerd krijgt u 502's in plaats van leesbare PHP-fouten.
grep -rn "request_terminate_timeout" /etc/php/*/fpm/pool.d/*.conf
Wat FPM heeft opgeruimd, staat in het log daarvan in gewone taal:
WARNING: [pool www] child 1234, script '/var/www/html/import.php'
(request: "POST /import.php") execution timed out (76.271849 sec), terminating
Voordat u termijnen verhoogt, laat u zich eerst tonen waar de tijd blijft. FPM heeft daarvoor een eigen log dat bij overschrijding een volledige PHP-aanroepstack wegschrijft. In het poolbestand activeren:
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s
Na een systemctl reload php8.2-fpm staat daar bij de volgende trage aanvraag de functie inclusief regelnummer die blijft hangen. In de praktijk is dat in vier van de vijf gevallen een databasequery zonder index of een aanroep naar een externe programmeerinterface zonder eigen termijn. Termijnen omhoog draaien verlengt dan alleen de tijd tot de fout en blokkeert bovendien werkprocessen.
Oorzaak 5: backend niet bereikbaar
Betreft elke backend die via TCP wordt aangesproken: FPM op poort 9000, een Node-applicatie, een Java-service, een container. De leidende melding is foutnummer 111.
connect() to 127.0.0.1:3000 failed (111: Connection refused) while connecting to upstream
Controleer eerst of er überhaupt iets luistert, en vooral waarop:
ss -ltnp
Dit commando toont uitsluitend TCP-sockets, en daaruit ontstaat een wijdverbreide denkfout: een PHP-FPM-pool in de standaardtoestand luistert op een Unix-socket onder /run/php/ en verschijnt in deze lijst helemaal niet, hoewel hij prima draait. Zichtbaar wordt hij pas zo:
ss -lxn | grep php-fpm
ls -l /run/php/
Voor FPM is ss -ltnp dus alleen veelzeggend als de pool via listen = 127.0.0.1:9000 bewust op TCP is omgezet. Bij Node-, Java- of containerbackends is het daarentegen precies het juiste commando.
Drie struikelblokken die zelden in handleidingen staan:
- localhost lost eerst op naar ::1. Staat er in nginx
proxy_pass http://localhost:3000;terwijl de applicatie alleen op127.0.0.1luistert, dan probeert nginx het IPv6-adres en krijgt het "Connection refused". De service draait, de poort staat open, en toch een 502. Oplossing: schrijf in nginx127.0.0.1voluit, of laat de applicatie aan beide adresfamilies binden. - nginx lost namen eenmalig bij het laden op. Staat er in de
proxy_passeen hostnaam, dan onthoudt nginx het adres. Wisselt de backend van IP-adres, bijvoorbeeld bij een opnieuw gestarte container, dan lopen de aanvragen tot de volgendereloadin het niets. - Firewall op de terugweg. Bij een backend op een andere server verschijnt
113: No route to hostof een time-out in plaats van "Connection refused". Controleer metufw statusen met een directe verbindingstest vanaf de nginx-server.
Wordt er een upstream-blok met meerdere doelen gebruikt, dan komt daar een eigen melding bij:
no live upstreams while connecting to upstream
Dat betekent dat nginx alle doelen na herhaalde mislukte pogingen voor de duur van fail_timeout uit het verkeer heeft genomen. Ook na reparatie van de backend duurt het dan nog tot die termijn verstreken is voordat er weer aanvragen doorkomen. Een systemctl reload nginx zet die toestand meteen terug.
De 502 die bij geen van de vijf oorzaken past
Twee gevallen zien eruit als een storing, maar zijn het niet, en kosten daarom bovengemiddeld veel tijd.
Antwoordheader te groot. De applicatie draait foutloos, alleen leveren losse aanvragen een 502 op:
upstream sent too big header while reading response header from upstream
Aanleiding zijn grote cookies of sessiekenmerken in headerregels, de buffer van nginx is te klein. In het server- of locationblok:
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_busy_buffers_size 64k;
Bij proxy_pass heten de directives proxy_buffer_size en proxy_buffers. Typisch is dat alleen ingelogde gebruikers er last van hebben, terwijl de startpagina foutloos laadt.
Werkprocessen op. Onder belasting verschijnt er in het FPM-log:
WARNING: [pool www] server reached pm.max_children setting (5), consider raising it
Nieuwe aanvragen wachten dan in de wachtrij van de socket. Is ook die vol, dan meldt nginx 11: Resource temporarily unavailable. Voordat u pm.max_children verhoogt, rekent u het even na: beschikbaar werkgeheugen gedeeld door het werkelijke verbruik van één werkproces. Een te hoge waarde ruilt 502's in voor een systeemtoestand waarin het geheugen opraakt, en dat treft dan ook de database.
Crashes. Regels met exited on signal 11 (SIGSEGV) wijzen op een defecte PHP-extensie, vaak na een wissel van PHP-versie met achtergebleven modules uit de vorige versie.
Als de ingreep niets oplevert: de weg terug
Twee regels houden de schade klein. Ten eerste: één wijziging tegelijk, met een kopie van het originele bestand onder /root/backups, nooit in de webmap. Ten tweede: na elke stap tegencontroleren, in plaats van drie dingen tegelijk te wijzigen en achteraf niet te weten wat er geholpen heeft.
Valt nginx na een wijziging helemaal weg, speel dan de kopie terug en laad opnieuw:
cp -a /root/backups/default.bak /etc/nginx/sites-available/default
nginx -t
systemctl reload nginx
Start nginx na een restart niet meer, dan zegt systemctl status nginx zelden genoeg. Veelzeggender:
journalctl -u nginx --since "10 min ago" --no-pager
De meest voorkomende reden voor een mislukte restart bij een syntactisch foutloze configuratie is een bezette poort 80 of 443, meestal een proces uit de vorige run. ss -ltnp | grep ':80' toont de boosdoener.
Waaraan u merkt dat het werkelijk verholpen is
Een commando zonder foutmelding bewijst helemaal niets. systemctl reload blijft ook stil als er inhoudelijk niets veranderd is, en nginx -t controleert alleen de syntaxis. Deze vier bewijzen zijn wel betrouwbaar:
- Vraag de statuscode rechtstreeks op de server op, zodat noch een cache noch een voorgeschakelde dienst het resultaat vervalst:
Verwacht wordt 200, niet 502. Geef bij meerdere virtuele hosts de naam mee:curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1/curl -H "Host: example.com" ... - Het foutlog blijft stil. Kort het vóór de test in met
truncate -s 0 /var/log/nginx/error.log, activeer meerdere aanvragen en kijk er opnieuw in. Een leeg bestand is het eigenlijke bewijs. - FPM antwoordt op de socket langs nginx heen, via
cgi-fcgien alswww-data. Daarmee zijn de rechtenvraag en de padvraag in één stap afgehandeld. - Een herstart verandert niets. Het belangrijkste en meest overgeslagen punt. Veel noodmaatregelen (met de hand gezette rechten, handmatig aangemaakte mappen onder
/run, een gestarte maar niet ingeschakelde service) overleven geen herstart. Controleersystemctl is-enabled php8.2-fpm nginxen herstart de server één keer gecontroleerd, zolang u er nog naar kijkt, in plaats van het aan het volgende onderhoudsvenster over te laten.
Bij de KVM-rootservers en dedicated servers van KernelHost voert u deze herstart inclusief consoletoegang uit in het klantenpaneel, ook als de webdienst op dat moment niet bereikbaar is. De servers staan in het datacenter maincubes in Frankfurt am Main (TÜV TIER3+), aangesloten op een eigen netwerk met DDoS-bescherming. Verder lezen: nginx 504 Gateway Time-out oplossen en PHP-FPM pm.max_children correct berekenen.
Korte checklist voor een noodgeval
tail -f /var/log/nginx/error.log, aanvraag activeren, foutnummer noteren.- Nummer 2 of 111: draait de service, klopt het pad.
ss -lx | grep phptegenovergrep -Rn "fastcgi_pass" /etc/nginx/. - Nummer 13: rechten in het poolbestand, niet via
chmod. - Nummer 104 of 110: FPM-log en
slowloglezen, pas daarna over termijnen praten. - Melding "too big header": buffergroottes verhogen.
- Na de fix: log inkorten, opnieuw testen, server één keer herstarten.
Veelgestelde vragen
Waarom zie ik een 502 en geen 504, terwijl de pagina alleen maar traag is?
Na de upgrade naar Debian 13 geven alle PHP-pagina's een 502. Wat moet ik aanpassen?
Is 'nginx -t' voldoende bewijs dat de fout verholpen is?
Hoe vind ik in het foutlog de regel die bij mijn 502 hoort?
De 502 treedt alleen op bij ingelogde gebruikers, de startpagina laadt normaal. Waar ligt dat aan?
Ik heb de rechten op de socket met chmod gecorrigeerd, na een herstart is de fout terug. Waarom?
2026 KernelHost GmbH. Alle rechten voorbehouden. Deze handleiding is auteursrechtelijk beschermd. Publicatie op andere websites, geheel, gedeeltelijk of in bewerkte vorm, is zonder onze schriftelijke toestemming niet toegestaan. Citeren met bronvermelding en link is uitdrukkelijk welkom.

