PHP-FPM Process Management Explained: Optimizing start_servers, min_spare_servers, and max_spare_servers for Performance


3 views

When running PHP-FPM in dynamic mode (pm = dynamic), the process manager dynamically adjusts the number of worker processes based on three key directives:

pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_children = 50

Let's clarify some confusing terms:

  • Server/Worker Process: A PHP-FPM process that handles requests (often called "workers")
  • Child Process: Same as worker process (terminology varies in docs)
  • 1:1 Relationship: Each worker handles one request at a time

For a 4-core server with 8GB RAM:

; Recommended starting configuration
pm = dynamic
pm.max_children = 80
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.process_idle_timeout = 10s

Calculation methodology:

max_children = (Total RAM - Other services) / Average PHP process size
start_servers = (max_children * 0.25)
min_spare_servers = (max_children * 0.125)
max_spare_servers = (max_children * 0.375)

Check current FPM status:

# Get current FPM status
sudo systemctl status php-fpm

# Alternatively for more details:
sudo ps aux | grep php-fpm | wc -l
  • Monitor memory usage per process: ps -ylC php-fpm --sort:rss
  • Adjust pm.process_idle_timeout based on traffic patterns
  • For burst traffic, temporarily increase start_servers
  • Never set max_spare_servers higher than max_children

Mistakes I've seen in production:

; Bad configuration example
pm.max_children = 200  ; Too high for available RAM
pm.start_servers = 50  ; Causes unnecessary memory usage
pm.min_spare_servers = 5  ; Too low for production traffic spikes

When running PHP-FPM in dynamic mode (pm = dynamic), three critical parameters control your process pool:

pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_children = 50

"Servers" vs "Children": In PHP-FPM terminology, each "server" is a PHP worker process. These are not directly equivalent to CPU cores, but rather to concurrent PHP execution contexts.

Process Lifecycle: PHP-FPM maintains a pool of worker processes:

  1. At startup: Creates start_servers workers
  2. During low traffic: Keeps between min_spare_servers and max_spare_servers idle workers
  3. Under load: Spawns new workers up to max_children

Here's a realistic configuration example for a 4-core server with 8GB RAM:

pm = dynamic
pm.max_children = 100
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.process_idle_timeout = 10s

Calculation methodology:

max_children = (Total RAM - System RAM) / Avg PHP process size
start_servers = (max_children * 0.2)
min_spare_servers = (max_children * 0.1)
max_spare_servers = (max_children * 0.3)

Use these commands to monitor your PHP-FPM status:

# Get current process count
ps aux | grep php-fpm | wc -l

# Check pool status (requires status path configured)
curl http://localhost/status

Sample output interpretation:

pool:                 www
process manager:      dynamic
start time:           01/Jan/2022:00:00:00 +0000
start since:          12345
accepted conn:        67890
listen queue:         0
max listen queue:     1
listen queue len:     128
idle processes:       15
active processes:     5
total processes:      20
max active processes: 25
max children reached: 0

For high-traffic WordPress sites, consider these additional parameters:

pm.max_requests = 500
request_terminate_timeout = 30s
request_slowlog_timeout = 5s

Always test configurations with:

sudo php-fpm -t
sudo systemctl restart php-fpm

Problem: max children reached appears in logs
Solution: Either increase max_children or optimize your PHP code to reduce memory usage

Problem: High CPU usage with many idle processes
Solution: Reduce max_spare_servers and adjust process_idle_timeout