# HTTP 502 Bad Gateway: nginx, ALB and Cloudflare Fixes

> Fix 502 Bad Gateway: decode nginx error-log lines, php-fpm sockets, ALB keep-alive mismatches and Cloudflare 502 vs 52x, with curl checks.

Source: https://howhttpworks.com/status-codes/502
Last reviewed: 2026-10-05

> **TL;DR:** HTTP 502 Bad Gateway means a proxy could not get a usable response from the server behind it. The error log of the proxy names the exact failure; match the line to the fix below, and check that the upstream process is running, on the expected port or socket, and not closing connections early.

## What it means

RFC 9110 section 15.6.3: a gateway or proxy received an invalid response from an inbound server it accessed while attempting to fulfil the request. In practice that covers a refused connection, a connection reset, a crashed worker, a malformed response, or oversized response headers. If the proxy's upstream timeout expires, it can return [504](https://howhttpworks.com/status-codes/504) instead.

Server responses and client messages below are illustrative.

```http
HTTP/1.1 502 Bad Gateway
Server: nginx
Content-Type: text/html
```

## What you see in your client

These messages assume the response reason is `Bad Gateway`; a server can use different wording.

- **Axios:** `AxiosError: Request failed with status code 502`. With the default `validateStatus`, Axios rejects this response. Inspect `error.response.status`, `error.response.headers` and `error.response.data` before changing the request. [Axios source](https://github.com/axios/axios/blob/v1.x/lib/core/settle.js).
- **fetch:** the promise resolves with `response.status === 502` and `response.ok === false`. Check `ok` yourself; a `catch` block alone misses HTTP error responses. [Fetch behaviour](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).
- **Python requests:** `requests.exceptions.HTTPError: 502 Server Error: Bad Gateway for url: https://example.com/api`. This appears when you call `response.raise_for_status()`. Save the response body and headers before raising. [Requests source](https://github.com/psf/requests/blob/main/src/requests/models.py).
- **curl -f:** `curl: (22) The requested URL returned error: 502`. Use `curl -sS -D - https://example.com/api` while diagnosing so you retain the error body. [curl source](https://github.com/curl/curl/blob/master/lib/http.c).
- **.NET:** `System.Net.Http.HttpRequestException: Response status code does not indicate success: 502 (Bad Gateway).` This is the English message from `EnsureSuccessStatusCode()` with that reason phrase. Inspect `StatusCode` and read the body before calling it. [Runtime source](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpResponseMessage.cs), [message resource](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/Resources/Strings.resx).
- **Spring WebClient:** `org.springframework.web.reactive.function.client.WebClientResponseException$BadGateway: 502 Bad Gateway from GET https://example.com/api`. `retrieve()` uses this subclass by default; read `getResponseBodyAsString()`. RestTemplate uses `HttpServerErrorException.BadGateway` for this 5xx status. [WebClient source](https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java), [RestTemplate handler](https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java).

## Who sent it?

| Signal | Layer |
| --- | --- |
| `Server: nginx`, page "502 Bad Gateway" | nginx; read its `error.log` |
| `Server: awselb/2.0` | ALB; check target health and keep-alive settings |
| `Server: cloudflare` + `CF-Ray`, Cloudflare-styled page "Bad gateway" | Cloudflare saw a bad response from your origin or origin proxy |
| `X-Cache: Error from cloudfront` | CloudFront |
| Body from your own app server | The app returned 502 itself (an upstream API call failed) |

```bash
curl -sI https://example.com/ | grep -iE '^(HTTP|server|via|cf-ray|x-cache|x-amz-cf-id|x-amzn)'
curl -sI http://127.0.0.1:3000/    # bypass the proxy: hit the app directly on the box
```

If the direct request works but the proxied one fails, the problem is in the proxy config or the connection between them.

## Fix it: match the nginx error log

Run `tail -f /var/log/nginx/error.log` and reproduce the 502.

| Log line | Meaning and fix |
| --- | --- |
| `connect() failed (111: Connection refused) while connecting to upstream` | Nothing is listening at the `proxy_pass` address. Start the app, fix the port, or check you used `127.0.0.1` vs `localhost` (check whether the selected address is IPv4 or IPv6). |
| `connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory)` | The PHP-FPM version or socket path in `fastcgi_pass` is wrong. |
| `connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied)` | The nginx user cannot access the socket: set `listen.owner`/`listen.group`/`listen.mode` in the FPM pool to match nginx's user. |
| `upstream prematurely closed connection while reading response header from upstream` | The app crashed, was OOM-killed, hit a worker timeout (`gunicorn --timeout`), or closed an idle keep-alive connection nginx reused. Check the app log and `dmesg` for OOM kills. |
| `recv() failed (104: Connection reset by peer) while reading response header from upstream` | Same family: the upstream reset the socket. |
| `upstream sent too big header while reading response header from upstream` | Response headers (often large `Set-Cookie`s) exceed the buffer: raise `proxy_buffer_size` (see below). |
| `no live upstreams while connecting to upstream` | nginx has no available peer in the `upstream` group; inspect servers marked `down` and passive failure tracking (`max_fails`/`fail_timeout`). Restore the backends and inspect `max_fails`/`fail_timeout`. |

```nginx
location / {
    proxy_pass http://app;
    proxy_http_version 1.1;
    proxy_set_header Connection "";        # needed for upstream keepalive
    proxy_set_header Host $host;

    proxy_buffer_size 16k;                 # default 4k or 8k; holds the response headers
    proxy_buffers 8 16k;
    proxy_busy_buffers_size 32k;
}

upstream app {
    server 127.0.0.1:3000;
    keepalive 32;
}
```

For PHP-FPM, `fastcgi_buffer_size 16k; fastcgi_buffers 16 16k;` is the equivalent. If a 502 appears only after the app has been idle, check for a keep-alive race: the upstream timeout is shorter than the time nginx keeps the connection pooled. Align them (see Node below) or drop `keepalive`.

## Fix it: other stacks

Use the proxy log to distinguish a refused connection from a malformed response; both can produce 502.

## Common causes by stack

### AWS ALB and Node.js

ALB closes idle connections at 60s by default. If the target closes its idle connection first, the ALB can return 502. Inspect the access-log fields `elb_status_code` and `target_status_code` to locate the responding layer. The target must keep connections open longer than the ALB:

```javascript
const server = app.listen(3000)
server.keepAliveTimeout = 65_000   // greater than ALB idle timeout (60s)
server.headersTimeout = 66_000     // separate limit for receiving request headers
```

Node's `headersTimeout` governs incoming request headers, separately from idle keep-alive. Gunicorn's `--keep-alive` applies to workers that support persistent connections; its sync worker ignores it. [Node HTTP settings](https://nodejs.org/api/http.html#serverkeepalivetimeout), [Gunicorn settings](https://gunicorn.org/reference/settings/#keepalive). Other ALB 502 causes: the target sent a malformed HTTP response or a header over the limit, a TLS handshake failure to HTTPS targets, or the target is a Lambda that returned an invalid response shape. [ALB troubleshooting](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html#http-502-issues).

**API Gateway with Lambda:** a function error or a wrongly formatted Lambda proxy response produces 502 with `{"message":"Internal server error"}`. Inspect Lambda logs and the response contract (`statusCode`, string `body`, headers). A rejected Lambda invocation produces 500 instead. [AWS Lambda integration errors](https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway-errors.html).

### Cloudflare

Cloudflare 502 and 504 can come from your origin or from Cloudflare. [Cloudflare's diagnosis guide](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-502-504/) distinguishes branded origin errors from unbranded Cloudflare errors. If an origin or its proxy answered 502, fix that layer; if the Cloudflare page says "Bad gateway" with a Ray ID, check origin logs for that time, and look at the 52x codes first, since they are more specific: [520](https://howhttpworks.com/status-codes/520) (empty or invalid response), [521](https://howhttpworks.com/status-codes/521) (refused), [522](https://howhttpworks.com/status-codes/522) (connection timeout), [523](https://howhttpworks.com/status-codes/523) (unreachable), [524](https://howhttpworks.com/status-codes/524) (timeout after connect).

### Kubernetes and PaaS

In ingress-nginx, connection refusal or a pod dropping a connection can produce 502. Compare the logged upstream address with the Service's EndpointSlices and listening port. Check `kubectl logs` and `kubectl describe pod`, make readiness probes accurate, and allow in-flight requests to finish within the termination grace period. [Kubernetes lifecycle hooks](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/). Heroku router errors H12 (request timeout) and H13 (connection closed without response) use 503. [Heroku error codes](https://devcenter.heroku.com/articles/error-codes).

### Application as the proxy

If your own code calls another service and returns 502 when that call fails, log the upstream URL, status and error, and set client timeouts; do not pass the upstream's raw error body through to browsers.

## A 502 from a localhost API gateway

If the failing URL is `http://127.0.0.1:15721/v1/responses`, inspect that local gateway's log and configured upstream first. Make a direct request to the actual upstream from the same host or container. `127.0.0.1` points to the caller's own network namespace; inside a container, use the reachable service address. Compare status, response body and timestamp across both hops. Raising an HTTP timeout will not start a missing listener or repair an invalid upstream response.

## Related codes

- [503 Service Unavailable](https://howhttpworks.com/status-codes/503): no healthy upstream, or deliberate unavailability.
- [504 Gateway Timeout](https://howhttpworks.com/status-codes/504): a gateway timed out connecting or waiting for an upstream.
- [500 Internal Server Error](https://howhttpworks.com/status-codes/500): the responding server failed unexpectedly.
- [520 Web Server Returned an Unknown Error](https://howhttpworks.com/status-codes/520): Cloudflare's empty or invalid origin response.

Step-by-step nginx fixes: [nginx 502 Bad Gateway: read the error log, fix the upstream](https://howhttpworks.com/debug/nginx-502-bad-gateway).

See also the comparison [502 vs 503 vs 504](https://howhttpworks.com/compare/502-vs-503-vs-504): how to tell the three gateway errors apart from the nginx error log and Cloudflare codes.
