What's Actually Happening

Your Nginx server is configured as a reverse proxy to forward requests to backend application servers. When users try to access your site, they see a "502 Bad Gateway" error instead of the expected content. Nginx can't communicate with the upstream server - either it's not running, not reachable, or the connection is being rejected.

The 502 Bad Gateway error means Nginx received an invalid response from the upstream server. This is different from a 504 Gateway Timeout (where Nginx waits too long) or a 503 Service Unavailable (where the server is explicitly overloaded).

The Error You'll See

Nginx reverse proxy 502 errors appear in various formats:

```bash # Browser error page 502 Bad Gateway nginx/1.24.0

# In Nginx error log 2026/04/09 10:15:23 [error] 12345#12345: *67890 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.1, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:3000/", host: "example.com"

# Connection refused 2026/04/09 10:15:24 [error] 12345#12345: *67891 connect() failed (113: No route to host) while connecting to upstream

# No live upstreams 2026/04/09 10:15:25 [error] 12345#12345: *67892 no live upstreams while connecting to upstream

# Upstream prematurely closed connection 2026/04/09 10:15:26 [error] 12345#12345: *67893 upstream prematurely closed connection while reading response header from upstream

# Upstream timed out 2026/04/09 10:15:27 [error] 12345#12345: *67894 upstream timed out (110: Connection timed out) while reading response header from upstream

# Permission denied 2026/04/09 10:15:28 [error] 12345#12345: *67895 connect() to 127.0.0.1:3000 failed (13: Permission denied)

# DNS resolution failed 2026/04/09 10:15:29 [error] 12345#12345: *67896 backend could not be resolved (3: Host not found)

# HTTP error in curl $ curl -I https://example.com/ HTTP/1.1 502 Bad Gateway Server: nginx Content-Type: text/html Content-Length: 166 Connection: keep-alive

# In browser developer tools GET https://example.com/ 502 (Bad Gateway)

# Nginx status shows upstream errors $ curl http://localhost/nginx_status Active connections: 10 server accepts handled requests 100 100 200 Reading: 0 Writing: 5 Waiting: 5 # High error count indicates problems ```

Additional symptoms: - Intermittent 502 errors (some requests work, others don't) - 502 only on specific endpoints or URLs - 502 after certain amount of traffic - Works from some IPs but not others - Backend server logs show no incoming requests - Backend server crashes or restarts frequently

Why This Happens

  1. 1.Backend Server Not Running: The upstream application server (Node.js, Python, PHP-FPM, etc.) is stopped, crashed, or failed to start. Nginx tries to connect but gets connection refused.
  2. 2.Wrong Port or Address: Nginx is configured to proxy to wrong port or IP address. The backend is running on port 3000 but Nginx proxies to port 8080.
  3. 3.Backend Server Overloaded: The upstream server is processing too many requests and rejecting new connections. Its backlog queue is full.
  4. 4.Connection Timeout: Backend server is slow to respond and Nginx's proxy timeout is too short. The connection times out before the response completes.
  5. 5.SELinux or Firewall Blocking: System firewall or SELinux prevents Nginx from connecting to the backend. Connections are rejected at network level.
  6. 6.Socket File Issues: If using Unix sockets, the socket file doesn't exist, has wrong permissions, or is owned by a different user.
  7. 7.Backend Crashes Under Load: The application server crashes when receiving requests, leaving Nginx with no upstream to connect to.
  8. 8.DNS Resolution Issues: When using domain names for upstream, DNS resolution fails or returns wrong IP.

Step 1: Check Backend Server Status

Verify the upstream server is running and accessible.

```bash # Check if backend is running # For Node.js apps: ps aux | grep node pm2 list # if using PM2

# For Python apps: ps aux | grep gunicorn ps aux | grep uwsgi

# For PHP-FPM: systemctl status php8.2-fpm ps aux | grep php-fpm

# Check if port is listening ss -tlnp | grep 3000 netstat -tlnp | grep 3000

# Test direct connection to backend curl http://localhost:3000/ curl http://127.0.0.1:3000/

# Check with telnet telnet localhost 3000

# Or with nc nc -zv localhost 3000

# For socket connections: ls -la /var/run/php/php-fpm.sock # Check permissions stat /var/run/php/php-fpm.sock

# Test socket connection: curl --unix-socket /var/run/php/php-fpm.sock http://localhost/status

# Check backend logs # Node.js: tail -100 /var/log/nodejs/app.log

# Python: tail -100 /var/log/gunicorn/access.log tail -100 /var/log/gunicorn/error.log

# PHP-FPM: tail -100 /var/log/php-fpm/error.log

# Check if backend is accepting connections: # From Nginx server: curl -v http://backend-ip:port/ ```

Step 2: Review Nginx Upstream Configuration

Check Nginx configuration for proxy settings.

```bash # View Nginx configuration cat /etc/nginx/nginx.conf cat /etc/nginx/sites-enabled/default cat /etc/nginx/conf.d/app.conf

# Check upstream block # Typical upstream configuration: ```

```nginx upstream backend { server 127.0.0.1:3000; # or multiple servers: # server 127.0.0.1:3001; # server 127.0.0.1:3002; }

server { listen 80; server_name example.com;

location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ```

```bash # Verify configuration syntax: nginx -t

# Common issues to check: # 1. Wrong port number # 2. Wrong IP address # 3. Missing upstream block # 4. Incorrect proxy_pass syntax

# Test with simple proxy: # Minimal working config: ```

```nginx server { listen 80; server_name example.com;

location / { proxy_pass http://127.0.0.1:3000; } } ```

```bash # Apply configuration: nginx -t && nginx -s reload

# Check if Nginx can resolve upstream: # For domain-based upstreams: getent hosts backend.example.com dig backend.example.com ```

Step 3: Check Network Connectivity

Verify Nginx can reach the backend server.

```bash # Test basic connectivity: ping backend-server-ip

# Test specific port: nc -zv backend-ip 3000

# Test HTTP request: curl -I http://backend-ip:3000/

# Check if firewall allows the connection: iptables -L -n | grep 3000 ufw status | grep 3000 firewall-cmd --list-ports

# Allow port in firewall: ufw allow 3000/tcp firewall-cmd --add-port=3000/tcp --permanent firewall-cmd --reload

# Check SELinux (CentOS/RHEL): getenforce

# If Enforcing, allow HTTPD network connections: setsebool -P httpd_can_network_connect 1

# Check SELinux boolean for connecting: getsebool httpd_can_network_connect

# Allow specific port: semanage port -a -t http_port_t -p tcp 3000

# Check if Nginx can make outbound connections: sudo -u www-data curl http://localhost:3000/

# Test from Nginx process user: su -s /bin/bash www-data -c "curl http://127.0.0.1:3000/" ```

Step 4: Fix Timeout Configuration

Adjust timeout settings for slow backends.

bash
# Edit Nginx configuration:
nano /etc/nginx/sites-enabled/app.conf

```nginx location / { proxy_pass http://backend;

# Increase timeouts for slow backends proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s;

# Send timeout for large uploads client_body_timeout 60s; client_header_timeout 60s;

# Buffer settings proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k;

# For long-polling or WebSocket: proxy_http_version 1.1; proxy_set_header Connection ""; proxy_read_timeout 86400s; # 24 hours } ```

```bash # Apply changes: nginx -t && nginx -s reload

# For fastcgi (PHP-FPM): ```

nginx
location ~ \.php$ {
    fastcgi_pass unix:/var/run/php/php-fpm.sock;
    fastcgi_read_timeout 60s;
    fastcgi_send_timeout 60s;
}
bash
# Check current timeout values:
nginx -T 2>/dev/null | grep timeout

Step 5: Fix Backend Server Issues

Start or fix the backend application server.

```bash # For Node.js apps with PM2: pm2 start app.js pm2 logs pm2 restart all pm2 status

# For Node.js without PM2: node app.js # Or with systemd: systemctl start nodejs-app systemctl status nodejs-app

# For Python with Gunicorn: gunicorn app:app --bind 0.0.0.0:3000 # Or with systemd: systemctl start gunicorn systemctl status gunicorn

# For PHP-FPM: systemctl start php8.2-fpm systemctl status php8.2-fpm

# Check service is enabled: systemctl is-enabled php8.2-fpm

# Check logs for startup errors: journalctl -u nodejs-app -f journalctl -u gunicorn -f journalctl -u php8.2-fpm -f

# Verify the process is running: ps aux | grep -E "node|gunicorn|php-fpm"

# Check memory usage (OOM killer): dmesg | grep -i "out of memory" free -h

# Check if port is already in use: lsof -i :3000 fuser 3000/tcp

# Kill process using port: fuser -k 3000/tcp ```

Step 6: Fix Socket Permission Issues

Correct Unix socket permissions for local backends.

```bash # Check socket file exists: ls -la /var/run/php/php-fpm.sock ls -la /var/run/gunicorn.sock

# Check socket ownership: stat /var/run/php/php-fpm.sock

# Socket should be owned by Nginx user (www-data) # Fix ownership: chown www-data:www-data /var/run/php/php-fpm.sock

# Fix permissions: chmod 660 /var/run/php/php-fpm.sock

# For PHP-FPM, edit pool config: nano /etc/php/8.2/fpm/pool.d/www.conf ```

ini
listen = /run/php/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

```bash # Restart PHP-FPM: systemctl restart php8.2-fpm

# Verify socket permissions after restart: ls -la /var/run/php/php-fpm.sock

# For Gunicorn socket: # Create with correct permissions: gunicorn --bind unix:/var/run/gunicorn.sock \ --umask 007 app:app

# Or in systemd service: ```

ini
[Service]
ExecStart=/usr/bin/gunicorn --bind unix:/var/run/gunicorn.sock app:app
RuntimeDirectory=gunicorn
RuntimeDirectoryMode=0755
bash
# Test socket connection:
curl --unix-socket /var/run/php/php-fpm.sock http://localhost/ping

Step 7: Configure Proper Upstream Health

Set up upstream health checks and load balancing.

```nginx # Basic upstream with multiple servers: upstream backend { server 127.0.0.1:3000 weight=3; server 127.0.0.1:3001 weight=2; server 127.0.0.1:3002 backup;

keepalive 32; # Keep connections open }

# With health checks (Nginx Plus or open source): upstream backend { server 127.0.0.1:3000 max_fails=3 fail_timeout=30s; server 127.0.0.1:3001 max_fails=3 fail_timeout=30s; }

# In location: location / { proxy_pass http://backend; proxy_next_upstream error timeout http_502 http_503 http_504; proxy_connect_timeout 5s; }

# For long-running requests: upstream backend { server 127.0.0.1:3000; keepalive 64; }

location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; }

# Reload Nginx: nginx -t && nginx -s reload ```

Step 8: Debug with Detailed Logging

Enable detailed logging for troubleshooting.

bash
# Enable debug logging:
# Edit nginx.conf:
nano /etc/nginx/nginx.conf

```nginx error_log /var/log/nginx/error.log debug;

# Or for specific server: server { error_log /var/log/nginx/app-error.log debug; } ```

```bash # Reload Nginx: nginx -s reload

# Watch logs: tail -f /var/log/nginx/error.log

# Log upstream address: ```

```nginx log_format upstream_logging '[$time_local] $remote_addr - $remote_user - $server_name to: $upstream_addr: $request upstream_response_time $upstream_response_time msec $msec request_time $request_time';

access_log /var/log/nginx/upstream.log upstream_logging; ```

```bash # Check upstream address in logs: tail -f /var/log/nginx/upstream.log

# Enable stub status for monitoring: ```

nginx
location /nginx_status {
    stub_status;
    allow 127.0.0.1;
    deny all;
}
bash
# Check status:
curl http://localhost/nginx_status

Step 9: Test and Verify Fix

Confirm the proxy is working correctly.

```bash # Test through Nginx: curl -I https://example.com/

# Should return 200 or expected status

# Test with verbose output: curl -v https://example.com/

# Test specific endpoints: curl -I https://example.com/api/status

# Check response headers: curl -I https://example.com/ | grep -i "x-"

# Test load: ab -n 100 -c 10 https://example.com/

# Monitor Nginx access log: tail -f /var/log/nginx/access.log

# Check for 502 errors: grep " 502 " /var/log/nginx/access.log | tail -20

# Verify no errors in error log: tail -50 /var/log/nginx/error.log

# Test direct to backend: curl http://localhost:3000/

# Compare responses: diff <(curl -s http://localhost:3000/) <(curl -s http://example.com/)

# Check Nginx is proxying correctly: curl -H "Host: example.com" http://localhost/ ```

Step 10: Set Up Monitoring and Prevention

Create monitoring to detect 502 errors early.

```bash # Create monitoring script: cat > /usr/local/bin/check-nginx-upstream.sh << 'EOF' #!/bin/bash # Nginx Upstream Health Check

UPSTREAM_URL="http://127.0.0.1:3000/health" NGINX_URL="https://example.com/health" ALERT_EMAIL="ops@company.com"

# Check backend directly if ! curl -s -f "$UPSTREAM_URL" > /dev/null; then echo "Backend is down!" # Try to restart systemctl restart nodejs-app sleep 5 if curl -s -f "$UPSTREAM_URL" > /dev/null; then echo "Backend restarted successfully" else echo "Failed to restart backend" | mail -s "Backend Down" $ALERT_EMAIL fi fi

# Check through Nginx HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$NGINX_URL") if [ "$HTTP_CODE" = "502" ]; then echo "Nginx returning 502" | mail -s "Nginx 502 Error" $ALERT_EMAIL fi EOF

chmod +x /usr/local/bin/check-nginx-upstream.sh

# Add to cron: crontab -e # Add: */1 * * * * /usr/local/bin/check-nginx-upstream.sh

# Configure Prometheus monitoring: # Add to nginx.conf: ```

nginx
location /metrics {
    vhost_traffic_status_display;
    vhost_traffic_status_display_format prometheus;
}
bash
# Reload Nginx:
nginx -s reload

Checklist for Fixing Nginx 502 Bad Gateway

StepActionCommandStatus
1Check backend server status`ps aux \grep node, ss -tlnp \grep 3000`
2Review upstream configuration`nginx -T \grep -A10 upstream`
3Check network connectivitync -zv backend-ip 3000
4Fix timeout configurationAdd proxy_*_timeout directives
5Fix backend server issuesStart/restart backend service
6Fix socket permissionschown www-data:www-data /path/to.sock
7Configure upstream healthAdd health check parameters
8Debug with detailed loggingEnable error_log debug
9Test and verify fixcurl -I https://example.com/
10Set up monitoringCreate health check script

Verify the Fix

After fixing Nginx reverse proxy 502 errors:

```bash # 1. Backend responds directly curl http://localhost:3000/ # Returns expected content

# 2. Nginx proxies correctly curl https://example.com/ # Returns expected content (not 502)

# 3. No errors in Nginx logs tail /var/log/nginx/error.log | grep -i 502 # Returns nothing

# 4. Backend process is running ps aux | grep -E "node|gunicorn|php-fpm" # Shows running process

# 5. Port is listening ss -tlnp | grep 3000 # Shows process listening

# 6. Configuration is valid nginx -t # Shows "syntax is ok"

# 7. Timeouts configured nginx -T | grep timeout # Shows timeout values

# 8. Upstream health is good nginx -T | grep -A5 upstream # Shows upstream configuration

# 9. No permission errors ls -la /var/run/php/php-fpm.sock # Shows correct ownership

# 10. Monitoring working /usr/local/bin/check-nginx-upstream.sh # Exit code 0 ```

  • [Fix Nginx 504 Upstream Timeout](/articles/fix-nginx-504-upstream) - Timeout errors
  • [Fix Nginx 503 Service Unavailable](/articles/fix-nginx-503-service-unavailable) - Overloaded server
  • [Fix Nginx Rate Limiting Not Working](/articles/fix-nginx-rate-limiting-not-working) - Rate limiting
  • [Fix HAProxy Backend Down](/articles/fix-haproxy-backend-server-down-not-recovering) - HAProxy issues
  • [Fix Apache VirtualHost Not Working](/articles/fix-apache-virtualhost-not-working) - Apache configuration
  • [Fix PHP-FPM Pool Exhausted](/articles/fix-php-fpm-pool-exhausted) - PHP-FPM issues
  • [Fix Node.js App Crashes](/articles/fix-nodejs-app-crash-on-startup) - Node.js application errors