502 Bad Gateway: what it means and how to fix it
·8 min read
A 502 Bad Gateway error means a server acting as a gateway or proxy, such as nginx, a load balancer or Cloudflare, passed your request to the server behind it and got back an invalid response or nothing it could use. The fault is almost never in the visitor's browser. It sits behind the proxy, where the application has crashed, is listening somewhere the proxy isn't looking, has dropped the connection mid-request or has sent headers the proxy can't handle.
Visitors can wait and reload, and that is about all. Site owners can usually find the cause in one line of the proxy's error log.
What does 502 Bad Gateway mean?
A 502 means the server that answered you is not the server that failed. RFC 9110, the HTTP standard, defines it as a gateway or proxy that "received an invalid response from an inbound server" (section 15.6.3).
The front machine, whether nginx, HAProxy, a load balancer or a CDN, is healthy enough to send you an error page. The one behind it, your Node, Python or PHP app, is the one to debug.
The other 5xx codes point at different problems, so read the number before you start.
| Code | Who usually sends it | What it usually means |
|---|---|---|
| 500 Internal Server Error | Your application | The code threw an error while building the page |
| 502 Bad Gateway | A proxy or gateway | The upstream refused the connection, closed it early or sent a response the proxy could not parse |
| 503 Service Unavailable | The server or its proxy | Overloaded or down for maintenance, ideally with a Retry-After header |
| 504 Gateway Timeout | A proxy or gateway | The upstream accepted the request but did not answer in time |
A 500 is a bug in your code, and our guide to the 500 internal server error covers that case. A 502 is usually a process or wiring problem.
How to fix a bad gateway error as a visitor
You can't fix a 502 from your browser, because the broken part is on the site's servers. Three things are worth trying:
- Wait 30 seconds and reload. A 502 caused by a deploy or a restart clears in seconds.
- Check the site's status page or its social accounts for an outage notice.
- If every site you open shows a 502, the failing gateway is on your side. Turn off your VPN or proxy, or switch networks.
Clearing your cache or cookies rarely helps.
502 Bad Gateway in nginx: read the error log first
When nginx generates a 502, it writes the reason to its error log, so start there. On most Linux installs the log is at /var/log/nginx/error.log.
sudo tail -n 200 /var/log/nginx/error.log | grep upstreamMatch the message against this table.
| Error log message | What happened | Check |
|---|---|---|
connect() failed (111: Connection refused) while connecting to upstream |
Nothing is listening on the host and port in proxy_pass |
Is the app running, and on that port? |
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) |
The socket in fastcgi_pass does not exist |
PHP-FPM status and the real socket path |
connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) |
nginx's user cannot open the socket | listen.owner and listen.group in the PHP-FPM pool |
upstream prematurely closed connection while reading response header from upstream |
The app took the request, then died or was killed before answering | App logs, worker timeouts, out-of-memory kills |
upstream sent too big header while reading response header from upstream |
The response headers did not fit in nginx's buffer | Cookie and header sizes, buffer settings |
no live upstreams while connecting to upstream |
nginx marked every server in the upstream block as failed |
The earlier errors for each server |
If the log has nothing at the time of the 502, nginx didn't create it. Your app or another proxy behind nginx returned the 502 and nginx passed it through. And if the log says upstream timed out (110: Connection timed out), you are chasing a different error. When nginx's own timeout expires it returns a 504, not a 502.
Common causes of a 502 error and how to fix them
Run these checks from the proxy machine, so you test the same network path nginx uses.
The app process or PHP-FPM is down
A refused connection means no process is listening. Confirm it, then find out why it stopped.
# Is anything listening on the port nginx proxies to?
sudo ss -ltnp | grep 3000
# Ask the app directly, skipping nginx
curl -i http://127.0.0.1:3000/
# Why did it stop?
sudo systemctl status php8.3-fpm
sudo journalctl -u myapp --since "15 min ago"
sudo dmesg | grep -i "killed process"If curl gets a normal page, the app is fine and nginx's config is wrong. If it fails, fix the app. A process that dies with nothing in its own log may have been killed by the kernel's out-of-memory killer, which dmesg records.
Short 502s on every deploy have the same cause. The old process stops before the new one listens. Use a graceful reload, or take the server out of the load balancer while it restarts.
nginx points at the wrong port or socket
The port in proxy_pass or the socket in fastcgi_pass has to match what the app listens on. On Debian and Ubuntu, a PHP upgrade breaks this easily, because the socket name carries the version.
# The app listens on 3000, so this returns 502
location / {
proxy_pass http://127.0.0.1:8080;
}
# PHP is now 8.3, but nginx still points at the 8.1 socket
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}Run ls /run/php/ to see which socket exists, fix the path, then run sudo nginx -t and reload nginx.
Docker adds its own version of this mistake. An app that binds to 127.0.0.1 inside its container can't be reached from an nginx container. Bind it to 0.0.0.0 and point proxy_pass at the Compose service name.
The app's own timeout kills the request
A slow request turns into a 502 instead of a 504 when the app server gives up before nginx does. nginx waits 60 seconds by default (proxy_read_timeout). Gunicorn kills a silent worker after 30 seconds by default and logs WORKER TIMEOUT. PHP-FPM's request_terminate_timeout kills the worker too, once you set it. Either way nginx sees the connection close with no response and logs "upstream prematurely closed connection".
Raising every timeout hides the error and leaves you with a page that takes a minute to load. Find the slow query or API call instead, and move long jobs such as exports to a background queue.
Response headers are too big for the proxy buffer
nginx reads the response headers into one buffer, 4 KB or 8 KB by default depending on the platform. If the headers don't fit, nginx treats the response as invalid and returns a 502. Large Set-Cookie headers from auth libraries and long Content-Security-Policy headers can push you over. Raise the buffers in the block that proxies to the app.
proxy_buffer_size 16k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# For PHP-FPM, set the fastcgi_ versions instead
fastcgi_buffer_size 16k;
fastcgi_buffers 8 16k;Then find out why the headers grew, because a bigger buffer only buys time.
Random 502s behind a load balancer
If the 502s are rare and random, suspect a keep-alive mismatch, where the load balancer reuses an idle connection just as the app closes it. AWS lists this as a 502 cause on Application Load Balancers and says to check whether the target's keep-alive is shorter than the load balancer's idle timeout (AWS troubleshooting guide). The ALB default is 60 seconds. Node.js sets server.keepAliveTimeout to 5 seconds, so raise it.
const server = app.listen(3000);
server.keepAliveTimeout = 65_000; // above the ALB's 60-second idle timeoutCloudflare 502: is it Cloudflare or your origin?
Look at the error page. Cloudflare's docs say a Cloudflare-branded 502 or 504 page means your origin returned that error and Cloudflare passed it on (Cloudflare's 502 and 504 guide). Cloudflare calls that the most common case, so debug your origin as above.
A plain, unbranded 502 page came from Cloudflare itself. The same docs blame broken compression at the origin, such as gzip content sent with a stale Content-Length header, and suggest turning compression off at the origin to test it. Brief 502s can also happen while Cloudflare shifts traffic between data centers.
When Cloudflare can't connect to your origin at all, it usually shows a different code. A 521 means the origin refused the connection, and a 522 means the connection timed out. With Cloudflare Tunnel, a 502 that says "Unable to reach the origin service" means the tunnel is connected but cloudflared can't reach the service in your ingress rule.
To take Cloudflare out of the picture, send the request straight to your origin's IP with the right hostname.
curl -sI --resolve example.com:443:203.0.113.10 https://example.com/
# add -k if the origin uses a Cloudflare Origin CA certificateIf the origin returns 502 here too, the problem is on your server. If it returns 200 and visitors still get the plain page, test with compression off, then contact Cloudflare Support with the time, the failing URL and the output of /cdn-cgi/trace.
Does a 502 error hurt SEO?
A 502 that clears in a few minutes does little harm. One that lasts days, or hits many URLs, slows Google's crawling and can push pages out of the index. Google's page on how HTTP status codes affect its crawlers says:
- 5xx and 429 errors make Google's crawlers slow down for a while, and the slowdown is proportionate to the number of URLs returning server errors.
- Google ignores any content it receives with a 5xx status.
- Indexed URLs stay in the index at first, but Google removes URLs that keep returning server errors.
- Once the server returns 2xx again, Google raises the crawl rate gradually.
Google doesn't say how long it waits before dropping a URL, so treat any 5xx in Search Console as urgent.
The worst place for a 502 is /robots.txt. If Google can't fetch the file because of a server error, it stops crawling the whole site for the first 12 hours, then uses the last good copy for up to 30 days, per Google's robots.txt spec. A proxy that returns 502 for every path takes robots.txt down with it.
In Search Console, the Page indexing report lists affected pages under "Server error (5xx)", and the Crawl stats report breaks Googlebot's requests down by response. After the fix, confirm those URLs return 200 before you click Validate fix.
For planned downtime, don't let the proxy throw 502s. Serve a 503 with a Retry-After header, the code RFC 9110 defines for overload and scheduled maintenance. Google's robots.txt spec says a 503 also makes Google retry the file fairly often.
Check your URLs for 502 errors in bulk
Our HTTP Status Bulk Checker takes a pasted list of up to 20 URLs, one per line, and requests each one. For every URL it returns the status code, the first redirect's code and target, the final URL, the number of hops and the response time, then counts the redirects, 4xx, 5xx and unreachable URLs. Each URL gets 8 seconds and up to five redirects. A rate limit, a bot check or a bare 403 is marked as not answering the checker, so your firewall isn't mistaken for a broken page.
It doesn't download page bodies, so an error message served with a 200 status reads as 200. It doesn't crawl your site or expand a sitemap. You pick the URLs, which suits a 502 investigation. After each fix, paste the URLs from Search Console's "Server error (5xx)" list, or one URL per page template. A run costs 20 credits. To follow one URL hop by hop, use the Redirect Chain & HTTP Header Checker.