PHP-FPM (FastCGI Process Manager) is the backbone of PHP web applications. A misconfigured pool either wastes memory with idle processes or bottlenecks under load with too few workers. This guide shows how to analyze your traffic patterns and tune PHP-FPM accordingly, covering process manager modes, memory calculations, and monitoring strategies.
Understanding Process Manager Modes
PHP-FPM offers three process management modes, each suited to different scenarios:
Static Mode
; Best for: dedicated servers with consistent traffic
pm = static
pm.max_children = 50
; All workers are always running — no spawn overhead under load
Static mode pre-forks a fixed number of workers. Ideal when your server primarily runs PHP and has predictable traffic. The trade-off is memory usage — all workers consume RAM even when idle.
Dynamic Mode (Default)
; Best for: most VPS deployments with variable traffic
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
Dynamic mode scales workers between min and max spare. Good general-purpose choice, but can cause latency spikes when spawning new workers during traffic surges.
Ondemand Mode
; Best for: low-traffic sites or shared hosting
pm = ondemand
pm.max_children = 30
pm.process_idle_timeout = 10s
pm.max_requests = 500
Ondemand mode starts workers only when requests arrive and kills them after idle timeout. Saves memory on quiet sites but adds latency for the first request after idle period.
Calculating pm.max_children
The most critical setting. Too high and you run out of RAM; too low and requests queue.
# Step 1: Find average memory per PHP-FPM worker
ps --no-headers -o rss -C php-fpm | awk '{ sum += $1; n++ } END { print sum/n/1024 " MB average per worker" }'
# Typical results:
# WordPress: 40-60 MB per worker
# Laravel: 30-50 MB per worker
# Magento: 80-120 MB per worker
# Step 2: Calculate max_children
# Formula: (Total RAM - OS/DB/other services) / Average worker memory
# Example: 4GB VPS, 1GB for OS/MySQL, 60MB per WordPress worker
# (4096 - 1024) / 60 = 51 workers
# Round down to 50 for safety margin
Memory Guard with pm.max_requests
; Recycle workers after N requests to prevent memory leaks
pm.max_requests = 500
; For applications with known memory leaks, set lower
pm.max_requests = 200
Pool Isolation
Run separate pools for different applications to prevent one from affecting another:
; /etc/php/8.3/fpm/pool.d/wordpress.conf
[wordpress]
user = www-data
group = www-data
listen = /run/php/php-fpm-wordpress.sock
pm = dynamic
pm.max_children = 30
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 16
pm.max_requests = 500
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 30
; /etc/php/8.3/fpm/pool.d/api.conf
[api]
user = www-data
group = www-data
listen = /run/php/php-fpm-api.sock
pm = static
pm.max_children = 20
pm.max_requests = 1000
php_admin_value[memory_limit] = 128M
php_admin_value[max_execution_time] = 10
Monitoring PHP-FPM Status
; Enable status page in pool config
pm.status_path = /fpm-status
; Nginx config to expose it
location /fpm-status {
access_log off;
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Check status
curl -s http://localhost/fpm-status
# Key metrics:
# active processes — currently handling requests
# idle processes — waiting for requests
# listen queue — requests waiting for a free worker (should be 0!)
# max listen queue — historical peak queue length
# max active processes — peak concurrent workers used
# JSON format for monitoring tools
curl -s "http://localhost/fpm-status?json"
Alerting on Queue Buildup
#!/bin/bash
# /usr/local/bin/check-fpm.sh
QUEUE=$(curl -s "http://localhost/fpm-status?json" | jq '.["listen queue"]')
if [ "$QUEUE" -gt 0 ]; then
echo "WARNING: PHP-FPM listen queue at $QUEUE — consider increasing pm.max_children"
# Send alert
fi
Slow Request Logging
; Log requests taking longer than 5 seconds
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slow.log
; Also set hard timeout to kill stuck workers
request_terminate_timeout = 60s
The slow log captures stack traces of running PHP code when the timeout is hit, making it invaluable for debugging performance issues.
OPcache Configuration
OPcache works hand-in-hand with PHP-FPM. Misconfigured OPcache negates FPM tuning benefits:
; php.ini OPcache settings for production
opcache.enable = 1
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 32
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0 ; Don't check for file changes (deploy needs restart)
opcache.revalidate_freq = 0
opcache.save_comments = 1 ; Required for some frameworks (Laravel, Doctrine)
Traffic Pattern Analysis
# Analyze request patterns from access logs
# Requests per second over the last hour
awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" '$4 ~ d' /var/log/nginx/access.log | \
awk '{print $4}' | cut -d: -f2,3 | sort | uniq -c | sort -rn | head -20
# Peak concurrent PHP requests (from FPM status over time)
while true; do
curl -s "http://localhost/fpm-status?json" | \
jq -r '"[" + (now|strftime("%H:%M:%S")) + "] Active: " + (.["active processes"]|tostring) + " Queue: " + (.["listen queue"]|tostring)'
sleep 5
done
Tuning Workflow
- Start with
pm = dynamicand conservative values. - Monitor with
pm.status_pathfor a week under normal traffic. - Check: if
listen queue > 0frequently, increasepm.max_children. - Check: if
idle processesis consistently nearmax_spare_servers, your min is too low. - Check: if
max active processesequalspm.max_children, you hit the ceiling — increase it or switch tostatic. - Verify total memory stays within budget:
max_children * avg_worker_mb < available_ram.
Summary
PHP-FPM tuning is fundamentally a memory budgeting exercise. Measure your average worker memory, allocate your available RAM across the right number of workers, and monitor queue length to confirm you have enough capacity. Use pool isolation for multi-application servers, enable slow logging to catch performance regressions, and always pair FPM tuning with OPcache optimization for maximum benefit.