TroubleshootingWritten by GLWS13 min readUbuntu 24.04 LTS / AlmaLinux 9
Commands run on your server unless a section says otherwise. Replace example usernames, addresses and paths with your own values. Keep a working session open when changing remote access.

What 502 and 504 mean

A 502 Bad Gateway means Nginx connected to the application but got no valid answer, or could not connect at all. A 504 Gateway Timeout means Nginx waited longer than its timeout for the application to respond. In both cases Nginx is working, and the problem is between Nginx and the application behind it.

Always read the Nginx error log before changing anything. It names the exact cause.

Both systems
sudo tail -n 30 /var/log/nginx/error.log
sudo nginx -t

Read the error message

Look for the upstream message. "connect() failed (111: Connection refused) while connecting to upstream" means nothing is listening where Nginx is looking. "(13: Permission denied)" points to socket permissions or SELinux. "upstream timed out (110: Connection timed out)" is a 504, where the application is too slow or hung. "no live upstreams" means every configured backend is marked down. "upstream prematurely closed connection" means the application crashed or reset the connection.

Find what Nginx is pointing at

Show the effective configuration for your site and find the proxy_pass or fastcgi_pass line. Note the address and port or socket path exactly. A mismatch between that and where the application actually listens is the most common cause.

Both systems
sudo nginx -T 2>/dev/null | grep -nE "server_name|proxy_pass|fastcgi_pass|upstream"
DNS points each domain to the VPS. Caddy or Nginx handles public HTTPS and forwards requests to the matching application on a private loopback port.
DNS points each domain to the VPS. Caddy or Nginx handles public HTTPS and forwards requests to the matching application on a private loopback port.

Check the application is running and listening

Check that the service is active and that something listens on the expected port. If the app is not running, read its logs for the reason. If it listens on a different port or on a different address, such as only an IPv6 address, correct one side. Bind the app to 127.0.0.1 when Nginx proxies to 127.0.0.1.

Both systems
sudo systemctl status myapp --no-pager
sudo ss -lntp | grep -E ":(3000|8080|8000)\b"
sudo journalctl -u myapp --since "30 minutes ago" --no-pager | tail -n 30
Test the application directly, bypassing Nginx
curl -sv http://127.0.0.1:3000/ -o /dev/null 2>&1 | head -n 15

PHP-FPM and sockets

For PHP sites, a 502 often means PHP-FPM is stopped or Nginx cannot use its socket. Check the service and the socket file, and make sure the path in fastcgi_pass matches. The socket must be accessible to the Nginx user.

Ubuntu
systemctl status php8.3-fpm --no-pager
ls -l /run/php/
AlmaLinux
systemctl status php-fpm --no-pager
ls -l /run/php-fpm/

SELinux on AlmaLinux

If the application runs and answers locally but Nginx still reports permission denied, SELinux is probably blocking Nginx from connecting out. Check for recent denials, and enable the network connection permission rather than disabling SELinux.

AlmaLinux only
sudo ausearch -m avc -ts recent | tail -n 10
getsebool httpd_can_network_connect
sudo setsebool -P httpd_can_network_connect 1

Timeouts and slow applications

A 504 means the application is slow or stuck. Test how long the application takes with curl directly, and check CPU, memory and database load at that moment, as in the slow VPS guide. If a request legitimately takes long, such as an upload or report, raise the Nginx timeouts for that location, but fixing the slow code or query is the better answer.

Both systems: time a direct request
curl -o /dev/null -s -w "connect %{time_connect}s  first byte %{time_starttransfer}s  total %{time_total}s\n" http://127.0.0.1:3000/
Raise timeouts for a location (in the Nginx configuration)
proxy_connect_timeout 10s;
proxy_read_timeout 120s;
proxy_send_timeout 120s;

Containers, big headers and uploads

If the backend is a Docker container, check that the port is published on 127.0.0.1 and that the container is healthy. An error with large cookies or headers can show as a 502, which needs larger proxy buffers. Large uploads fail with 413, which needs client_max_body_size. After any change, test and reload.

Both systems
sudo docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
sudo nginx -t && sudo systemctl reload nginx
curl -I https://example.com/

Official documentation

These upstream references provide more detail on the commands and workflows in this guide.