PHP-FPM Calculator: How Many Workers Your Server Can Run
Too few PHP-FPM workers and requests queue behind each other. Too many and the server runs out of memory and starts swapping, or kills MySQL. Put in your server's memory and how big your workers get, and get pm.max_children and the rest of the pool settings.
Workers From Memory
Measure your workers while the site is busy (the command is below). Sizing on the largest worker means the server stays safe even when every worker grows that big.
"Everything else" is the operating system, the web server, and MySQL, Redis or anything else running on the same server. Allow 1 GB for a server that only runs PHP and nginx, and add the database's own memory if it's on the same server.
Sized on the largest worker, so even if every worker grows that big at once, the server won't run out of memory and start swapping.
pm = dynamic pm.max_children = 38 pm.start_servers = 8 pm.min_spare_servers = 4 pm.max_spare_servers = 11 pm.max_requests = 500
Measure Your Workers First
The calculator is only as good as the worker size you put in.
Run this on the server while it's busy, changing php-fpm8.3 to your PHP version. It prints the average and largest worker in MB, and how many are running:
ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1; if ($1>m) m=$1} END {printf "average %.0f MB, largest %.0f MB, %d workers\n", s/NR/1024, m/1024, NR}'
This counts shared memory in every worker, so it overstates a little. That errs on the safe side, which is what you want here.
Expect a wide range. A plain PHP site might use 20 to 40 MB a worker. A WordPress site with a page builder and WooCommerce often uses 100 to 250 MB, and an import or report page can push one worker far higher. On one client server, the largest workers used 255 MB while the setting had been worked out for 10 to 20 MB. That gap is how a server ends up swapping on its busiest day.
When the Numbers Look Wrong
Workers are huge. Look at what the biggest requests do: a plugin loading every product at once, an import running through the web server, or a memory_limit set to 1 GB "to fix an error". Fixing that is worth more than adding memory.
Plenty of memory, but still slow. If workers are all busy but the CPU is quiet, requests are waiting on something: a slow query, an external API, or a lock. More workers won't help; finding the wait will. The PHP-FPM slow log (request_slowlog_timeout = 5s) shows where each slow request was stuck.
More workers than cores. That's normal for PHP, because most of a request is spent waiting on the database. If requests are CPU-heavy (image resizing, PDF generation), workers far beyond the core count only make each request slower. Watch the load average after any change.
After changing the pool file, check it with php-fpm8.3 -t, then systemctl reload php8.3-fpm. A reload lets running requests finish.
Questions
How do I calculate pm.max_children?
Take the server's memory, subtract what everything else needs (the operating system, nginx or Apache, and MySQL or Redis if they run on the same server), and divide what is left by the memory of your largest PHP-FPM worker. 6 GB left over with workers of up to 160 MB gives pm.max_children = 38.
How do I find out how much memory a PHP-FPM worker uses?
Run ps --no-headers -o rss -C php-fpm8.3 (use your own PHP version) while the site is busy. It lists each worker's memory in KB. The command further down this page turns that into the average and the largest worker, in MB.
Should I size on the average worker or the largest one?
The largest. Workers grow to the size of the heaviest request they have handled, so on a busy day many of them end up near the top figure. Sizing on the average is how servers end up swapping or having MySQL killed by the out-of-memory killer.
What does "server reached pm.max_children setting" mean?
Every worker was busy and new requests had to queue. Now and then it is harmless. If it shows up often, either the limit is too low for the memory you have, or requests are slow (a slow query or an external API) and are holding on to workers. Fix the slow requests first: more workers only queue them somewhere else.
Which is better: pm = dynamic, ondemand or static?
dynamic keeps a few spare workers ready and suits most servers. ondemand starts workers only when requests arrive, which saves memory on a server with many small, quiet sites. static keeps every worker running all the time, and only makes sense on a server dedicated to one busy application.
What should pm.max_requests be?
Something like 500. Each worker is restarted after that many requests, which clears out memory leaks in plugins or extensions without you noticing any difference.
Does each site need its own pool?
On a server with several sites, a pool per site is better. Each one runs as its own user, so one compromised site can't read another's files, and one busy site can't take every worker. Divide the memory between the pools, rather than giving each one the whole server's figure.
Server Struggling Under Load?
I tune PHP-FPM, MySQL and nginx for agencies and software teams as part of Managed. Tell me what you run and where it hurts.