PHP-FPM is the traffic controller deciding how many requests your server processes simultaneously. Set it too conservatively and legitimate traffic queues up waiting for a free worker even though the server has spare capacity; set it too aggressively and a traffic spike exhausts your server's memory trying to run more PHP processes than it can actually hold, taking the whole site down instead of just slowing it.
What you'll learn in this lesson
- What a PHP-FPM pool and its worker processes actually are
- The math behind sizing
pm.max_childrencorrectly - The three process-management strategies and when each fits
The core pool settings
; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
Each PHP-FPM "child" is a full PHP process capable of handling exactly one request at a time. pm.max_children is the hard ceiling — the 51st simultaneous request queues until a worker frees up, rather than the server trying (and potentially failing) to spawn unlimited processes.
The sizing math
# Rough formula: available RAM for PHP ÷ average memory per PHP process
# Example: 4GB available, ~80MB average process (check with `ps` on a
# running site under real load, not a guess) → max_children ≈ 50
free -m
ps -ylC php-fpm --sort:rss
max_children is sized for more memory than the server actually has, a genuine traffic spike causes the OS to start killing processes (an OOM kill) or thrashing into swap — both far worse than the queuing that happens when the limit is sized correctly. "More workers" is not always faster; it's only faster up to the point your RAM actually supports.Three process-management strategies
static— always exactlypm.max_childrenprocesses running, no scaling. Predictable memory use, wastes resources during quiet periods.dynamic— scales betweenmin_spare_serversandmax_spare_serversbased on load. The usual choice for a typical Drupal site with variable traffic.ondemand— spawns workers only as requests arrive, killing idle ones after a timeout. Best for low-traffic or memory-constrained environments, at the cost of a small latency hit on a cold worker's first request.
Quick check: your server has 4GB RAM and each PHP-FPM worker uses roughly 100MB under real load. Is
pm.max_children = 100a safe setting? If you said no — that's 10GB of PHP processes alone on a 4GB server, well past what the OS can hold before something gets killed — you've done the sizing math correctly.
Key takeaways
pm.max_childrenis a hard concurrency ceiling — size it from actual measured per-process memory, not a guess.- Setting it too high risks OOM kills under real traffic spikes; too low causes unnecessary request queuing with spare capacity sitting idle.
dynamicis the right strategy for most Drupal sites;ondemandsuits low-traffic/memory-constrained hosting;staticsuits predictable, consistently high load.- Measure real per-process memory with
psunder actual traffic before sizing — don't copy a number from a blog post written for a different site.
Coming up next
You've tuned how PHP compiles and how many requests it can handle at once. Next, we look at the transport layer itself: enabling HTTP/2 to let browsers fetch more of your page's assets in parallel.