PHP-FPM Capacity Planning: Sizing Workers for Throughput
Learn to size pm.max_children and related PHP-FPM settings from real memory and traffic numbers, and to detect slow requests before they exhaust your worker pool.
This lesson assumes familiarity with how PHP-FPM's process models, pm settings, and worker lifecycle work — that ground-up explanation lives in the PHP-FPM Internals lesson. Here, the focus is narrower and more practical: given a server with a fixed amount of RAM and a PHP application that uses a certain amount of memory per request, how many workers can you actually run, and how do you know when requests are slow enough to threaten that capacity?
Sizing pm.max_children from memory, not guesswork
The single most common PHP-FPM misconfiguration is setting pm.max_children to an arbitrary round number (50, 100) without reference to how much memory each worker actually consumes. Every FPM worker is a full PHP process; if the pool's total memory footprint exceeds available RAM, the OS starts swapping or the OOM killer starts terminating workers — both catastrophic for latency and availability.
The correct approach is arithmetic: measure the resident memory of a single worker under realistic load (not an idle worker — memory use rises once autoloaders, caches, and per-request buffers are warmed up), then divide the RAM you're willing to dedicate to PHP-FPM by that number, leaving headroom for the OS, the web server, the database if co-located, and other processes.
• Measure real per-worker memory: ps -o rss -p <worker-pid> after the pool has served traffic for a few minutes, or average across several workers since memory can vary by endpoint. • Reserve headroom: don't plan for 100% of RAM going to FPM workers — leave room for the kernel, Nginx/Apache, monitoring agents, and traffic spikes. • pm.max_children = (RAM reserved for PHP-FPM) / (realistic RSS per worker). If reserved RAM is 6GB and a worker uses ~60MB under load, that's roughly 100 workers — not a round number picked by feel. • Re-measure after any dependency upgrade or new feature that changes memory footprint (a new library, a larger in-memory cache, an ORM change) — capacity planning is not a one-time exercise.
Slow requests and recycling as capacity risks
Worker count sizing only holds if requests finish quickly. A pool sized for a 50ms average response time can be brought to its knees by a handful of requests that hang for 30 seconds — each one occupies a worker slot for the entire duration, and once all workers are busy, new requests queue (and eventually time out) even though the server may have plenty of idle CPU and memory.
pm.max_requests (recycling a worker after N requests to bound memory growth from leaks) and slow-request logging are your two main levers here. Configure request_slowlog_timeout with a slowlog path so FPM logs a full backtrace of any request exceeding your threshold — this turns 'the site is randomly slow sometimes' into a specific stack trace pointing at the offending code path, without needing a full profiler session to catch it in the act.
For capacity planning purposes, treat 'p99 request duration' as important as 'average request duration': a small tail of slow requests disproportionately eats into your effective worker pool during traffic spikes, which is exactly when you can least afford it.
Code Examples
pm.max_children here is not an arbitrary number — it should be the output of the RAM-divided-by-per-worker-RSS calculation. The slowlog settings mean that instead of discovering slow requests only when the whole pool is saturated, you get a backtrace the moment any single request crosses the 2-second threshold, letting you fix the cause before it becomes a capacity incident. Expected result: # Example slowlog entry after a request exceeds 2s: # [24-Aug-2026 14:02:11] [pool www] pid 18422 # script_filename = /var/www/app/public/index.php # [0x00007f2a1c000000] curl_exec() /var/www/app/src/Http/ShippingClient.php:42 request_slowlog_timeout = 2s
; www.conf (pool definition)
[www]
pm = dynamic
pm.max_children = 100 ; derived from (RAM budget) / (RSS per worker), not guessed
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 500 ; recycle workers periodically to bound memory growth
; Anything over 2 seconds gets a full backtrace logged for later analysis.
request_slowlog_timeout = 2s
slowlog = /var/log/php-fpm/www-slow.logCross-reference
The process models (static/dynamic/ondemand), the full set of pm.* settings, and how the FPM master and worker lifecycle actually operates are covered in depth in the PHP-FPM Internals lesson. This lesson focused specifically on the capacity-planning question — how many workers can this server safely run, and how do you catch the slow requests that threaten that capacity — rather than re-explaining the process model itself.
pm.max_children = 200; "seemed like a safe round number" on a 4GB server; Measure real RSS per worker (e.g. 80MB under load), reserve RAM for OS/webserver/db,; then compute: (4096MB - 1024MB reserved) / 80MB per worker ≈ 38 pm.max_children = 38
An arbitrarily large pm.max_children lets the pool spawn more workers than the server has memory for. Under a traffic spike, the OS starts swapping or the OOM killer starts killing PHP-FPM workers mid-request — a self-inflicted outage that correct arithmetic-based sizing prevents.
What is the recommended way to determine pm.max_children for a production pool? A. Pick a large round number so the pool never runs out of workers B. Divide the RAM budgeted for PHP-FPM by the realistic per-worker memory usage measured under load C. Set it equal to the number of CPU cores on the server D. Leave it at the package's default value for all environments
B. Divide the RAM budgeted for PHP-FPM by the realistic per-worker memory usage measured under load — pm.max_children should be derived from actual measured memory usage per worker under realistic load, divided into the RAM you can safely dedicate to the pool (with headroom reserved) — not guessed or left at a default.
[true-false] A small number of unusually slow requests can meaningfully reduce a PHP-FPM pool's effective capacity, even if the server has spare CPU and memory. A. True B. False
A. True — True — each slow request occupies a worker slot for its entire duration. If enough workers are tied up in slow requests, new requests queue and time out regardless of available CPU or memory, which is why tracking p99 duration and slow-request logs matters for capacity planning.