A WordPress 502 Bad Gateway error means NGINX received the request, forwarded it to PHP-FPM for processing, and got no valid response back. The fix is almost always one of seven things: PHP-FPM is stopped, the socket path is mismatched, the FPM worker pool is exhausted, NGINX's FastCGI timeout is too short, the Linux OOM killer terminated the process, MySQL is hanging PHP workers, or a firewall (including fail2ban) is blocking loopback traffic. This guide walks through diagnosing each cause systematically, with real commands and DirectAdmin-specific paths, so you are not guessing.

Before You Start: Read the Logs First

Do not touch configuration files before you know what the error actually is. The logs will tell you exactly which category of 502 you are dealing with, and skipping this step wastes time. Run these three commands in sequence and read the output carefully before proceeding to any fix section.

# NGINX error log — tells you what NGINX saw when the request failed
tail -50 /var/log/nginx/error.log

# PHP-FPM log — tells you if FPM is crashing, running out of workers, or refusing connections
tail -50 /var/log/php8.5-fpm.log

# Kernel ring buffer — tells you if the OOM killer fired and terminated a process
dmesg | grep -iE "oom|killed process" | tail -20

The NGINX error log is the most important. A line containing connect() to unix:/var/run/php/php8.5-fpm.sock failed (2: No such file or directory) points to Fix 1 or Fix 2. A line containing connect() failed (111: Connection refused) points to Fix 1. A line containing upstream timed out (110: Connection timed out) points to Fix 4. A line with no upstream error at all, combined with a kernel OOM entry, points to Fix 5. Match the error pattern to the fix.

Fix 1: PHP-FPM Is Stopped

This is the most common cause. PHP-FPM crashes silently after a memory exhaustion event, a failed update, or a pool configuration error, while NGINX stays running. DirectAdmin's automatic service monitoring watches NGINX but does not always restart PHP-FPM alongside it.

systemctl status php8.5-fpm

# If inactive or failed:
systemctl start php8.5-fpm

# Check why it failed to start (read the full output):
journalctl -xeu php8.5-fpm --no-pager | tail -30

If it starts cleanly and the 502 disappears, that is not the end of the investigation. Something stopped it. Check the PHP-FPM log and the kernel OOM log (Fix 5) to find the root cause, or it will crash again within hours. If systemctl reports a syntax error in the pool configuration, find and fix the offending directive before restarting.

Fix 2: Socket Path Mismatch Between NGINX and PHP-FPM

This is the second most common cause and is almost always triggered by a PHP version upgrade through DirectAdmin's CustomBuild. When CustomBuild upgrades PHP (for example from 8.4 to 8.5), it creates a new socket file at a new path but does not automatically update every NGINX virtual host configuration that references the old path. The socket file referenced in the NGINX config no longer exists, producing the "No such file or directory" error in the NGINX error log.

First, list what socket files actually exist on the server right now.

ls -la /var/run/php/

Then find what your NGINX virtual host configuration for the affected domain is actually requesting.

# DirectAdmin stores virtual host configs here:
grep -r "fastcgi_pass" /etc/nginx/conf.d/virtual/yourdomain.conf

# Or search across all virtual hosts at once:
grep -r "fastcgi_pass" /etc/nginx/conf.d/virtual/

The value after fastcgi_pass unix: must exactly match a socket file that exists in /var/run/php/. If they differ, edit the virtual host configuration to reference the correct socket path, then test and reload NGINX.

nginx -t && systemctl reload nginx

In DirectAdmin, the per-user NGINX configuration is typically located at /etc/nginx/conf.d/virtual/username.yourdomain.conf. Changes made here survive CustomBuild rebuilds if they are inside the correct user-controlled configuration file. Changes made to the global NGINX configuration will be overwritten on the next CustomBuild run.

Fix 3: PHP-FPM Worker Pool Exhausted (pm.max_children)

PHP-FPM can be running with the correct socket and still produce 502 errors under traffic if every available worker process is busy. When all pm.max_children slots are occupied and a new request arrives, PHP-FPM cannot accept the connection. NGINX waits until the fastcgi_read_timeout expires and then returns a 502. This produces intermittent errors that worsen as traffic grows.

The FPM log will show the explicit warning when this is the case.

grep "max_children" /var/log/php8.5-fpm.log

If you see entries like server reached pm.max_children setting (X), consider raising it, the pool is undersized. Open the pool configuration and recalculate the limit based on actual RAM. Find the average memory size of your current PHP worker processes first.

ps --no-headers -o "rss,cmd" -C php-fpm8.5 | awk '{ sum+=$1 } END { printf ("%d%sn", sum/NR/1024,"M") }'

If the output is 45MB per process and you have 1.8GB of RAM available for PHP after accounting for NGINX, MySQL, and OS overhead, your safe maximum is 40 children (1800 / 45 = 40). Open /etc/php/8.5/fpm/pool.d/www.conf (or the per-user pool file in DirectAdmin's setup) and adjust accordingly. Also switch the process manager from ondemand to dynamic to keep baseline workers pre-warmed and eliminate cold-start latency.

pm = dynamic
pm.max_children = 40
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500
systemctl reload php8.5-fpm

Fix 4: NGINX FastCGI Timeout Too Short

Heavy WordPress operations — large WooCommerce product imports, Elementor page renders with dozens of widgets, or admin screens running complex database queries — can exceed NGINX's default 60-second FastCGI timeout. NGINX abandons the connection and returns a 502 even though PHP-FPM is still processing the request normally in the background.

The NGINX error log will show upstream timed out (110: Connection timed out) while reading response header from upstream if this is the cause. In DirectAdmin, add timeout overrides to the domain's custom NGINX configuration block, not to the global NGINX config. The correct location for user-level customizations that survive a CustomBuild rebuild is in the per-user virtual host file.

fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;

This is a mitigation, not a fix. A PHP process taking 120 seconds to generate a page has a problem — a slow database query, a blocking external API call, or a poorly coded plugin. After increasing the timeout to stop the 502s, install the Query Monitor plugin and profile the slow request to find the actual bottleneck. Increasing timeouts without investigating the root cause will surface again as a database connection problem during peak traffic.

Fix 5: Linux OOM Killer Terminated PHP-FPM

If PHP-FPM is found stopped with no errors in its own log, and restarting it temporarily resolves the 502 before it crashes again, the Linux OOM killer is the likely culprit. The kernel OOM killer is a last-resort mechanism that terminates the most memory-hungry process to prevent total system failure. It writes its kills to the kernel log, not to the PHP-FPM log, which is why it is so frequently missed.

dmesg | grep -iE "oom|killed process" | tail -20
# Or via journalctl:
journalctl -k | grep -iE "oom|killed"

If you find entries like Out of memory: Kill process [PID] (php-fpm8.5) score X or sacrifice child, the RAM on your VPS is genuinely insufficient for the current pm.max_children setting. Restarting PHP-FPM without reducing memory consumption will result in the same crash during the next traffic spike. Your options are: reduce pm.max_children to a value your RAM can actually sustain (calculate it properly per Fix 3), identify and remove memory-leaking plugins that inflate worker process size, or upgrade to a VPS plan with more RAM. A ct.Ready plan with 2GB of RAM handles a properly configured WordPress stack comfortably; a ct.Entry plan with 512MB is tight and requires extremely lean configuration.

Fix 6: MySQL Is Hanging PHP-FPM Workers

This is the most underdiagnosed cause of 502 errors and is rarely covered in general WordPress guides. When MySQL is overloaded, locked, or running an extremely slow query, every PHP-FPM worker that makes a database call stalls while waiting for a response. Because PHP workers are blocked — not crashed — they appear as active processes, and pm.max_children fills up instantly. New requests queue and then time out, producing a 502. MySQL itself does not throw an error visible to WordPress until the connection timeout expires.

Check if MySQL is running and identify any long-running or locked queries.

systemctl status mysql

# Connect and check active process list:
mysql -u root -p -e "SHOW FULL PROCESSLIST;"

# Look for queries with a long Time value or State showing "Waiting for table lock"
# Kill a blocking query by its process ID (replace ID with the actual number):
mysql -u root -p -e "KILL QUERY ID;"

If MySQL is running but the processlist shows dozens of queries piling up, the issue is likely a missing database index, a runaway WP-Cron job triggering mass simultaneous queries, or a WordPress plugin performing a full table scan on every page load. The WordPress critical error recovery guide covers disabling WP-Cron and isolating plugin conflicts at the database level.

Fix 7: Fail2Ban Blocking Loopback or Cloudflare IP Ranges

This is a specific and frustrating cause that presents as a completely normal 502 from the visitor's perspective but leaves no obvious PHP-FPM error in the logs. Fail2ban, which is a legitimate and important security tool, can be misconfigured to accidentally ban 127.0.0.1 (the loopback address) or the IP ranges used by Cloudflare as a proxy. When NGINX attempts to forward a request to PHP-FPM over the loopback interface, it is blocked at the firewall level and returns a connection refused error.

# Check if the loopback address or Cloudflare IPs are currently banned:
fail2ban-client status
fail2ban-client status nginx-http-auth

# Check the active iptables rules for any DROP on loopback:
iptables -L -n | grep "127.0.0.1"

# If 127.0.0.1 is banned, unban it immediately:
fail2ban-client set JAILNAME unbanip 127.0.0.1

To prevent this from recurring, add the loopback address and Cloudflare's published IP ranges to fail2ban's ignoreip whitelist in /etc/fail2ban/jail.local. Cloudflare publishes its current IP ranges at https://www.cloudflare.com/ips/. If your site runs behind Cloudflare and you are seeing 502 errors only from certain geographic regions, a Cloudflare-side routing issue or an outdated origin IP in Cloudflare's DNS configuration is worth checking before digging deeper into the server stack.

Bonus: Xdebug Left Enabled in Production

Xdebug is a PHP debugging extension that should never be loaded on a production server. When active, it adds 30 to 60MB of overhead per PHP worker and significantly slows execution. On a VPS where 40 normal workers fit in RAM, Xdebug reduces capacity to 15 workers before exhausting memory, directly causing the pool exhaustion 502 described in Fix 3. This is particularly common after a developer tests locally, exports a configuration, and it lands on production with the Xdebug ini still enabled.

# Check if Xdebug is active:
php -m | grep -i xdebug

# Find the configuration file enabling it:
find /etc/php/8.5/ -name "*.ini" | xargs grep -l "xdebug"

# Disable it by commenting out the extension line:
# Change: zend_extension=xdebug.so
# To:    ;zend_extension=xdebug.so

systemctl reload php8.5-fpm

Rapid-Fire Diagnostic Checklist

If you are mid-incident and need a structured sequence to follow, use this order. Each step eliminates a category of causes before moving to the next.

  1. Read /var/log/nginx/error.log — identify the exact upstream error string before touching anything.
  2. Run systemctl status php8.5-fpm — is the service running? If not, start it and check journalctl -xeu php8.5-fpm for the reason it stopped.
  3. Run ls /var/run/php/ and compare to grep fastcgi_pass /etc/nginx/conf.d/virtual/yourdomain.conf — do they match exactly?
  4. Run dmesg | grep -i oom — was the process killed by the kernel?
  5. Run grep "max_children" /var/log/php8.5-fpm.log — is the worker pool hitting its ceiling?
  6. Run systemctl status mysql and SHOW FULL PROCESSLIST — is MySQL blocked or overloaded?
  7. Run iptables -L -n | grep 127.0.0.1 — is fail2ban blocking loopback traffic?
  8. Run php -m | grep xdebug — is a debugging extension inflating memory consumption?

If you work through this entire checklist and the 502 persists, the issue is almost certainly environmental: a corrupted PHP binary after a botched update, a full disk preventing socket file creation, or a hardware-level problem on the underlying node. Check disk space with df -h and inode usage with df -i before escalating. If the server is managed through our web hosting plans, open a support ticket with the output of the NGINX error log attached — a 502 on managed infrastructure means something on the server side needs investigation at the hypervisor level.

Source & Attribution

This article is based on original data belonging to serverspan.com blog. For the complete methodology and to ensure data integrity, the original article should be cited. The canonical source is available at: WordPress 502 Bad Gateway in 2026: The Complete Fixing and Debugging Guide (DirectAdmin + VPS).