502 Bad Gateway on a WordPress Site — Name the Proxy, Then Fix the Hop Behind It
A 502 is a proxy reporting that the server behind it gave no valid answer. Work out which proxy said it, read the matching error log line, and the fix is usually PHP-FPM: workers exhausted, a crashed process or response headers too large for the buffer.
A 502 Bad Gateway means a proxy got no valid answer from the server behind it, so the first job is naming which proxy said it — the fix is always one hop below. On a typical WordPress stack there are up to three hops: a CDN in front, a web server (nginx or Apache) at the origin, and PHP-FPM running WordPress. WordPress itself never sends a 502; if PHP runs far enough to fail, you get a 500 instead.
Which proxy sent it
| What the page looks like | Who sent the 502 | Look at |
|---|---|---|
| A CDN-branded error page, often with a browser–network–host diagram | The CDN could not get an answer from your origin | Origin web server and firewall |
Plain "502 Bad Gateway" with nginx underneath |
Your nginx could not get an answer from PHP | PHP-FPM |
| Your hosting company's styled error page | The host's front proxy | The host's status page or support |
Confirm with headers, and test the origin without the CDN by pinning the domain to the origin IP:
curl -sI https://example.com/ | head -5
curl -sI --resolve example.com:443:203.0.113.10 https://example.com/
If the origin answers 200 directly while the CDN says 502, the problem is between them: a firewall dropping the CDN's IP ranges, an expired origin certificate, or the CDN set to the wrong port. WordPress behind Cloudflare covers the SSL mode mismatch, the most common version of that. If the origin also fails, read on.
Read the nginx error log
The origin's error.log names the reason in the same second as the request:
| Log line contains | Meaning |
|---|---|
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) |
FPM is not running, or nginx points at the old socket after a PHP upgrade |
(111: Connection refused) |
FPM is down on its TCP port |
(11: Resource temporarily unavailable) |
The socket's queue is full: every worker is busy |
upstream prematurely closed connection while reading response header |
A worker died mid-request |
upstream sent too big header while reading response header from upstream |
Response headers exceed nginx's buffer |
A 504 with upstream timed out is a different problem — the request was slow, not dead — and is handled in maximum execution time exceeded.
Read the PHP-FPM log
The FPM log (often /var/log/php8.3-fpm.log) explains the worker side:
server reached pm.max_children setting (10), consider raising it— all workers were busy (ondemandpools saymax_children;pm = staticnever logs it). Before raising the number, find out why they were busy.child 4121 exited on signal 11 (SIGSEGV)— PHP crashed: a buggy extension, an opcache problem or unbounded recursion.execution timed out (60.004213 sec), terminating—request_terminate_timeoutkilled the request; nginx sees the connection close and returns 502.
If neither log explains it, check the kernel: dmesg -T | grep -i 'killed process' shows the out-of-memory killer removing PHP workers.
Workers exhausted
Workers run out when requests are slow, not only when traffic is high. Ten workers serving 200 ms requests handle 50 requests a second; the same ten serving 5-second requests handle two. Turn on the FPM slow log for a day:
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm-slow.log
Each entry is a PHP stack trace of a request that ran past five seconds, which usually names the plugin making a remote call or a heavy query. Only then size pm.max_children: divide the memory you can give PHP by the average worker size from ps -o rss -C php-fpm8.3. Moving WP-Cron off visitor requests (disable WP-Cron) also stops cron runs from occupying workers during traffic.
The header-size trap
upstream sent too big header is the 502 nobody expects, because the site works for most people. nginx reads PHP's response headers into fastcgi_buffer_size, 4 KB or 8 KB by default depending on the platform. Anonymous pages fit; a logged-in page or a WooCommerce cart sets several cookies, and a plugin may add long Link preload headers. Cross the buffer and only those pages fail — so the bug report says "checkout is broken" or "I can't log in", not "the site is down".
Raise the buffers in the PHP location block and reload nginx:
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k;
Then find out what is setting so many headers; a plugin that writes a new cookie on every request is worth replacing.
Crashes caused by WordPress code
Infinite recursion in a plugin or theme usually ends in "Allowed memory size … exhausted", a 500. Recursion through callbacks, hooks or magic methods could overflow the C stack and kill the process outright, which nginx reports as a 502; PHP 8.3 and later throw "Maximum call stack size of … bytes … reached. Infinite recursion?" instead, which shows as a 500. Either way, isolate code with WP-CLI, which does not go through FPM:
wp plugin deactivate --all
wp theme activate twentytwentyfive
Then reactivate one at a time, as in the 500 error procedure.
Common mistakes
- Purging the CDN cache for an error that comes from the origin.
- Raising
pm.max_childrenpast what RAM can hold. - Restarting FPM, seeing the site return, and never reading why workers ran out.
- Treating a 502 on logged-in pages as a login bug.
Verify
curl -sIagainst the origin and through the CDN both return 200.- The nginx error log has no new
upstreamlines under normal traffic. - The FPM log shows no
max_childrenwarnings for a full day. - For wider outages, the layer-by-layer checks in site not loading all pass.
Frequently asked
- Both come from a proxy. A 504 means the proxy waited for the server behind it and gave up when its timeout ran out. A 502 means it got no usable answer at all: the connection was refused, closed early or the response was malformed. Timeouts point at slow code; 502s point at a process that is down, full or crashing.
- Those responses carry extra cookies and headers. If the combined response headers exceed nginx's fastcgi_buffer_size, nginx logs 'upstream sent too big header' and returns 502 for exactly those pages, while anonymous visitors see a normal site.
- Only if the log says the limit was reached and the server has free memory for more workers. Each PHP worker uses tens of megabytes; adding workers the RAM cannot hold moves the failure to the kernel's out-of-memory killer, which produces more 502s, not fewer.