PHP-FPM is the process manager that runs PHP behind Nginx or Apache. Its defaults are written for a machine with unknown RAM and unknown traffic, which means they are wrong for yours: either too few workers (requests queue, 502/504 errors under load) or too many (the server runs out of memory and swaps). Sizing it takes five minutes and a calculator.
Where the config is
Each pool is a file: /etc/php/8.3/fpm/pool.d/www.conf on Ubuntu/Debian, /etc/php-fpm.d/www.conf on EL. On Plesk, each domain has its own pool under /var/www/vhosts/system/<domain>/etc/php-fpm.conf, and the settings below are exposed under PHP Settings → Performance settings for the domain.
The one number that matters: pm.max_children
The maximum number of PHP processes that can run at once. Too low and requests wait; too high and memory runs out. The calculation:
-
Find out how much memory one PHP process uses on your site under load:
ps -ylC php-fpm8.3 --sort:rss | awk '{sum+=$8; n++} END {print sum/n/1024 " MB average, " n " processes"}'WordPress typically lands at 40–80 MB; Laravel 30–60 MB; a big WooCommerce site with many plugins 100+ MB.
-
Decide how much RAM PHP may have: total minus what the OS, MariaDB, Nginx and Redis need. On a 4 GB server with MariaDB taking 1 GB, that is about 2 GB for PHP.
-
Divide:
2048 MB / 60 MB ≈ 34. Round down.
pm.max_children = 32That is the ceiling. Starting from a wrong ceiling is how servers with 2 GB of RAM end up with pm.max_children = 200 and a kernel OOM kill of MariaDB at the first traffic spike.
dynamic or ondemand
pm = dynamic
pm.max_children = 32
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12dynamic keeps a few workers warm and scales up to the max — the right choice for a site with steady traffic. ondemand starts workers only when requests arrive and kills them after pm.process_idle_timeout; it uses no memory when idle, at the cost of a few milliseconds' startup per burst. Use ondemand on a server hosting many small sites, each with its own pool, so idle sites cost nothing:
pm = ondemand
pm.max_children = 16
pm.process_idle_timeout = 10sstatic (a fixed number always running) is for a dedicated server running one busy application where you want zero scaling latency.
Recycle workers
PHP applications leak a little memory over thousands of requests. Restart each worker periodically:
pm.max_requests = 500Timeouts, and the 504
Nginx gives up waiting for PHP after fastcgi_read_timeout (default 60 s) and returns a 504. PHP gives up on itself after max_execution_time (default 30 s). For a normal site both are fine; for a report or an import that takes minutes, raise them for that location only rather than globally, or move the work to a queue.
request_terminate_timeout = 120sin the pool kills a request that has run too long, which stops one stuck script eating a worker forever.
Find the slow scripts
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5sEvery request over five seconds writes a stack trace to the slow log — exactly which plugin, which function, which query. This is the fastest way to answer "why is the site slow" and it costs nothing.
One pool per site
On a server with several sites, give each its own pool with its own user, group and socket. Sites cannot then read each other's files, a runaway on one site cannot starve the others, and each can have its own PHP version and limits. This is how Plesk does it by default and worth copying by hand:
[example.com]
user = example
group = example
listen = /run/php/example.com.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 12and in the Nginx server block, fastcgi_pass unix:/run/php/example.com.sock;.
Apply and verify
sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm
sudo systemctl status php8.3-fpmThen watch under load:
watch -n 2 "ps -C php-fpm8.3 --no-headers | wc -l; free -m | grep Mem"Worker count should rise with traffic and fall after; memory should never approach zero available. If journalctl -u php8.3-fpm shows "server reached pm.max_children", you are hitting the ceiling — either raise it (if memory allows), add caching so fewer requests reach PHP, or add RAM.
On a VPSPioneer managed VPS, pool sizing is part of setup: we measure the real per-process footprint of your application and set the limits from it rather than from a default file. Where the memory goes on the database side is in MySQL and MariaDB tuning basics.