PHP-FPM runs a pool of PHP worker processes behind Nginx or Apache. Too few workers and visitors queue up with 502/504 errors; too many and the server runs out of RAM. PHP-FPM tuning mostly comes down to sizing pm.max_children correctly for your memory.
Before you start
- Root access to a VPS running PHP-FPM (any PHP 7.4–8.x version).
- Find the pool file:
/etc/php/8.3/fpm/pool.d/www.confon Debian/Ubuntu,/etc/php-fpm.d/www.confon AlmaLinux/Rocky. - Let the site run under normal traffic for a while so process sizes are realistic.
Measure memory per PHP process
ps --no-headers -o rss -C php-fpm8.3 | awk '{sum+=$1; n++} END {print sum/n/1024 " MB average"}'Use php-fpm as the process name on RHEL-based systems. WordPress and WooCommerce workers often use 60–120 MB each; simple apps far less.
Calculate pm.max_children
Work out the RAM available to PHP: total RAM minus what MySQL, Redis, the web server and the OS need. Then:
pm.max_children = RAM for PHP / average process sizeExample: 4 GB VPS, MySQL uses 1.2 GB, OS and Nginx 0.6 GB, leaving about 2.2 GB. With 80 MB per worker: 2200 / 80 ≈ 27. Round down and leave headroom: 24.
Choose a process manager
| pm mode | Behaviour | Good for |
|---|---|---|
dynamic | Keeps between min and max spare workers | Most sites |
static | Always runs max_children workers | Busy dedicated servers with predictable load |
ondemand | Starts workers on request, stops idle ones | Many low-traffic pools, small RAM |
A balanced dynamic pool for the example above:
pm = dynamic
pm.max_children = 24
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 10
pm.max_requests = 500pm.max_requests recycles each worker after 500 requests, which contains slow memory leaks.
Enable the status page and slow log
In the pool file:
pm.status_path = /fpm-status
slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5sExpose the status page only to localhost in Nginx:
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}Check it with curl http://127.0.0.1/fpm-status. Watch listen queue (should be 0) and max children reached. The slow log shows a stack trace for every request over 5 seconds, pointing at the plugin or query responsible.
Apply and verify
php-fpm8.3 -t && systemctl reload php8.3-fpmThen watch memory with free -h under load. Available RAM should stay comfortably above zero and swap should not climb.
Troubleshooting
- WARNING: [pool www] server reached pm.max_children setting: all workers were busy. Raise the limit only if RAM allows; otherwise make requests faster (caching, OPcache, slow log fixes).
- 502 Bad Gateway with high load: workers are exhausted or crashed. Check the PHP-FPM log and
listen.backlog. - Server swapping or OOM kills: max_children is too high for available RAM. Lower it.
- seems busy (you may need to increase pm.start_servers): raise
start_serversandmin_spare_serversa little.
PHP-FPM FAQ
Does more max_children make my site faster?
Only up to the number of CPU cores and the RAM you have. Beyond that, requests compete for CPU and the server slows down or swaps.
Should I enable OPcache?
Yes. OPcache caches compiled PHP and is one of the biggest free speed-ups. It is enabled by default on most modern PHP builds.
Can I tune PHP-FPM on cPanel hosting?
On shared hosting the provider manages it. On MdmHost cPanel hosting, PHP runs through LiteSpeed's LSAPI, which manages workers automatically.
