A 502 Bad Gateway error means a server acting as a gateway or proxy got an invalid response from the server behind it. The visitor reached something, usually nginx, Apache, a load balancer or a CDN, but that layer could not get a proper answer from your application. The application crashed, is restarting, is listening somewhere else, or closed the connection halfway through.
That makes a 502 a message about two servers, not one. This guide shows how to tell which layer returned it, how to fix the usual causes and how to hear about the next 502 before your customers do.
What a 502 tells you
Most websites have at least two layers between the visitor and the code. A CDN or load balancer sits in front, a web server such as nginx terminates the connection, and an application process, such as PHP-FPM, Node.js, Gunicorn or a Java server, builds the page. Each layer forwards the request to the next one and passes the answer back.
A 502 appears when one of those layers forwards a request and gets nothing usable back. Compare it with the other gateway errors:
- 502 Bad Gateway: the upstream server answered with something invalid, reset the connection or refused it.
- 503 Service Unavailable: the service is overloaded, in maintenance or has no healthy server to send the request to.
- 504 Gateway Timeout: the upstream server did not answer in time.
So a 502 usually means broken or not running, a 504 usually means slow. Our HTTP status codes cheat sheet lists the rest of the family, and the guide to 503 errors covers overload and maintenance.
Find the layer that returned the 502
Before changing anything, find out who answered. The error page and the response headers usually say it outright:
curl -sI https://www.example.com/checkout
HTTP/2 502
server: cloudflare
cf-ray: 8d2f1c3a5b7e4a21-ARN
server: cloudflarewith a Cloudflare error page naming your host: Cloudflare reached your origin, and the origin failed.server: nginxorApachewith a plain error page: your own web server could not reach the application behind it.server: awselb/2.0or another load balancer: the load balancer got no valid answer from any target, or from the one it picked.
Then compare with a request that skips the outer layer. Send it straight to the origin or to the application port on the server itself:
curl -sI --resolve www.example.com:443:203.0.113.10 https://www.example.com/checkout
curl -sI http://127.0.0.1:3000/checkout
If the application answers locally but not through the proxy, look at the proxy’s configuration. If it doesn’t answer locally either, the application is the problem.
Common causes and how to fix them
The application process crashed or is restarting
The most common cause. The process behind the proxy ran out of memory, hit an unhandled exception or was killed, and the proxy has nothing to talk to. nginx logs it plainly:
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://127.0.0.1:3000/checkout"
Check whether the process runs and why it stopped:
systemctl status myapp
journalctl -u myapp --since "30 min ago"
dmesg -T | grep -i "killed process"
A line from the kernel’s out-of-memory killer means the server ran out of memory. Restarting brings the site back, but the real fix is more memory, fewer workers or a leak found and fixed. Make sure the service restarts on its own after a crash, for example with Restart=on-failure in its systemd unit.
PHP-FPM is down or out of workers
On PHP sites, nginx passes requests to PHP-FPM through a socket or port. If PHP-FPM is stopped, the socket path is wrong or the socket’s permissions keep nginx out, every PHP page returns 502:
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory)
That message usually follows a PHP upgrade: nginx still points at the old version’s socket. Point fastcgi_pass at the new socket and reload nginx. If the log instead says server reached pm.max_children, every worker is busy, often waiting for a slow database or an external API. Raise the limit only if the server has memory to spare; otherwise find what keeps the workers busy.
Wrong upstream address or port
After a migration, a container rebuild or a change of port, the proxy may still forward to the old address. The error log shows the address it tried. Compare it with where the application actually listens:
ss -ltnp | grep -E ':(3000|8000|9000)'
In Docker and Kubernetes, the cause is often a service name that no longer resolves, a container listening on 127.0.0.1 inside the container instead of 0.0.0.0, or a readiness check that marks every pod as unready.
The upstream closed the connection early
upstream prematurely closed connection while reading response header means the application accepted the request and then hung up. Typical reasons are a crash halfway through the request, a worker killed for exceeding its own time limit, such as Gunicorn’s --timeout or PHP’s max_execution_time, and keep-alive connections the application closes sooner than the proxy expects. Align the timeouts so the proxy waits a little longer than the application, never the other way round, and look at which requests are slow enough to hit the limit.
Response headers too large
Large cookies or many Set-Cookie headers can be bigger than nginx’s buffer for the upstream’s response headers. nginx then logs upstream sent too big header while reading response header from upstream and returns 502. Raise the buffers carefully:
proxy_buffer_size 16k;
proxy_buffers 8 16k;
# for PHP-FPM
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
Then find out why the headers grew. A cookie that grows on every visit is a bug worth fixing.
The CDN cannot talk to your origin
When a CDN or load balancer returns 502, check what sits between it and your server. A firewall that blocks the CDN’s address ranges, an origin certificate the CDN does not accept, an origin that only speaks HTTP/1.0 or closes connections too eagerly, or a DNS record pointing at an old server all end the same way. Test the origin directly, as shown above, and check the firewall and the TLS settings on both sides.
502s during deploys
If 502s show up for ten or twenty seconds at a time and always around a release, the deploy itself causes them. The old process stops, the new one takes a few seconds to start, and every request in between fails. The fix is a release order where something is always ready to answer:
- Start the new version next to the old one.
- Wait until its health check passes. Our guide to building a health check endpoint shows what that check should test.
- Switch traffic to the new version.
- Let the old version finish the requests it has, then stop it.
Process managers and orchestrators support this as rolling or zero-downtime reloads. Use them rather than a plain restart.
How monitoring catches 502s
A 502 is often short. A deploy causes twenty seconds of errors, a worker crashes and restarts, a cache hides the problem for most visitors. By the time someone opens the site it works again, but a customer in the middle of checkout saw the error.
An uptime monitor requests your pages on a schedule and records the status code and response time of every check. A 502 is a failed check. What matters is what happens next:
- Confirm the failure. A single failed request can be a blip. Uptimecheck checks every minute from Stockholm, Sweden, and confirms every failure with a second check before an incident opens, so a one-off reset doesn’t wake anyone up.
- Show the evidence. The incident carries the failing response, so you see the status code, the
serverheader and the error page, and a screenshot of what visitors saw. When the server did not answer at all, you get the network path instead. - Reach the right people. Alerts go to email, Slack, Microsoft Teams, PagerDuty, webhooks and the other alert channels your team uses.
Watch more than the homepage. The homepage is often cached by a CDN and keeps working while checkout, login and the API return 502. Add uptime monitors for the pages that earn money and API monitors for the endpoints your apps call. When the 502 comes from an application that ran out of memory, server monitoring shows the memory curve next to the outage.
502 checklist
- Read the
serverheader and the error page to see which layer returned the 502. - Request the origin and the application port directly to narrow it down.
- Read the proxy’s error log for the same timestamp: refused, closed early or too big header.
- Check that the application process runs, and why it stopped if it didn’t.
- Compare the upstream address, socket and port with where the application listens.
- Align timeouts so the proxy waits longer than the application.
- Deploy by starting the new version before stopping the old one.
- Monitor key pages and endpoints every minute and confirm every failure before alerting.