DEV Community

Cover image for Why your PHP-FPM throws 502 under load (pm.max_children, explained)
North Dev
North Dev

Posted on

Why your PHP-FPM throws 502 under load (pm.max_children, explained)

Your PHP site runs fine, then traffic spikes and nginx starts throwing 502 Bad Gateway. Logs blame the upstream. The cause is almost always one number in your PHP-FPM pool: pm.max_children.

What actually happens

Nginx hands PHP requests to PHP-FPM. FPM has a fixed ceiling of worker processes — pm.max_children. When every worker is busy and the listen backlog fills, FPM can't accept the connection, nginx's fastcgi_pass fails, and the user gets a 502. Under pm = dynamic, you'll also see this in the FPM status page:

max children reached: 37
listen queue: 120
Enter fullscreen mode Exit fullscreen mode

That counter is the smoking gun: you've hit the ceiling 37 times.

How to size max_children (stop guessing)

The honest formula:

max_children = (RAM budget for FPM) / (average worker RSS)
Enter fullscreen mode Exit fullscreen mode
  • Measure a real worker's resident memory (RSS) under your actual app, not memory_limit (that's a cap, not usage).
  • Pick a RAM budget — total RAM minus what the DB, cache, and OS need. Leave headroom.
  • Divide. That's your ceiling. Set pm.max_children to it, not to a number you copied from a blog.

Too high and a traffic spike spawns more workers than RAM allows → the OOM killer starts reaping processes (worse than a 502). Too low and you 502 under load you could have served.

The shared-pool 502 storm

The nastiest version: multiple sites share one FPM master and one max_children. One busy site starves the others, and they all 502 — even the idle ones. Fix: give each site its own pool with its own ceiling, sized from its own RSS.

Also worth setting

  • pm.max_requests (recycle workers to bound memory leaks).
  • Watch slow requests in the status page and the slowlog — a few slow endpoints hold workers hostage and fake a capacity problem.

I turned this into a read-only CLI, fpm-doctor — it reads your pool configs + live status, measures real worker RSS, and recommends max_children from your actual memory budget, flags the shared-pool pattern, and ranks your slowlog. Composer + .phar, never writes your config.

Top comments (0)