Nginx 502 Bad Gateway: causes and how to fix it

A 502 from Nginx means Nginx itself is working but the application it forwards requests to (Node, PHP-FPM, Gunicorn, a container) did not give it a valid response. The Nginx error log names the exact reason; most often the backend is not running, listens on a different address, or Nginx is not allowed to reach its socket.

Applies to:Nginxany version used as a reverse proxyLinuxUbuntu, Debian, RHEL and others
On this page

What it means

When Nginx works as a reverse proxy, it receives the visitor's request and passes it on to another program, called the upstream: an application server, PHP-FPM, or a container. The HTTP standard defines 502 Bad Gateway as the answer a proxy gives when it got an invalid response from that upstream. So the page you see comes from Nginx, but the problem is almost always on the hop between Nginx and the application.

A related code helps narrow it down: if the upstream accepts the connection but does not answer in time, Nginx returns 504 Gateway Timeout instead. A 502 usually means the connection failed outright or the answer was malformed or cut off.

Diagram: the browser sends a request to Nginx, Nginx forwards it to the application with proxy_pass; when the application refuses or breaks the connection, Nginx returns 502 to the browser.
IllustrationNginx answers 502 when the application behind it refuses the connection or returns an invalid response.

Symptoms and causes

You are likely dealing with this problem if:

  • Browsers show 502 Bad Gateway with nginx (and often a version) at the bottom of the page.
  • Static files served by Nginx still load, while pages handled by the application fail.
  • The error appears right after a deploy, a restart or a configuration change, or under heavy load.

The usual causes, most common first:

The upstream is not running
The app crashed, failed to start after a deploy, or was killed for using too much memory, so nothing listens where Nginx connects. The log says connect() failed (111: Connection refused).
Wrong address, port or socket
proxy_pass or fastcgi_pass points to a port or socket path the app does not use, for example after a PHP version upgrade changed the PHP-FPM socket name.
Nginx is not allowed to connect
Socket file permissions, or SELinux on Red Hat based systems, block the Nginx worker. The log shows (13: Permission denied).
The response is too big or cut off
Very large response headers (often big cookies) overflow the proxy buffer (upstream sent too big header), or the app closes the connection mid-response (upstream prematurely closed connection).
All upstream servers marked as failed
With several servers in an upstream block, Nginx stops sending to ones that failed recently and logs no live upstreams.

How to fix it

Work through these in order. They start with the quickest, safest checks and move to deeper repairs; stop as soon as the problem is gone.

1.Read the Nginx error log

Easy

Each cause of a 502 writes a different message, so the log tells you which of the fixes below applies.

  1. Follow the error log and reload the failing page (the default path on most Linux packages is shown; check the error_log directive if yours differs):
    Terminalneeds sudo / root
    sudo tail -f /var/log/nginx/error.log
  2. Match the message: Connection refused → fix 2; No such file or directory with a socket path → fix 3; Permission denied → fix 4; too big header → fix 5; prematurely closed → fix 2 and the app's own log; upstream timed out → it is really a 504, see the note in fix 5.

2.Check that the application is running and listening

Easy

Connection refused means nothing accepts connections on the address Nginx uses. The app must be started, or restarted after a crash.

  1. Check the service (replace the name with your app's systemd unit, for example php8.3-fpm, gunicorn or myapp):
    Terminal
    systemctl status myapp
    journalctl -u myapp -n 50 --no-pager
  2. See what is actually listening on the port from your proxy_pass line (here 3000):
    Terminalneeds sudo / root
    sudo ss -ltnp | grep 3000
  3. Ask the app directly, bypassing Nginx. A normal HTTP answer means the app is fine and the problem is in the Nginx configuration:
    Terminal
    curl -i http://127.0.0.1:3000/
  4. If the app is stopped or crashing, start it and read its own log for the reason (missing environment variable, port already in use, out of memory). For containers, docker ps and docker logs <container> show the same.

3.Make proxy_pass point to the right address or socket

ModerateChanges settings

Nginx and the app have to agree on one address. A typo, a changed port, or a renamed socket after an upgrade breaks every request.

Before you start: Test the configuration before reloading; a syntax error would stop Nginx from applying any changes.

  1. Find where Nginx sends requests:
    Terminalneeds sudo / root
    sudo nginx -T | grep -nE 'proxy_pass|fastcgi_pass|upstream'
  2. Compare it with the app's listen address. For PHP-FPM, the socket path is the listen line of the pool file:
    Terminal
    grep -R '^listen' /etc/php/*/fpm/pool.d/
    ls -l /run/php/
  3. Correct the proxy_pass (or fastcgi_pass) line, for example:
    nginx.conf
    location / {
        proxy_pass http://127.0.0.1:3000;
    }
    
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
  4. Test and reload:
    Terminalneeds sudo / root
    sudo nginx -t && sudo systemctl reload nginx

4.Fix socket permissions or SELinux

AdvancedChanges settings

With (13: Permission denied) the address is right but the Nginx worker user (often www-data or nginx) may not open the socket, or SELinux forbids Nginx from making network connections.

Before you start: Grant only what Nginx needs. Do not make the socket writable for everyone and do not switch SELinux off; both open the server to other problems.

  1. Check which user Nginx runs as and who owns the socket:
    Terminal
    ps -o user= -C nginx | sort -u
    ls -l /run/php/php8.3-fpm.sock
  2. For PHP-FPM, set the socket owner in the pool file to the Nginx user, then restart PHP-FPM:
    Config
    listen.owner = www-data
    listen.group = www-data
    listen.mode = 0660
  3. On RHEL, Rocky, Alma or Fedora with SELinux enforcing, a proxy to a TCP port is blocked by default. Check for denials and allow Nginx to connect:
    Terminalneeds sudo / root
    sudo ausearch -m avc -ts recent | grep nginx
    sudo setsebool -P httpd_can_network_connect 1

5.Raise proxy buffers for large response headers

AdvancedChanges settings

Nginx reads the start of the upstream response, including headers, into a buffer of one memory page (4K or 8K) by default. Large cookies or many headers overflow it and Nginx answers 502 with upstream sent too big header.

Before you start: Bigger buffers use more memory per connection. Raise them moderately and only where needed.

  1. Add larger buffers in the location or server block that proxies the app:
    nginx.conf
    proxy_buffer_size 16k;
    proxy_buffers 8 16k;
    proxy_busy_buffers_size 32k;
  2. For PHP-FPM use the matching fastcgi_buffer_size and fastcgi_buffers directives instead.
  3. Test with sudo nginx -t and reload. If the log instead says upstream timed out, the request is too slow rather than broken: raise proxy_read_timeout (default 60s) only after making the slow request faster where possible.

Check that it worked

  • curl -I https://your-site/ returns HTTP/1.1 200 (or the status your page should have) instead of 502.
  • New 502 entries stop appearing in /var/log/nginx/error.log while you browse the site.
  • After a restart of the app or the server, the site comes back on its own (the app's systemd unit is enabled: systemctl is-enabled myapp).

If it still fails

  • If the 502 only happens under load, the app may be running out of workers or memory: check dmesg for OOM kills and the app's worker settings.
  • If a CDN or Cloudflare sits in front, check whether the 502 page is theirs or Nginx's: the footer and response headers say which server produced it.
  • Compare with a known-good configuration from version control, and read the ngx_http_proxy_module documentation for the directives you use.

Sources

  1. Module ngx_http_proxy_moduleOfficial · nginx.org
  2. Module ngx_http_upstream_moduleOfficial · nginx.org
  3. 502 Bad Gateway · MDN Web Docs
  4. RFC 9110: HTTP Semantics, 502 Bad GatewayOfficial · IETF