A 502 Bad Gateway from Nginx means the proxy did not receive a valid response from the upstream. First, compare the error.log entries at the same timestamp with the actual proxy_pass target. What you need to fix depends on whether the app is down, the address is wrong, or socket access was denied.
The commands below assume a Linux Nginx reverse proxy. In containers, also check where you run the commands. 127.0.0.1 inside the Nginx container is the Nginx container itself, not another app container.
502 Bad Gateway: Log-Based Cause Lookup Table
| Key string in error.log | First cause to suspect | Next check |
|---|---|---|
connect() failed (111: Connection refused) | App is not listening on that address/port; connection refused | ss -ltnp, app service status, proxy_pass |
connect() to unix:... failed (2: No such file or directory) | Socket path mismatch or socket not created (PHP-FPM, etc.) | Compare fastcgi_pass with FPM listen |
connect() ... failed (13: Permission denied) | Socket or parent-directory permissions, or security policy | Worker user, path permissions, SELinux/AppArmor logs |
upstream prematurely closed connection | App closed the connection before sending headers | App exceptions, restarts, OOM records |
upstream sent too big header | Upstream response headers larger than the buffer | Cookie/auth header size, proxy/FastCGI buffers |
no live upstreams | No selectable upstreams | Upstream config, preceding connection-failure logs |
This table is a starting point for diagnosis. The same error can produce a different final status code depending on proxy retries and a front-end CDN. Nginx distinguishes connection errors, timeouts, and invalid response headers when deciding whether to retry an upstream. Nginx proxy_next_upstream documentation
1. Start by matching the request time and upstream address
# Run on the Nginx server: adjust the path to match the actual error_log setting.
sudo tail -n 80 /var/log/nginx/error.log
sudo nginx -T 2>&1 | grep -nE 'upstream|proxy_pass|fastcgi_pass|uwsgi_pass|error_log'
sudo ss -ltnpnginx -T dumps the full configuration that will be applied. Strip any secrets from the config before sharing it externally. The upstream: address in the log must match the listening port.
For an HTTP upstream, send a request from the same server or container as Nginx. Replace the address below with your actual settings.
curl -sv --connect-timeout 3 --max-time 10 http://127.0.0.1:3000/health- Connection refused: check the app process, listen address, and port
- App returns a normal response: check Nginx's target address, request path, and Host/TLS settings
- Hangs after connecting: check app logs and DB/external API latency
A PHP-FPM FastCGI socket is not an HTTP server, so do not use this curl check as-is.
2. Recover from a stopped app or address mismatch
# Replace myapp with the actual service name
sudo systemctl status myapp --no-pager
sudo journalctl -u myapp --since '15 minutes ago' --no-pager
# If you run the app with PM2
pm2 statusIf the service failed, fix the startup failure shown in the logs first. If the app listens on 3001 but Nginx connects to 3000, align one side with the actual design. In Docker Compose, check the service name on the same network and the container port.
3. Check the PHP-FPM socket and permissions
# Change the PHP version and paths below to match your install
sudo systemctl status php8.2-fpm --no-pager
sudo grep -nE '^listen([[:space:]]|\.)' /etc/php/8.2/fpm/pool.d/www.conf
ls -l /run/php/Compare FPM listen with Nginx fastcgi_pass and confirm they point at the same socket. For permission errors, align FPM listen.owner, listen.group, and listen.mode with the Nginx worker user's access. Execute permission on parent directories is also required. PHP-FPM configuration documentation
If SELinux/AppArmor denial records exist, adjust the required access policy. Blindly applying chmod 777 to the socket or disabling the security module entirely is not a cause-appropriate fix.
4. Distinguish app crashes, oversized response headers, and timeouts
sudo journalctl -k --since '30 minutes ago' --no-pager | grep -iE 'oom|killed process'
free -hCheck whether the app restarted or was killed for memory at the same time as upstream prematurely closed connection. For oversized-header errors, inspect cookie or auth response size first, then adjust the buffers for that protocol.
Do not raise proxy_read_timeout just because you saw a 502. This value is the idle wait between successive reads from the upstream, not a limit on total request processing time. Timeouts are more often part of 504 diagnosis, so check the actual logs and responses together. Nginx proxy_read_timeout documentation
5. Verify after the fix
sudo nginx -t
# Run only after the syntax check succeeds
sudo systemctl reload nginxRecheck both a direct request to the upstream and a request via the external domain. Confirm the HTTP status is back to the expected value and that the same error is not still accumulating in new logs. Do not treat the app's 401 or 404 as a failure by default; compare against the expected successful response for the path you tested.
FAQ
Will restarting Nginx fix it?
If the cause is a stopped app, a wrong upstream address, or socket permissions, restarting Nginx alone will not fix it. Fix the layer the logs point to first.
The app is healthy, but I only get 502 in Docker
A test inside the app container is not the same as a test from the Nginx container. Compare the service DNS name, network connectivity, the app's listen address, and the container port.
If you confirmed timeout logs, continue with Diagnosing 504 timeouts in nginx, ALB, and gunicorn.
AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us
Questions & answers
Ask a question about this guide. We'll review and reply.