502 / 504 upstream failures

502 Bad Gateway and 504 Gateway Timeout are reported by Nginx but are almost always caused downstream — PHP-FPM, MySQL, Valkey/Redis, or Drupal itself. Read the meaning first, then walk the FPM/pool/timeout chain.

Error What Nginx saw
502 Bad Gateway FPM accepted the request, then dropped the connection abnormally
504 Gateway Timeout FPM did not respond within fastcgi_read_timeout (_PHP_FPM_TIMEOUT, effective 180 s)

The Nginx-side internals (vhost templates, the FPM socket plumbing) are in Nginx debug; this page is the downstream-cause procedure.

Is FPM running?

# Every installed PHP version
for v in 5.6 7.1 7.4 8.1 8.3 8.4; do
  vv=${v//./}
  printf 'PHP-FPM %s: ' "$v"
  service "php${vv}-fpm" status 2>/dev/null | head -1
done

Restart any that are down:

service php84-fpm restart

Read the logs

Nginx error log

tail -100 /var/log/nginx/error.log
Log pattern Cause
connect() to unix:/run/*.fpm.socket failed FPM pool not running (sockets live under /run/, named <user_socket>.fpm.socket)
upstream prematurely closed connection PHP segfaulted or hit OOM
upstream timed out (110: Connection timed out) 504 — request too slow

PHP-FPM error log

BOA writes one error log per version to a single /var/log/php directory, named phpNN-fpm-error.log — there is no per-version directory and no php-fpm.log:

tail -100 /var/log/php/php84-fpm-error.log
Log pattern Cause
child <pid> exited on signal 11 (SIGSEGV) PHP segfault — usually APCu/opcache; see Recovery & cache faults
executing too slow Slow query / lock — fix Drupal side
server reached max_children setting Pool hit its pm.max_children ceiling — size via the instance plan (below)

Common 502 causes by frequency

Cause Fix
APCu / Valkey cache poisoning drush cr; see Recovery & cache faults
FPM pool not running service phpNN-fpm start
Long DB query hit _PHP_FPM_TIMEOUT (504) Optimise the query; raise timeout temporarily
Memory limit exceeded Raise PHP memory_limit; investigate the leak
Valkey/Redis down Restart the service
Disk full Free disk
max_children exceeded First decide organic vs abuse-driven (below); if organic: dedicated plans auto-size, shared instances raise the plan or pin _PHP_FPM_MAX_CHILDREN_FORCE
Loadguard tripped (high CPU) Check /var/log/boa/high.load.incident.log; wait for load to drop

Why one busy site 502s its neighbours

All sites under one Octopus instance share a single PHP-FPM pool per PHP version. When that pool hits pm.max_children, every site on the instance returns 502 — even idle ones — because they all wait on the same saturated pool. Dedicated plans (PHANTOM and above) size the pool from box capacity to absorb bursts; shared plans are capped on purpose. fpmreport shows each pool's configured ceiling next to its observed peak, so you can see how close a pool runs to its cap. See PHP-FPM capacity for the plan-based sizing model.

Before raising pm.max_children or the plan, decide whether the saturation is organic capacity or an attack: raising the ceiling absorbs organic growth, but against abuse-driven saturation it is not the cure — it only moves the collapse point. Three signals (all in 5.10.3) answer the question:

  • Capacity NOTE from the minutely monitor. php.sh logs NOTE: PHP-FPM reached max_children setting (N new hit(s) in <files>). Consider raising pm.max_children for the affected pool(s). It byte-offset-tails /var/log/php/php*-fpm-error.log, so only new per-pool exhaustion hits since the last run are counted. (Before 5.10.3 this NOTE was dead: it grepped for the global process.max ceiling, which BOA sets to 0 and which therefore never logs.)
  • FPM-saturation alert + forensic snapshot. Independently, the scan_nginx scoring engine watches the same reached max_children setting signal (_NGINX_FPM_SAT_DETECT=YES by default) and, on a hit, alerts and snapshots the box-wide top talkers — the who/what behind the exhaustion.
  • 444s in the access log. If the saturation coincides with a flood of uncached localized pages (/<lang>/... requests), the Tier-A request guard caps anonymous localized concurrency at 24 in-flight per vhost (limit_conn boa_i18n_anon, tunable via the provision option nginx_i18n_anon_conn; logged-in users are never capped) and sheds the excess with 444 — so a flood shows up as 444s in the access log, not only as 502s. Run floodreport to answer "was that an i18n flood, and did the guardrail hold?" from /var/xdrago/monitor/log/i18n_flood.log and the snapshots under /var/xdrago/monitor/log/i18n_flood/.

502 on /install.php after Clone-then-Migrate

curl https://foo.example.com/  →  redirect to /install.php  →  /install.php returns 404

Cause: the site's sites/<domain>/settings.php is missing on the new platform after the migrate. Fix: Verify the site; if Verify also fails, read /data/disk/<USER>/log/<site>.log; last resort, restore from a known-good backup.

504 on long admin operations

Cause: an admin task runs longer than _PHP_FPM_TIMEOUT (effective 180 s; AUTO by default, clamped 60–180 s). Examples: config-import, large feature revert, mass content migration.

  • Raise the timeout in /opt/etc/fpm/fpm-pool-common*.conf (Operator FAQ). Survives until the next barracuda upgrade.
  • Run the operation via Drush — CLI has no FPM timeout.

Is it actually a firewall block?

If one IP gets 502 but others get 200, the IP may be blocked at the network layer (a different failure mode that looks similar):

curl -I https://foo.example.com/      # from a different IP

If the other IP gets 200 and yours gets 502, it is a firewall block — see IP-blocked recovery.

Tenant note. A .dev. cache-bypass alias (dev.<domain>) exposes in-browser errors, but it is not auto-present on every site — the operator or tenant must add the dev.<domain> alias to the site first. Hitting dev.foo.example.com on a site without that alias yields no vhost / NXDOMAIN.

Related