On this page
What Is a 502 Bad Gateway (and How Do You Fix It)?
A 502 means your proxy could not get a valid response from the app behind it. Learn the real causes, how to diagnose them, and how to fix each one.
Quick answer
- A 502 Bad Gateway means a proxy in front of your app could not get a valid response from the app behind it. Your app did not return a 502 — the proxy did.
- Most often the app is not running, is listening on a different port, or is bound to
127.0.0.1where the proxy cannot reach it.- Diagnose from the proxy’s error log, not the app’s logs: the proxy names the upstream it tried and the reason it failed.
- A 502 is a connection failure; a 504 is a timeout. That distinction tells you whether to look at crashes or at slowness.
What is a 502 Bad Gateway?
A 502 Bad Gateway is an HTTP error generated by a proxy server when it cannot obtain a valid response from the upstream server it forwarded your request to. The proxy received your request successfully, tried to pass it along, and got back either nothing or something it could not use.
The consequence is that the error tells you about the layer in front of your app, not the app itself. This is why a 502 can appear while your application logs show no errors at all — the request never reached the application, or the application’s response never made it back.
The concept depends on What Is a Reverse Proxy?: a reverse proxy sits in front of your app to terminate TLS, serve static files, cache, or spread load. Nginx, Caddy, Traefik, and every PaaS router are reverse proxies, which is why 502 is one of the most common errors a small app meets.
Where this bites vibecoders
The specific way this bites vibecoders is the bind address. Run
python app.pyornode server.jslocally and it often binds to127.0.0.1, which is reachable only from the same machine. Put it behind nginx on a VPS and every request gets a 502, because nginx is a different machine from the app’s perspective. The AI-generated Dockerfile has the same problem when it binds to127.0.0.1instead of0.0.0.0, and the container is then unreachable from the host.
What causes a 502 Bad Gateway?
In rough order of how often they are the answer:
| Cause | What you see | Quick check |
|---|---|---|
| App is not running | Connection refused | curl localhost:3000 on the app’s own host |
| Wrong port in the proxy config | Connection refused | Compare proxy_pass with what the app actually binds |
Bound to 127.0.0.1 | Connection refused from proxy, works locally | Listen on 0.0.0.0 instead |
| App crashed or is crash-looping | Connection refused or reset mid-request | See How to Debug a Crash-Looping Container |
| App too slow to accept connections | Upstream timed out | Check start-up time and the connect timeout |
| App accepted then hung | Upstream timed out, or empty reply | Check for an exhausted connection pool |
| Upstream closed the connection | Empty reply from upstream | Look for a crash right at the request boundary |
| TLS mismatch between proxy and upstream | SSL handshake failed | Match protocols on both sides |
Only the first three are common for a small app. Start there, then work down.
How do I diagnose a 502?
Work outside-in, and read the proxy’s log before anything else. The proxy knows what it tried and why it failed; your app does not, because it may never have been asked.
1. Read the proxy error log. For nginx that is /var/log/nginx/error.log. The message names the upstream and the failure mode.
# Upstream refused the connection — app down, wrong port, or wrong bind address.
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://127.0.0.1:3000/api/users"
# Upstream accepted but took too long.
upstream timed out (110: Connection timed out) while reading response header
from upstream, upstream: "http://127.0.0.1:3000/api/users"2. Test the app directly, bypassing the proxy. On the app’s own host, so you isolate the layer that is broken.
# Is anything listening on that port at all?
ss -tlnp | grep 3000
# Does the app answer when asked directly?
curl -v http://127.0.0.1:3000/api/usersIf step 2 fails, the problem is the app, not the proxy, and the crash-loop guide applies. If it succeeds but the proxy still returns 502, the problem is between them: a bind address, a container network, or a firewall.
3. Check what the app is bound to. This is the single most productive check on a VPS.
# 127.0.0.1 only — unreachable from nginx. You want 0.0.0.0.
ss -tlnp | grep 30004. Look for a slow start. An app that takes 30 seconds to boot will 502 for the first 30 seconds after every deploy, then be fine. Compare the timestamp of the first 502 against the restart time. If it lines up, you need readiness gating — see What Is a Health Check?.
How do I fix a 502?
Match the fix to the cause, and confirm the fix the same way every time: curl the app directly, then curl through the proxy.
server {
listen 80;
server_name example.com;
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# A crashed upstream should be noticed quickly, not after 60s.
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
# Send the real client address so your app can log it and build
# correct absolute URLs behind HTTPS.
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}The X-Forwarded-* headers matter beyond logging: without them, your app may generate http:// URLs on an HTTPS site, or apply the wrong secure cookie and CORS origin rules.
A correct startup sequence prevents most recurring 502s: start the app, wait for it to answer a health check, then let the proxy route to it. That is what the Kubernetes readiness probe does in What Is a Health Check? and what a systemd Type=notify or a depends_on: condition: service_healthy in Compose gives you on a single box.
Why does a 502 happen during deploys specifically?
Because a deploy briefly takes the app away while the proxy keeps receiving traffic. The window is small, and the fix is to stop routing before you stop the process — take the instance out of rotation, let in-flight requests drain, then terminate it. That is What Is Graceful Shutdown?, and pairing it with a canary or blue-green rollout from What Is a Canary Deployment? means a bad release never produces a user-visible 502 at all.
If the app is exiting immediately on SIGTERM rather than draining, that is the bug to fix first.
Checklist
- Read the proxy’s error log before restarting anything.
- Confirm the app answers when curled directly, on the app’s own host.
- Verify the app is listening on
0.0.0.0, not127.0.0.1. - Check the port in
proxy_passagainst the port the app actually binds. - Set
proxy_connect_timeoutlow so a dead upstream fails fast. - Forward
HostandX-Forwarded-*headers. - Gate traffic on a health check so a slow start does not produce 502s.
- Drain connections on shutdown instead of dropping them.
- Set container and process memory limits so a leak fails as OOMKilled, not as a mystery 502 — see Why Do My Containers Keep Getting Killed (OOMKilled)?.
- Alert on 502 rate, not just uptime, since the proxy stays up while serving errors.
FAQ
What does 502 Bad Gateway mean?
A 502 means a server acting as a proxy — nginx, a load balancer, a PaaS router — tried to forward your request to the application behind it and got something invalid back, usually nothing at all. The error is generated by the proxy, not by your application, which is why the app’s own logs can look perfectly clean.
Why is my 502 only happening in production?
Production almost always has a proxy in front of the app that local development does not. The usual causes are a bind address that only listens on localhost, a port mismatch between what the proxy forwards to and what the app binds, a crashed process, or an app that is starting too slowly for the proxy’s timeout.
What is the difference between 502 and 504?
Both are gateway errors, but 504 is specifically a timeout — the proxy reached the app and waited too long for a response. A 502 means the connection failed outright or the response was malformed, which points more often at a process that is not running, a wrong port, or a bind-address problem.
Should I restart my app to fix a 502?
It may clear the symptom, but it destroys the evidence. Read the proxy’s error log first — it names the upstream it tried and the exact reason it failed, which distinguishes a connection refused, a timeout, and an upstream that closed the connection early.
Why do I get 502s for the first minute after every deploy?
Your app is starting slower than the proxy’s patience. The proxy receives traffic immediately, tries the upstream, and the connection is refused until the app finishes binding. Add a health check that gates traffic on readiness, and drain connections on shutdown, so the proxy only routes to an instance that is actually serving.
Does a 502 mean my application code has a bug?
Rarely directly. A 502 is raised by the proxy, so it usually means the app is not reachable rather than that it returned an error. Check whether the process is running, listening on the right port and address, and reachable from the proxy before looking at application logic.
Related topics
- What Is a Reverse Proxy?
- How to Debug a Crash-Looping Container
- What Is a Health Check?
- What Is Graceful Shutdown?
- How to Get Alerted When Your Site Goes Down