← Back to Blog

Fix Nginx 502 Bad Gateway with PHP-FPM: Complete Troubleshooting Guide

Quick Fix for Panicking Engineers

Run these diagnostic commands in order. The first one that shows an error is your cause:

# 1. Is PHP-FPM even running?
sudo systemctl status php8.2-fpm
# If "inactive" or "failed": sudo systemctl start php8.2-fpm

# 2. Can Nginx reach the socket?
sudo ls -la /run/php/php8.2-fpm.sock
# If "No such file": FPM is not running or using a different socket path

# 3. Are all workers busy?
sudo grep 'max_children' /var/log/php8.2-fpm.log | tail -5
# If you see "server reached pm.max_children": increase the value

# 4. What does the Nginx error log say?
sudo tail -20 /var/log/nginx/error.log | grep 502

The Nginx 502 Bad Gateway error with PHP-FPM has exactly 7 root causes. Not approximately. Not "several." Seven. Each produces a distinct log pattern in either the Nginx error log, the PHP-FPM error log, or both. This guide documents every pattern, the exact cause, and the exact fix. Stop guessing, start reading the logs.

Cause 1: PHP-FPM Is Not Running

Nginx Error Log Pattern

2026/04/02 14:23:17 [error] 1234#1234: *5678 connect() to unix:/run/php/php8.2-fpm.sock
  failed (2: No such file or directory) while connecting to upstream,
  client: 10.0.0.1, server: yourdomain.com, request: "GET / HTTP/2.0",
  upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:", host: "yourdomain.com"

Or This Variant (TCP)

2026/04/02 14:23:17 [error] 1234#1234: *5678 connect() failed (111: Connection refused)
  while connecting to upstream, client: 10.0.0.1, server: yourdomain.com,
  upstream: "fastcgi://127.0.0.1:9000", host: "yourdomain.com"

Diagnosis

# Check FPM status
sudo systemctl status php8.2-fpm

# If it failed to start, check why
sudo journalctl -u php8.2-fpm --no-pager -n 50

# Common startup failures:
# - "ERROR: unable to bind listening socket for address '/run/php/php8.2-fpm.sock'"
#   -> Another process is using the socket, or stale socket file exists
# - "ERROR: [pool www] cannot get uid for user 'www-data'"
#   -> Wrong user configured in pool config
# - PHP Fatal error in php.ini or a loaded extension

Fix

# Remove stale socket if it exists
sudo rm -f /run/php/php8.2-fpm.sock

# Start FPM
sudo systemctl start php8.2-fpm

# Enable it to start on boot
sudo systemctl enable php8.2-fpm

# Verify the socket exists
ls -la /run/php/php8.2-fpm.sock

Cause 2: Socket Permission Denied

Nginx Error Log Pattern

2026/04/02 14:23:17 [crit] 1234#1234: *5678 connect() to unix:/run/php/php8.2-fpm.sock
  failed (13: Permission denied) while connecting to upstream,
  client: 10.0.0.1, server: yourdomain.com

Diagnosis

# Check socket ownership and permissions
ls -la /run/php/php8.2-fpm.sock
# Should output: srw-rw---- 1 www-data www-data ... /run/php/php8.2-fpm.sock

# Check which user Nginx runs as
ps aux | grep 'nginx: worker' | grep -v grep | awk '{print $1}'
# Usually: www-data or nginx

# The Nginx worker user must match the socket owner/group
# Or the socket mode must allow access (0660 = owner+group, 0666 = everyone)

Fix

# Edit the FPM pool config
sudo nano /etc/php/8.2/fpm/pool.d/www.conf

# Set these values (assuming Nginx runs as www-data):
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

# If Nginx runs as 'nginx' user:
listen.owner = nginx
listen.group = nginx
listen.mode = 0660

# Restart FPM (reload is not enough for socket permission changes)
sudo systemctl restart php8.2-fpm

# Verify
ls -la /run/php/php8.2-fpm.sock
# Output should show the correct owner and mode

Cause 3: Wrong Socket Path in Nginx Config

Nginx Error Log Pattern

# Same as Cause 1: "No such file or directory"
# But FPM IS running, just on a different socket

Diagnosis

# Find where FPM is actually listening
sudo grep -r 'listen' /etc/php/8.2/fpm/pool.d/ | grep -v '^#'
# Output: /etc/php/8.2/fpm/pool.d/www.conf:listen = /run/php/php8.2-fpm.sock

# Find what Nginx is trying to connect to
sudo grep -r 'fastcgi_pass' /etc/nginx/ | grep -v '#'
# Output: /etc/nginx/sites-enabled/default:    fastcgi_pass unix:/var/run/php-fpm.sock;

# Mismatch! Nginx is looking for /var/run/php-fpm.sock
# FPM is listening on /run/php/php8.2-fpm.sock

Fix

# Update the Nginx config to match the actual FPM socket
# In your server block:
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

# Test config and reload
sudo nginx -t && sudo systemctl reload nginx

Cause 4: pm.max_children Exhausted

PHP-FPM Error Log Pattern

[02-Apr-2026 14:23:17] WARNING: [pool www] server reached pm.max_children setting (5),
  consider raising it
[02-Apr-2026 14:23:17] WARNING: [pool www] child 12345, script '/var/www/html/index.php'
  (request: "GET /index.php") execution timed out (30.015 sec), terminating

Nginx Error Log Pattern

2026/04/02 14:23:17 [error] 1234#1234: *5678 upstream timed out
  (110: Connection timed out) while reading response header from upstream

Diagnosis

# Check current max_children and active processes
sudo grep 'pm.max_children' /etc/php/8.2/fpm/pool.d/www.conf
# Output: pm.max_children = 5  <-- way too low

# Count current active workers
ps -C php-fpm --no-headers | wc -l

# Check the FPM status page (if enabled)
curl -s http://127.0.0.1/fpm-status | grep -E 'active|idle|total'
# active processes: 5
# idle processes: 0        <-- all workers busy, no idle = saturated
# total processes: 5

Fix: Calculate pm.max_children

# Step 1: Get average worker memory
ps --no-headers -o rss -C php-fpm | awk '{ sum+=$1; count++ } END { printf "Average: %.0f MB (%d workers)\n", sum/count/1024, count }'

# Step 2: Apply the formula
# max_children = (Total_RAM_MB - Reserved_MB) / Avg_Worker_MB
#
# Example for 16GB server:
# Total RAM:     16384 MB
# Reserved:       4096 MB (OS, MySQL, Redis, Nginx)
# Available:     12288 MB
# Avg worker:       80 MB
# max_children:    153

# Step 3: Update pool config
sudo nano /etc/php/8.2/fpm/pool.d/www.conf
# /etc/php/8.2/fpm/pool.d/www.conf
[www]
pm = dynamic
pm.max_children = 150
pm.start_servers = 30
pm.min_spare_servers = 15
pm.max_spare_servers = 45
pm.max_requests = 1000

# Rules of thumb for pm.start_servers and spare:
# start_servers = min_spare_servers + (max_spare_servers - min_spare_servers) / 2
# min_spare_servers = max_children * 0.1
# max_spare_servers = max_children * 0.3
# Restart FPM
sudo systemctl restart php8.2-fpm

Cause 5: fastcgi_read_timeout Exceeded

Nginx Error Log Pattern

2026/04/02 14:23:17 [error] 1234#1234: *5678 upstream timed out
  (110: Connection timed out) while reading response header from upstream,
  client: 10.0.0.1, server: yourdomain.com, request: "POST /api/import HTTP/2.0",
  upstream: "fastcgi://unix:/run/php/php8.2-fpm.sock:"

This is a 504, not a 502. But many engineers confuse them because the user experience is identical. If your Nginx log says "upstream timed out" with error code 110, the PHP script is running but exceeding the timeout.

Fix

# In your Nginx server block or location block
location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;

    # Timeout settings
    fastcgi_connect_timeout 60s;   # Time to establish connection to FPM
    fastcgi_send_timeout 300s;     # Time to send request to FPM
    fastcgi_read_timeout 300s;     # Time to wait for FPM response

    # Buffer settings (prevents "upstream sent too big header")
    fastcgi_buffer_size 32k;
    fastcgi_buffers 16 16k;
    fastcgi_busy_buffers_size 32k;
}

# For specific long-running endpoints, use a separate location block
location = /api/import {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    fastcgi_read_timeout 600s;  # 10 minutes for import endpoint
}

sudo nginx -t && sudo systemctl reload nginx

Cause 6: PHP Worker Killed by OOM

System Log Pattern

# dmesg or /var/log/kern.log
[423847.291] Out of memory: Killed process 28491 (php-fpm8.2)
  total-vm:2148392kB, anon-rss:1048576kB, file-rss:0kB, shmem-rss:0kB,
  UID:33 pgtables:4096kB oom_score_adj:0

PHP-FPM Error Log

[02-Apr-2026 14:23:17] WARNING: [pool www] child 28491 exited on signal 9 (SIGKILL)
  after 12.432 seconds from start
[02-Apr-2026 14:23:17] NOTICE: [pool www] child 28495 started

Diagnosis

# Check for OOM events
sudo dmesg -T | grep -i 'out of memory\|oom\|killed process' | tail -10

# Check individual worker memory usage
ps -eo pid,rss,comm --sort=-rss | grep php-fpm | head -10
# Look for workers using > 256MB (or whatever memory_limit is)

# Check PHP memory_limit
php -i | grep memory_limit

Fix

# Option A: Reduce memory_limit to prevent runaway scripts
# /etc/php/8.2/fpm/php.ini
memory_limit = 256M

# Option B: Reduce max_children so total memory fits in RAM
# If each worker can use up to 256MB and you have 16GB:
# max_children = (16384 - 4096) / 256 = 48

# Option C: Add swap as a safety net (not a solution, a safety net)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab

# Option D: Set pm.max_requests to recycle workers before they leak too much
pm.max_requests = 500

Generate Nginx Configurations

Use SecureBin's Nginx Config Generator to create properly configured server blocks with correct PHP-FPM upstream settings.

Generate Nginx Config

Cause 7: FPM Returns Invalid Response

Nginx Error Log Pattern

2026/04/02 14:23:17 [error] 1234#1234: *5678 recv() failed (104: Connection reset by peer)
  while reading response header from upstream

# Or:
2026/04/02 14:23:17 [error] 1234#1234: *5678 upstream prematurely closed connection
  while reading response header from upstream

Diagnosis

This happens when a PHP script triggers a fatal error, segfault, or calls exit() before sending any output. FPM closes the connection and Nginx has no response to forward.

# Check PHP error logs
sudo tail -50 /var/log/php8.2-fpm.log

# Enable PHP error logging if disabled
# /etc/php/8.2/fpm/php.ini
log_errors = On
error_log = /var/log/php_errors.log
display_errors = Off

# Common causes:
# - PHP Fatal error: Allowed memory size exhausted
# - PHP Fatal error: Class 'SomeClass' not found (autoloader issue)
# - Segmentation fault in a PHP extension (see our PHP-FPM SIGSEGV guide)

Fix

# If it's a memory issue:
# /etc/php/8.2/fpm/php.ini
memory_limit = 256M

# If it's an autoloader issue after a deployment:
# Clear opcache
php -r "opcache_reset();"
sudo systemctl reload php8.2-fpm

# If it's a segfault, see our dedicated guide:
# /blog/fix-php-fpm-segfault-high-traffic/

Diagnostic Flowchart

Run this single command to diagnose the cause in seconds:

#!/bin/bash
# Save as /usr/local/bin/diagnose-502
echo "=== PHP-FPM Status ==="
systemctl is-active php8.2-fpm && echo "FPM: RUNNING" || echo "FPM: STOPPED (Cause 1)"

echo ""
echo "=== Socket Check ==="
SOCK="/run/php/php8.2-fpm.sock"
if [ -S "$SOCK" ]; then
  echo "Socket exists: $SOCK"
  ls -la "$SOCK"
  NGINX_USER=$(ps aux | grep 'nginx: worker' | grep -v grep | awk '{print $1}' | head -1)
  echo "Nginx runs as: $NGINX_USER"
  SOCK_GROUP=$(stat -c '%G' "$SOCK" 2>/dev/null || stat -f '%Sg' "$SOCK")
  echo "Socket group: $SOCK_GROUP"
  id "$NGINX_USER" | grep -q "$SOCK_GROUP" && echo "Permission: OK" || echo "Permission: DENIED (Cause 2)"
else
  echo "Socket NOT FOUND: $SOCK (Cause 1 or 3)"
fi

echo ""
echo "=== FPM Pool Status ==="
ACTIVE=$(ps -C php-fpm --no-headers | wc -l)
MAX=$(grep 'pm.max_children' /etc/php/8.2/fpm/pool.d/www.conf | grep -o '[0-9]*')
echo "Active workers: $ACTIVE / $MAX max_children"
[ "$ACTIVE" -ge "$MAX" ] && echo "WARNING: max_children EXHAUSTED (Cause 4)" || echo "Workers: OK"

echo ""
echo "=== Recent Errors ==="
echo "-- Nginx errors (last 5):"
tail -5 /var/log/nginx/error.log 2>/dev/null | grep -E '502|upstream'
echo "-- FPM errors (last 5):"
tail -5 /var/log/php8.2-fpm.log 2>/dev/null | grep -E 'WARNING|ERROR|SIGSEGV'
echo "-- OOM kills:"
dmesg -T 2>/dev/null | grep -i 'killed process.*php' | tail -3

Complete Nginx + PHP-FPM Configuration

A production-ready configuration that prevents all 7 causes:

# /etc/nginx/sites-available/yourdomain.conf
upstream php-fpm {
    server unix:/run/php/php8.2-fpm.sock;
    # Keepalive connections to FPM (reduces socket overhead)
    keepalive 16;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;
    root /var/www/html/public;
    index index.php;

    # Timeouts
    client_body_timeout 60s;
    send_timeout 60s;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_pass php-fpm;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Timeouts
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 300s;
        fastcgi_read_timeout 300s;

        # Buffers
        fastcgi_buffer_size 32k;
        fastcgi_buffers 16 16k;
        fastcgi_busy_buffers_size 32k;
        fastcgi_temp_file_write_size 256k;

        # Keepalive
        fastcgi_keep_conn on;

        # Intercept FPM errors to show Nginx error pages
        fastcgi_intercept_errors on;
    }

    # Custom error page for 502
    error_page 502 /502.html;
    location = /502.html {
        root /var/www/error-pages;
        internal;
    }
}

Monitoring to Prevent Future 502s

# Enable the FPM status page
# /etc/php/8.2/fpm/pool.d/www.conf
pm.status_path = /fpm-status
ping.path = /fpm-ping
ping.response = pong

# Nginx config for status endpoint (restrict to internal)
location = /fpm-status {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    allow 127.0.0.1;
    deny all;
}

# Monitor with a cron job
*/1 * * * * curl -s http://127.0.0.1/fpm-status | grep -E 'active|idle|listen queue' >> /var/log/fpm-metrics.log

# Alert when listen queue > 0 (requests waiting for a free worker)
*/1 * * * * LQ=$(curl -s http://127.0.0.1/fpm-status | grep 'listen queue:' | awk '{print $NF}'); [ "$LQ" -gt 0 ] && echo "FPM listen queue: $LQ" | mail -s "FPM WARNING" ops@company.com

Share Debug Output Securely

Sharing Nginx error logs and FPM diagnostics with your team? Use SecureBin to send them through encrypted, self-destructing links.

Share Securely

Summary

Nginx 502 Bad Gateway with PHP-FPM has 7 causes, each with a unique log signature. Cause 1: FPM not running ("No such file or directory"). Cause 2: Socket permissions ("Permission denied"). Cause 3: Wrong socket path (FPM listens on path A, Nginx connects to path B). Cause 4: max_children exhausted ("server reached pm.max_children"). Cause 5: Timeout exceeded ("upstream timed out"). Cause 6: Worker OOM killed (check dmesg). Cause 7: PHP fatal error or segfault ("upstream prematurely closed connection"). Always check the Nginx error log and the PHP-FPM error log together. The cause is always in one of those two files.

Related Articles

Continue reading: Fix PHP-FPM SIGSEGV Crashes, Fix Incomplete SSL Certificate Chain, Fix CORS No Access-Control-Allow-Origin.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.