How to Fix 502 Bad Gateway Errors (Nginx, Cloudflare, Docker)
Diagnose and fix 502 Bad Gateway errors by checking upstream services, ports, proxy config, timeouts, and logs with real commands.
How to Fix 502 Bad Gateway Errors
A 502 Bad Gateway means a reverse proxy (nginx, HAProxy, Cloudflare, an AWS ALB) tried to forward your request to an upstream server and got back either nothing or an invalid response. The client did nothing wrong; the proxy could not complete the handoff. Fixing 502s is almost always about answering one question: what happened between the proxy and the backend?
What 502 actually means
RFC 9110 defines 502 as: the gateway received an invalid response from an inbound server. In practice, that covers four situations:
- The upstream process is not running.
- The upstream is running but not listening on the address the proxy is dialing.
- The upstream accepted the connection but closed it or timed out before sending a full response.
- The upstream returned bytes the proxy could not parse as HTTP (a common symptom of TLS on one side and cleartext on the other).
Work through them in that order. Do not restart nginx first, do not clear caches first, do not blame Cloudflare first.
Step 1: Confirm the upstream is running
On a systemd host:
systemctl status myapp
journalctl -u myapp -n 200 --no-pager
On Docker:
docker ps --filter name=myapp
docker logs --tail=200 myapp
If the container is restarting in a loop, that is your 502. Fix the crash before touching the proxy.
Step 2: Check it is listening on the expected port
ss -tlnp | grep -E '3000|8080|8000'
# or
netstat -tlnp | grep LISTEN
Pay attention to the bind address. A process bound to 127.0.0.1:3000 is unreachable from another container or a proxy on a different host, even though it looks fine locally. If your nginx is on the host and your app is in Docker, the app must bind 0.0.0.0 inside the container and publish the port.
Step 3: Curl the upstream directly
From the proxy machine, bypass nginx entirely:
curl -v http://127.0.0.1:3000/health
curl -v --resolve api.internal:8080:10.0.1.42 http://api.internal:8080/health
If this fails, the proxy is innocent. If it succeeds, the proxy config is the problem.
Step 4: Audit the nginx upstream and proxy_pass
Typical shape:
upstream app_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 443 ssl;
server_name api.example.com;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
}
Validate and reload:
nginx -t
nginx -s reload
Common config bugs that cause 502:
proxy_pass https://...pointing at an HTTP-only upstream (or vice versa).- Trailing slash mismatch in
proxy_passproducing a URI the upstream rejects. - SELinux on RHEL/Rocky blocking nginx from making outbound sockets. Fix with
setsebool -P httpd_can_network_connect 1.
Step 5: Raise timeouts for slow endpoints
If the 502 only appears on long requests (reports, uploads, LLM streams), you are hitting timeout defaults. Set explicit values in the location or http block:
proxy_connect_timeout 10s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
The default proxy_read_timeout is 60 seconds. Anything the backend takes longer than that to answer will surface as 502 or 504 depending on when the socket dies.
Step 6: Keepalive for high-traffic upstreams
Under load, nginx can exhaust ephemeral ports opening a new TCP connection per request and start returning intermittent 502s. Add keepalive to the upstream block and set proxy_http_version 1.1 plus Connection "" in the location, as shown above.
Step 7: Read the error log
tail -f /var/log/nginx/error.log
The exact wording maps to the fix:
connect() failed (111: Connection refused)- upstream is down or not listening on that port.upstream prematurely closed connection- the app crashed mid-request or its own timeout is shorter than nginx's.no live upstreams- every server in the upstream block has been marked down; check health of each.upstream sent invalid header- the app returned garbage, often because it is speaking a different protocol.
Cloudflare-specific 502s
When Cloudflare returns a 502, the issue is between Cloudflare and your origin, not between the browser and Cloudflare. Check:
- Origin unreachable - your firewall or security group is blocking Cloudflare IPs. Allow the ranges published at cloudflare.com/ips-v4.
- Origin timeout - Cloudflare waits 100 seconds for the origin on Free/Pro plans. Longer responses need Enterprise or a background job pattern.
- SSL handshake failure - Full (Strict) mode requires a valid cert on the origin. Use
curl -v https://origin.example.com --resolveto reproduce what Cloudflare sees. - Test the origin bypassing Cloudflare by editing your local hosts file or using
curl --resolve. If the origin is fine directly, the fix is at Cloudflare (Page Rules, WAF, cache).
Docker networking cases
Most Docker-related 502s come down to network namespaces:
- nginx in a container calling
127.0.0.1:3000is calling itself, not the host and not another container. Use the other container's service name on a shared Docker network. - To reach a service on the host from inside a container, use
host.docker.internalon Docker Desktop, or add--add-host=host.docker.internal:host-gatewayon Linux. - If both nginx and the app are in Compose, put them on the same network and use
proxy_pass http://app:3000;- Docker's embedded DNS resolves the service name.
A quick checklist
- Is the upstream process alive?
- Is it bound to the address the proxy dials?
- Does
curlfrom the proxy host succeed? - Does
nginx -tpass and has nginx been reloaded? - Are timeouts long enough for the slowest endpoint?
- What does the error log say, verbatim?
Answer those six in order and you will resolve almost every 502 without guesswork.