How to Fix Nginx 502 Bad Gateway: A Complete Troubleshooting Guide for Developers

How to Fix Nginx 502 Bad Gateway: A Complete Troubleshooting Guide for Developers

You deploy your app, fire up the browser, and instead of your beautifully crafted interface, you’re staring at a stark white page that reads “502 Bad Gateway.” If you’re nodding along, you’re in good company — this is one of the most common errors developers encounter when working with Nginx as a reverse proxy.

I’ve spent more hours than I care to admit hunting down the source of this error across production servers, staging environments, and local dev setups. The frustrating part isn’t the error itself — it’s that “502 Bad Gateway” is a symptom, not a diagnosis. It’s Nginx shrugging and saying, “I tried to talk to the backend, but something went wrong.”

In this guide, I’ll walk you through exactly how to fix nginx 502 bad gateway errors, starting from the most common culprits and working toward the edge cases that’ll have you pulling your hair out at 2 AM.


What Does “502 Bad Gateway” Actually Mean?

Before we fix anything, let’s understand what’s happening under the hood.

A 502 Bad Gateway is an HTTP status code that means Nginx — acting as a proxy or gateway — received an invalid response from the upstream server it was trying to reach. Think of Nginx as a middleman: it receives a request from the user, forwards it to your backend application (Node.js, Python, PHP-FPM, etc.), and then relays the response back.

When that backend doesn’t respond properly — or at all — Nginx returns a 502 to the client.

The key insight here: the problem is almost never Nginx itself. It’s the communication between Nginx and whatever sits behind it.

[Client Browser] → [Nginx Reverse Proxy] → [Backend Application]
                                              ↑
                                    Something breaks here

Quick Diagnosis: Where to Start Looking

Before diving into specific fixes, run these three commands. They’ll tell you 80% of what you need to know in under 30 seconds.

Step 1: Check the Backend Service

sudo systemctl status your-backend-service

If the service shows as inactive, failed, or dead, you’ve found your culprit. Restart it:

sudo systemctl restart your-backend-service

Step 2: Check Nginx Error Logs

This is your single most important diagnostic tool:

sudo tail -50 /var/log/nginx/error.log

Look for lines like these — they tell you exactly what went wrong:

*1 connect() to unix:/var/run/php/php8.3-fpm.sock failed (2: No such file or directory)
*3 connect() failed (111: Connection refused) while connecting to upstream
*5 upstream timed out (110: Connection timed out) while reading response header
*7 recv() failed (104: Connection reset by peer) while reading response header

Each of these messages points to a different root cause, which we’ll address below.

Step 3: Test the Backend Directly

Bypass Nginx entirely and see if the backend responds on its own:

curl -I http://127.0.0.1:3000
# or for Unix sockets:
curl -I --unix-socket /var/run/php/php8.3-fpm.sock http://localhost

If this fails, the problem is in your backend. If it succeeds, the problem is in how Nginx talks to your backend.


Most Common Causes (And How to Fix Them)

1. The Backend Service Is Down or Crashed

This is the number one cause of 502 errors. Your application crashed, ran out of memory, or was never started in the first place.

How to Diagnose

# Check if the service is running
sudo systemctl status gunicorn    # for Django/Flask
sudo systemctl status pm2         # for Node.js
sudo systemctl status php8.3-fpm  # for PHP
sudo systemctl status puma        # for Rails

# Check recent logs for crash details
sudo journalctl -u your-service-name --since "10 minutes ago"

How to Fix It

If the service crashed due to an out-of-memory error (very common on small VPS instances), you’ll see something like this in the logs:

Jan 15 14:23:01 server kernel: Out of memory: Killed process 12345 (node) total-vm:2048000kB

The immediate fix is to restart the service:

sudo systemctl restart your-service

But the real fix is to address why it crashed:

  • Add swap space if you’re on a memory-constrained server
  • Optimize your app to use less memory
  • Upgrade your server if traffic has outgrown your resources

Here’s how to add a 2GB swap file as a safety net:

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make it permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

2. Wrong Port or Socket Configuration

The second most common cause: Nginx is trying to connect to a port or socket that your backend isn’t actually listening on.

How to Diagnose

Check what your backend is actually listening on:

sudo ss -tlnp | grep -E 'node|python|php|ruby|gunicorn'

Output might look like:

LISTEN  0  511  127.0.0.1:3000  0.0.0.0:*  users:(("node",pid=1234,fd=20))

Now check what Nginx thinks the backend is on. Open your site configuration:

sudo cat /etc/nginx/sites-available/your-site.conf

Look for the proxy_pass directive:

location / {
    proxy_pass http://127.0.0.1:3000;  # Does this match?
}

How to Fix It

Make sure the port in proxy_pass matches the actual port your application is using. If your Node.js app listens on port 3000, but you configured Nginx to point at 8080, you’ll get a 502 every time.

For PHP-FPM, the mismatch often happens between a TCP socket and a Unix socket. Check your Nginx config:

# If using a Unix socket (default on most setups):
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;

# If using a TCP socket:
fastcgi_pass 127.0.0.1:9000;

Then verify that PHP-FPM is configured to match. Open www.conf or pool.d/www.conf:

sudo grep -n "listen =" /etc/php/8.3/fpm/pool.d/www.conf

If it says listen = /run/php/php8.3-fpm.sock but Nginx is configured for 127.0.0.1:9000, they won’t find each other. Align both configurations and restart:

sudo systemctl restart php8.3-fpm
sudo systemctl restart nginx

3. Permission Denied on Unix Sockets

This one is sneaky. The backend is running, the path is correct, but Nginx still can’t connect. The error log will show something like:

*1 connect() to unix:/var/run/php/php8.3-fpm.sock failed (13: Permission denied)

This means Nginx doesn’t have permission to access the socket file.

How to Fix It

Check the socket file’s permissions and ownership:

ls -la /var/run/php/php8.3-fpm.sock

You might see:

srw-rw---- 1 www-data www-data 0 Jan 15 14:00 /var/run/php/php8.3-fpm.sock

Now check what user Nginx runs as:

ps aux | grep nginx

If Nginx runs as nginx but the socket belongs to www-data, you have a mismatch. Fix it by aligning the users in both configurations.

In /etc/php/8.3/fpm/pool.d/www.conf:

listen.owner = www-data
listen.group = www-data
listen.mode = 0660

In /etc/nginx/nginx.conf:

user www-data;

Then restart both services:

sudo systemctl restart php8.3-fpm
sudo systemctl restart nginx

Intermediate Causes (When the Basics Don’t Work)

4. Firewall or SELinux Blocking Local Connections

Sometimes the backend and Nginx are on different servers, or your firewall is being overzealous about local traffic.

How to Diagnose

Test connectivity directly:

# For TCP connections
curl -v http://your-backend-ip:3000/

# For local connections
telnet 127.0.0.1 3000

If you get Connection refused but the service is running, check your firewall:

# UFW (Ubuntu/Debian)
sudo ufw status

# firewalld (RHEL/CentOS/Fedora)
sudo firewall-cmd --list-all

# iptables
sudo iptables -L -n

How to Fix It

Allow traffic between Nginx and the backend. If they’re on the same server:

sudo ufw allow from 127.0.0.1 to any port 3000

If the backend is on a different server, allow traffic from the Nginx server’s IP:

sudo ufw allow from nginx-server-ip to any port 3000

SELinux on CentOS/RHEL/Fedora

On RHEL-based systems, SELinux is a common culprit. If your error log shows Permission denied but file permissions look correct, SELinux might be blocking Nginx from making network connections.

Check if SELinux is the issue:

sudo getsebool -a | grep httpd
sudo getsebool httpd_can_network_connect

If it returns off, enable it:

sudo setsebool -P httpd_can_network_connect 1

The -P flag makes the change persist across reboots.

5. Upstream Timeouts on Slow Endpoints

If your 502 errors are intermittent — especially on endpoints that process large files, run complex queries, or call external APIs — the issue might be timeouts, not crashes.

The error log will show:

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

By default, Nginx waits 60 seconds for a response. If your backend takes longer, Nginx gives up and returns a 502 or 504.

How to Fix It

Increase the timeout values in your Nginx configuration:

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

    proxy_connect_timeout 60s;
    proxy_send_timeout 120s;
    proxy_read_timeout 120s;

    # If you have legitimately long-running requests (exports, reports, etc.)
    # you can go higher, but be careful about resource exhaustion
}

For PHP-FPM specifically, also check max_execution_time in php.ini:

max_execution_time = 120

And the request_terminate_timeout in PHP-FPM’s config:

request_terminate_timeout = 120

All three values should be aligned. If PHP-FPM kills the process at 30 seconds but Nginx waits 120 seconds, you’ll still get errors.

Personal note: I once spent two days debugging intermittent 502 errors on a report-generation endpoint. Turns out a database query was taking 90 seconds due to a missing index. The fix wasn’t in Nginx at all — it was CREATE INDEX. Always check your backend’s performance before cranking up timeouts.

6. Buffer Size Exceeding Limits

If your backend sends large headers or responses, Nginx’s default buffer sizes might be too small. The error log typically shows:

upstream sent too big header while reading response header from upstream

How to Fix It

Increase the buffer sizes:

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

    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;

    # For FastCGI (PHP-FPM)
    fastcgi_buffer_size 128k;
    fastcgi_buffers 4 256k;
    fastcgi_busy_buffers_size 256k;
}

Test your configuration before reloading:

sudo nginx -t
sudo systemctl reload nginx

Edge Cases (The Hard Ones)

7. DNS Resolution Failures

If you’re using a domain name in proxy_pass instead of an IP address, Nginx resolves it at startup. If the DNS entry changes later (common with dynamic cloud infrastructure), Nginx keeps using the old IP and you’ll get 502 errors.

How to Fix It

Use the resolver directive to enable runtime DNS resolution:

location / {
    resolver 8.8.8.8 8.8.4.4 valid=30s;
    resolver_timeout 5s;

    # Use a variable to force runtime resolution
    set $backend "http://my-backend.example.com";
    proxy_pass $backend;
}

The valid=30s parameter tells Nginx to re-resolve the DNS every 30 seconds.

8. SSL/TLS Issues Between Nginx and Backend

If your backend uses HTTPS and there’s a certificate mismatch, protocol mismatch, or the backend’s certificate isn’t trusted, Nginx will return a 502.

The error log might show:

SSL: certificate subject name 'old-domain.com' does not match target host name 'new-domain.com'

How to Fix It

For internal backends with self-signed certificates, you can disable verification (only do this for trusted internal networks):

location / {
    proxy_pass https://backend.internal;
    proxy_ssl_verify off;
    proxy_ssl_server_name on;
}

For production, the proper fix is to make sure the backend’s SSL certificate is valid and matches the hostname Nginx is connecting to.

9. IPv6/IPv4 Mismatches

This is a surprisingly common issue. If your backend listens on IPv4 only (127.0.0.1) but Nginx resolves localhost to ::1 (IPv6), the connection will fail.

How to Fix It

Always use explicit IP addresses in proxy_pass for local backends:

# Good - explicit IPv4
proxy_pass http://127.0.0.1:3000;

# Risky - might resolve to IPv6
proxy_pass http://localhost:3000;

Or ensure your backend listens on both protocols.

10. Worker Process Exhaustion

If your backend uses a process manager (like Gunicorn’s worker processes or PHP-FPM’s children), all workers might be busy. New requests queue up, time out, and Nginx returns a 502.

How to Diagnose

Check the backend’s process count:

# For Gunicorn/Python
ps aux | grep gunicorn | wc -l

# For PHP-FPM
ps aux | grep php-fpm | wc -l

Compare against the configured maximum in your backend’s config.

How to Fix It

Increase the number of workers — but be careful not to exceed your server’s memory capacity:

For PHP-FPM (/etc/php/8.3/fpm/pool.d/www.conf):

; Dynamic process management
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500

The pm.max_requests = 500 setting is especially important — it restarts workers periodically to prevent memory leaks from accumulating.

To calculate the right max_children value:

  1. Find your average PHP-FPM process memory usage:
ps -eo pid,rss,cmd | grep php-fpm | awk '{sum+=$2} END {print sum/NR/1024 " MB per process"}'
  1. Divide your available RAM by that number. If you have 2GB free and each process uses ~50MB, max_children = 40 is your ceiling.

How to Create a Custom 502 Error Page

While you’re fixing the underlying issue, you can at least give your users a better experience than Nginx’s default error page.

“`nginx
server

Leave a Reply

Your email address will not be published. Required fields are marked *