Fixing 502 Bad Gateway Errors
A 502 Bad Gateway comes from a middleman. Your web server, CDN or load balancer passed the request on to the program that actually builds the page, such as PHP-FPM or a Node app, and got back nothing usable.
The visitor sees a bare error page, often branded by nginx or Cloudflare, while the real fault sits one layer behind it. This guide shows you how to work out which layer produced the 502, read the one log line that names the cause, fix the common ones, and set the application to restart itself so a crash does not become an outage.
What sits behind a 502
Almost every modern site has at least two programs involved in answering a request. A front server receives the connection and handles TLS, static files and caching. It then hands dynamic requests to a back-end program, called the upstream, which runs your PHP, Node, Python or Ruby code. A 502 means the front server tried to talk to the upstream and the conversation failed: it could not connect, the upstream hung up part way through, or it sent back something that was not a valid HTTP response.
The important point is that the program showing the error page is not the program at fault. nginx did its job by reporting the failure. Restarting nginx rarely helps; the fix is almost always in the upstream or in how the two are wired together.
Common chains and where a 502 can start
| Set-up | Front server | Upstream that usually failed |
|---|---|---|
| WordPress or other PHP on a VPS | nginx | PHP-FPM pool |
| Plesk or cPanel hosting | nginx in front of Apache | Apache, or PHP-FPM behind it |
| Node, Next.js or Express app | nginx or a platform router | The Node process |
| Site behind Cloudflare or another CDN | The CDN edge | Your origin server as a whole |
| Cloud load balancer in front of several servers | The load balancer | One or more unhealthy instances |
With a CDN in front there can be two gateways in a row, and either one can produce the 502. Step 1 below sorts that out before you start digging.
The error log line that names the cause
nginx writes one line to its error log for every 502, and the wording tells you the cause. On Debian
and Ubuntu the file is usually /var/log/nginx/error.log; hosting panels often keep a separate
error log per domain. Search it for the minute when the audit reported the failure.
sudo grep -i "upstream" /var/log/nginx/error.log | tail -20
| Message fragment | What it means | Where to look next |
|---|---|---|
connect() to unix:/run/php/... failed (2: No such file or directory) | The PHP-FPM socket file does not exist, so the pool is stopped or uses another path | Is PHP-FPM running? Which socket does the pool create? |
(111: Connection refused) while connecting to upstream | Nothing is listening on that port or socket | The app has crashed, or is on a different port |
(13: Permission denied) while connecting to upstream | nginx is not allowed to open the socket | Socket owner and mode in the pool config; SELinux on some distributions |
(11: Resource temporarily unavailable) | The upstream's queue of waiting connections is full | Too few workers for the traffic; see the capacity section |
upstream prematurely closed connection while reading response header | The upstream accepted the request then died or hung up before answering | Application and PHP-FPM logs for a crash on that request |
upstream sent too big header while reading response header | The response headers were larger than nginx's buffer | Large cookies or many Set-Cookie headers;
step 6 |
recv() failed (104: Connection reset by peer) | The upstream reset the connection | A worker being killed, or an idle keep-alive connection closed |
Apache used as a reverse proxy logs similar failures through mod_proxy, with messages
such as "AH01102: error reading status line from remote server" or "AH00957: attempt to connect to ...
failed".
The fix, step by step
Step 1
Find out which layer returned the 502
Look at the error page and the response headers. A plain page saying nginx came
from your own server. A Cloudflare-branded page came from the CDN edge, and a response with a cf-ray
header but an origin-styled page came from your server through Cloudflare. Then request the origin directly,
bypassing the CDN, with curl -I --resolve www.example.com:443:203.0.113.10 https://www.example.com/
using your server's real IP. If the origin answers 200, the fault is between the CDN and your server,
not in your application.
Step 2
Read the proxy error log at the failure time
Take the time of a failure from the HTTP Error Audit results and open the front server's error log for that minute. Match the message against the table above: a missing socket, a refused connection, a permission error, a full queue, an upstream that closed early, or headers that were too large. Each points to a different fix, so write down the exact wording. If the log is empty for that time, you are probably reading the wrong file, or the 502 came from a CDN or load balancer rather than your own server.
Step 3
Check the upstream process is running and listening
Run systemctl status php8.3-fpm, adjusting the version to yours, or
pm2 list for a Node app, and look for a stopped or repeatedly restarting service. Then confirm
something is actually listening with ss -ltnpx, which lists TCP ports and Unix sockets with
the owning process. If the service is down, start it and reload the page. If that clears the 502, you
have restored service, but you still need step 5 to find out why it stopped.
Step 4
Match the socket or port on both sides
Compare the address in the proxy config with the address the upstream opens. For PHP-FPM,
the fastcgi_pass path in nginx must match the listen line in the pool file,
which changes whenever PHP is upgraded to a new version. For Node, the proxy_pass port must
match the port the app binds to. Use 127.0.0.1 rather than localhost on both
sides, because localhost can resolve to the IPv6 address ::1 while the app
listens only on IPv4.
Step 5
Find out why the process died
Read
the upstream's own log, not the proxy's. Check the PHP-FPM log for children exiting on a signal or the
pool hitting pm.max_children, and run journalctl -u php8.3-fpm --since "1 hour ago"
or pm2 logs --lines 200 for Node. Run dmesg -T | grep -i "killed process" to
see whether the kernel's out-of-memory killer ended it. A memory kill needs less memory per worker, fewer
workers or more RAM; a crash on one URL needs a code fix.
Step 6
Raise buffers for oversized response headers
If the log says the upstream sent too big a header, nginx's buffer for the first part
of the response is smaller than the headers your application sends. This happens with large or numerous
cookies, long Link headers and some debugging plugins. For PHP set fastcgi_buffer_size 32k;
and fastcgi_buffers 16 16k;, and for proxied apps set proxy_buffer_size and
proxy_buffers to similar values. Test the config with nginx -t and reload.
Also ask why the headers are so large.
Step 7
Set the application to restart itself
Make a crash cost seconds rather than hours. For a systemd service, add Restart=on-failure
and RestartSec=2 in an override with systemctl edit. For Node under PM2, keep
automatic restart on and add max_memory_restart so a leaking process is recycled before
the kernel kills it. For PHP-FPM, set pm.max_requests so each child is replaced after a
number of requests. Then run the HTTP Error Audit again at a busy time to confirm the
502s have stopped.
When 502s appear only under load
A site that works for you but returns 502s during a crawl, a sale or a traffic spike is usually running out of workers or memory. Each PHP-FPM child or Node process handles a limited number of requests at once. When all are busy, new connections queue; when the queue is full, the proxy cannot connect and reports a 502.
For PHP-FPM, the PHP-FPM log will contain a warning that the pool reached its pm.max_children
setting. Raising the limit helps only if the server has memory to spare. A rough way to size it:
- Find the typical memory per PHP-FPM child with
ps -ylC php-fpm8.3 --sort:rss(the process name varies by distribution) and read the RSS column, which is in kilobytes. - Decide how much RAM PHP may use after leaving room for the database, the web server and the operating system.
- Divide that figure by the memory per child. The result is a sensible ceiling for
pm.max_children.
Setting the limit higher than memory allows turns queueing into out-of-memory kills, which produce more 502s, not fewer. If the numbers do not work, the answer is caching whole pages so fewer requests reach PHP at all, or more RAM.
; /etc/php/8.3/fpm/pool.d/www.conf (path varies)
pm = dynamic
pm.max_children = 12
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
For Node, a single process uses one CPU core for JavaScript. Running one process per core with PM2 cluster mode or several containers behind the proxy spreads the load, and lets one process restart while others carry on serving.
502s during deploys and restarts
If 502s cluster around the time you release code, the application is being stopped before the new one is ready. For a few seconds nothing is listening, and every request in that gap fails.
- PHP-FPM: use
systemctl reloadrather thanrestartwhere you only changed configuration. A reload lets existing children finish their requests. - Node under PM2: in cluster mode,
pm2 reload app-namereplaces processes one at a time so at least one is always serving.pm2 restartstops them all together. - Containers and platforms: give the new container a health check and do not send traffic to it until the check passes. Keep the old one running until then.
- Build steps: run dependency installs and builds before switching traffic, not while the live process is stopped.
Intermittent 502s from reused connections
Some 502s come and go with no crash in any log. A common cause is connection reuse between a load balancer or proxy and the app. To save time, the proxy keeps idle connections open and reuses them. If the application closes an idle connection at the very moment the proxy sends a new request down it, the request fails and the proxy reports a 502, often logged as a connection reset.
The rule is that the upstream should keep idle connections open for longer than the proxy in front
of it does. Node's HTTP server has a default keepAliveTimeout of a few seconds, which is
shorter than the idle timeout of many cloud load balancers. Raising it above the load balancer's value,
and setting headersTimeout a little higher still, removes this class of 502:
const server = app.listen(3000, '127.0.0.1');
// Longer than the load balancer's idle timeout (check your provider's setting)
server.keepAliveTimeout = 65000;
server.headersTimeout = 66000;
More on Node process management, PM2 and platform limits is in the Node.js and Next.js HTTP errors guide.
When the CDN reports the 502
If requests straight to the origin succeed but visitors get a 502 through the CDN, the edge cannot reach or understand your server. Check that the CDN's IP ranges are allowed through the server firewall and any rate limiting, that the origin address in the CDN settings is current after a server move, and that the origin can complete a TLS connection using the mode the CDN expects. Cloudflare uses its own codes in the 520 to 526 range for many origin problems, and its documentation lists what each means; those are worth reading alongside your origin logs. Certificate problems between the CDN and origin are covered in the SSL errors guide.
--resolve request from a trusted machine to reach the origin directly instead,
so you can compare the two paths without exposing the server.Keeping 502s away
- Monitor the upstream directly, for example a health URL that runs a trivial database query, not just the homepage through the CDN.
- Alert on repeated restarts. Automatic restarts hide crashes; a process restarting every few minutes needs attention even if visitors rarely notice.
- Pin PHP-FPM socket paths in one include file, or update nginx in the same change as any PHP version upgrade.
- Leave spare memory. A server running close to its limit will eventually lose a worker to the out-of-memory killer at the busiest moment.
- Cache full pages for anonymous visitors so bursts of traffic do not all reach the application.
- Run the HTTP Error Audit after every release and after any server or hosting change.
1. Is a 502 Bad Gateway my fault or my host's?
It depends on which layer failed. If the error page is branded by your CDN and requests straight to your server work, the problem is between the two, often a firewall or an origin setting. If your own server returns the 502, the application behind the web server has failed or is misconfigured, which is usually within your control on a VPS. On shared or managed hosting you may not be able to see or restart PHP-FPM yourself, so send your host the URL and exact times.
2. Why does refreshing the page sometimes fix a 502?
Because the cause was brief. A PHP-FPM child may have crashed on one request and been replaced, a Node process may have restarted after an error, or a reused connection may have been closed at the wrong moment. The next request found a healthy process. That makes the 502 easy to dismiss, but a pattern of short failures usually means a real crash, memory pressure or a connection timing problem that will get worse as traffic grows, so check the logs anyway.
3. Does restarting nginx fix a 502 error?
Rarely. nginx is reporting that the program behind it failed; it is not usually the cause. Restarting PHP-FPM or the Node app may bring the site back if that process had stopped, but it will fail again unless you find out why it stopped. Read the nginx error log line for the failed request first, then the upstream's own log. The message usually tells you whether you are looking at a crash, a wrong socket path, a permission problem or too little memory.
4. Do 502 errors affect my Google rankings?
Short, occasional 502s are unlikely to have a lasting effect. Search engines treat 5xx responses as temporary server problems and try again later. If they keep meeting server errors, they slow down how often they crawl the site, and pages that fail for a long period can eventually drop out of the index until they recover. Fixing the cause quickly and confirming it with a fresh crawl is what matters, rather than any one short outage.
5. Why do I get a 502 only on one page or one form?
A 502 limited to one URL usually means the application crashes while building that particular page, for example because of a fatal error in a plugin used only there, a request that uses far more memory than others, or response headers that are unusually large because of many cookies. Look at the upstream log at the moment you load that page. If nginx reports that the upstream sent too big a header, increase the buffer sizes; otherwise the fix is in the code.
6. What is the difference between 502, 503 and 504?
A 502 means a gateway or proxy got an invalid answer, or no answer, from the server behind it. A 503 means a server is temporarily unable to handle the request, often because of maintenance mode, overload or rate limiting, and it may send a Retry-After header. A 504 means the gateway waited for the server behind it and gave up because it took too long. They point to crashes, deliberate unavailability and slowness respectively, which is why the fixes differ.
The HTTP Error Audit requests every URL in your crawl scope, records the status code and response time, follows redirects and flags anything outside the 200 range, giving you the URLs and times to match against your server logs.
Open HTTP Error Fixes โ