# Fix nginx 502 Bad Gateway and 504 Gateway Timeout

> Your site shows 502 or 504 from nginx. Read the one error log line that says why, then fix the cause it points to, instead of restarting things at random.

Source: https://rack4.io/tutorials/fix-nginx-502-504 · Updated: 2026-11-06

Difficulty: intermediate · Time: about 15 minutes · Tested on: Ubuntu 24.04 LTS, Debian 13 Stable

Prerequisites:

- A site served by nginx as a reverse proxy
- Root or sudo access to the server

A 502 or 504 from nginx means nginx itself is working, but the app behind it (the "upstream": your Node, Python or PHP process, or a container) didn't give it a usable answer. nginx writes the exact reason to its error log, so start there:

```bash
sudo tail -n 20 /var/log/nginx/error.log
```

Find the newest line for your request, match it to a section below, and fix that. Restarting nginx rarely helps, because nginx usually isn't what broke.

## Is it really nginx answering?

Check the `Server:` header of the error page from your computer:

```bash
curl -sI https://example.com/ | grep -iE '^(HTTP|server)'
```

`server: nginx` means your server's nginx answered, and this page is for you. `server: cloudflare` means a CDN or proxy in front of your server answered; its own error pages (such as Cloudflare's 521, 522 or 524) are about reaching your server at all. In that case, check the server's nginx directly with `curl -sI http://127.0.0.1/` on the server.

## 502: "connect() failed (111: Connection refused)"

```text
connect() failed (111: Connection refused) while connecting to upstream, … upstream: "http://127.0.0.1:8080/"
```

Nothing is listening where nginx is sending requests. Either the app isn't running, or it's listening on a different port or address from the one in `proxy_pass`.

Check whether the app is running, using its service name:

```bash
systemctl status myapp
journalctl -u myapp -n 50 --no-pager
```

If it stopped or keeps restarting, the journal says why: a crash, a bad config, a missing environment variable, or a database it can't reach. Fix that, then `sudo systemctl restart myapp`. ([Read logs with journalctl](https://rack4.io/tutorials/read-logs-with-journalctl) has more on reading them.)

If the app *is* running, compare where it listens with where nginx sends requests:

```bash
sudo ss -ltnp | grep -E 'node|python|gunicorn|uvicorn|java|php'
grep -rn proxy_pass /etc/nginx/sites-enabled/
```

On our test server, `proxy_pass` pointed at `127.0.0.1:8080` while the app had come up on `127.0.0.1:8081`: nginx returned 502 until the two matched. Also check the address: an app listening only on `::1` (IPv6) won't answer a `proxy_pass` to `127.0.0.1`.

The same message with a unix socket, `connect() to unix:/run/app/app.sock failed (111: Connection refused)`, means the socket file exists but nothing is behind it: the app stopped and left the file behind. Restart the app.

## 502: "(2: No such file or directory)"

```text
connect() to unix:/run/app/app.sock failed (2: No such file or directory)
```

nginx is pointed at a socket file that doesn't exist. Either the app isn't running, or it creates its socket somewhere else. For PHP-FPM, compare the `listen =` line in the pool file (`/etc/php/*/fpm/pool.d/www.conf`) with `fastcgi_pass` in your site; the PHP version is part of the socket name, so a PHP upgrade often changes it.

## 502: "(13: Permission denied)"

```text
connect() to unix:/run/app/app.sock failed (13: Permission denied)
```

The socket exists, but nginx's user isn't allowed to use it. Check who nginx runs as and who owns the socket:

```bash
ps -o user= -C nginx | sort | uniq -c
ls -l /run/app/app.sock
```

nginx's workers run as `www-data` on Ubuntu and Debian (`nginx` on AlmaLinux and Rocky Linux). On our test server the socket was `srw------- root root`, so only root could connect. Give nginx's group access in the app's own settings, so it survives a restart. For PHP-FPM that's `listen.owner = www-data`, `listen.group = www-data` and `listen.mode = 0660` in the pool file; other apps have their own setting for the socket's group and mode. As a quick check that this is the problem:

```bash
sudo chgrp www-data /run/app/app.sock && sudo chmod 660 /run/app/app.sock
```

That fixed the 502 on our test server, but it lasts only until the app recreates the socket.

On AlmaLinux and Rocky Linux, SELinux can cause the same message even with the right permissions: look for `avc: denied` in `sudo ausearch -m avc -ts recent`.

## 504: "upstream timed out (110: Connection timed out)"

```text
upstream timed out (110: Connection timed out) while reading response header from upstream
```

The app accepted the request but took longer to answer than nginx was willing to wait: 60 seconds by default (`proxy_read_timeout`). Look at what's slow before raising the limit:

- **The app is overloaded**: check CPU and memory with `top` ([Troubleshoot high CPU and memory](https://rack4.io/tutorials/troubleshoot-high-cpu-and-memory)). If the server is out of memory or swapping, everything slows down.
- **A slow database query or external API call**: the app's own log, or the database's slow query log, shows which.
- **Too few workers**: one long request can block every other request when the app has only one or two workers.

Raising the timeout is right only for requests that are slow by design, such as a report or an export. Do it for that location only, not the whole site:

```nginx
location /export/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_read_timeout 300s;
}
```

A long timeout on everything just makes visitors wait longer for the same failure.

## After any change to nginx

Test the configuration before reloading, so a typo doesn't take the whole site down:

```bash
sudo nginx -t && sudo systemctl reload nginx
```

`reload` keeps serving while it switches over; a failed `nginx -t` changes nothing.

## Still stuck

Undo your most recent change, if a 502 started right after one: a deploy, a package upgrade or a config edit. If you ask for help, include the error log line, `systemctl status` of the app, and the `location` block from your site's config.

We reproduced every error on this page on 6 November 2026 on a Rack4 server running Ubuntu 24.04 with nginx 1.24, using a small test app: stopped, on the wrong port, behind a missing socket, behind a root-only socket, and slower than the timeout. The SELinux note wasn't part of that test.
