How do I configure PHP-FPM for maximum performance on a VPS?
PHP-FPM (FastCGI Process Manager) manages a pool of PHP worker processes. Proper tuning of pm.max_children, pm.start_servers, and pm.max_requests dramatically improves throughput and prevents memory exhaustion on high-traffic sites.
DETAILED EXPLANATION:
PHP-FPM pool modes:
- static: Fixed number of processes always running. Best for high-traffic, predictable load.
- dynamic: Scales between min and max as needed. Best for variable traffic (recommended).
- ondemand: Processes created on demand, killed when idle. Best for low-traffic sites.
Memory formula:
max_children = Available PHP RAM / Average PHP process memory
Example: 4GB VPS, reserve 1GB OS+MySQL = 3GB for PHP
3000 MB / 70 MB avg WordPress process = ~42 max_children
WHEN TO USE:
- Any VPS running PHP applications (WordPress, Laravel, CodeIgniter)
- Sites receiving 1,000+ daily visits
- After seeing 502/503 errors under load
STEP-BY-STEP - PHP-FPM configuration for 4 GB VPS:
File: /etc/php/8.1/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 40
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 16
pm.max_requests = 500
request_terminate_timeout = 30s
pm.status_path = /status
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php/www-error.log
php_admin_flag[log_errors] = on
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.max_accelerated_files] = 10000
php_admin_value[opcache.validate_timestamps] = 0
Then: systemctl restart php8.1-fpm
MONITOR status: curl -s http://127.0.0.1/status
Key metric: if "max children reached" > 0, increase pm.max_children
FLOW:
Nginx receives request -> Unix socket -> PHP-FPM pool manager -> idle worker executes -> Response -> worker returns to pool
KEY POINTS:
- Unix socket (/run/php/php8.1-fpm.sock) is faster than TCP (127.0.0.1:9000)
- pm.max_requests = 500 recycles workers and prevents PHP memory leaks
- opcache.validate_timestamps = 0 in production (eliminates file stat checks)
- Connect Quest VPS NVMe SSD reduces OPcache miss penalty
COMMON MISTAKES:
- Setting pm.max_children too high causes OOM (Out of Memory) swap thrashing
- Using pm = static on servers with variable traffic wastes RAM
- Not monitoring /status endpoint - blind to pool saturation
QUICK FIX:
503 errors from PHP-FPM: systemctl status php8.1-fpm
If "max children reached" counter increasing -> increase pm.max_children
or upgrade VPS RAM at connectquest.co.in
DIFFICULTY: Advanced
RELATED: Nginx, WordPress Performance, VPS Optimization, LiteSpeed
DETAILED EXPLANATION:
PHP-FPM pool modes:
- static: Fixed number of processes always running. Best for high-traffic, predictable load.
- dynamic: Scales between min and max as needed. Best for variable traffic (recommended).
- ondemand: Processes created on demand, killed when idle. Best for low-traffic sites.
Memory formula:
max_children = Available PHP RAM / Average PHP process memory
Example: 4GB VPS, reserve 1GB OS+MySQL = 3GB for PHP
3000 MB / 70 MB avg WordPress process = ~42 max_children
WHEN TO USE:
- Any VPS running PHP applications (WordPress, Laravel, CodeIgniter)
- Sites receiving 1,000+ daily visits
- After seeing 502/503 errors under load
STEP-BY-STEP - PHP-FPM configuration for 4 GB VPS:
File: /etc/php/8.1/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /run/php/php8.1-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 40
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 16
pm.max_requests = 500
request_terminate_timeout = 30s
pm.status_path = /status
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php/www-error.log
php_admin_flag[log_errors] = on
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 256
php_admin_value[opcache.max_accelerated_files] = 10000
php_admin_value[opcache.validate_timestamps] = 0
Then: systemctl restart php8.1-fpm
MONITOR status: curl -s http://127.0.0.1/status
Key metric: if "max children reached" > 0, increase pm.max_children
FLOW:
Nginx receives request -> Unix socket -> PHP-FPM pool manager -> idle worker executes -> Response -> worker returns to pool
KEY POINTS:
- Unix socket (/run/php/php8.1-fpm.sock) is faster than TCP (127.0.0.1:9000)
- pm.max_requests = 500 recycles workers and prevents PHP memory leaks
- opcache.validate_timestamps = 0 in production (eliminates file stat checks)
- Connect Quest VPS NVMe SSD reduces OPcache miss penalty
COMMON MISTAKES:
- Setting pm.max_children too high causes OOM (Out of Memory) swap thrashing
- Using pm = static on servers with variable traffic wastes RAM
- Not monitoring /status endpoint - blind to pool saturation
QUICK FIX:
503 errors from PHP-FPM: systemctl status php8.1-fpm
If "max children reached" counter increasing -> increase pm.max_children
or upgrade VPS RAM at connectquest.co.in
DIFFICULTY: Advanced
RELATED: Nginx, WordPress Performance, VPS Optimization, LiteSpeed