pm.max_children is the PHP-FPM setting that most often decides whether a site survives a traffic spike or starts returning 502 Bad Gateway. Too low a value backs up the request queue, too high a value can crash the server through an out-of-memory (OOM) kill. Below is an honest calculation formula and what it cannot predict without measuring your own workload.
What makes up the memory budget for PHP-FPM
Before counting the number of processes, you need to know how much memory is physically free for PHP-FPM on the server. Subtract from the total RAM whatever the operating system, MySQL or another database, Redis and Nginx already use. What remains is the budget you can give to PHP-FPM workers.
For example, on a VPS with 8 GB RAM, if the system and MySQL reserve 3 GB, about 5 GB (5120 MB) is left for PHP-FPM. The separate memory limit for a single PHP process is covered in the article on memory_limit and upload_max_filesize; here the total budget for the whole worker pool is what counts.
How to measure the real size of a single process
Theoretical calculations are useless without measuring actual memory use by workers. The correct way is to check the RSS (resident set size) of php-fpm processes already running on the server under normal, not peak, load.
The command ps -o rss= -C php-fpm8.3 | awk '{sum+=$1} END {print sum/NR/1024" MB"}' prints the average process size in megabytes. Take the reading several times during the day, after OPcache has warmed up and under different load, because process size grows as new files and extensions get loaded.
The pm.max_children formula
In general form the formula is: pm.max_children equals the memory available to PHP-FPM divided by the average size of one process. The result is an upper bound, not a target: individual processes swell while handling heavy requests, so it is reasonable to leave a 20-30% margin below the calculated value. A detailed look at pm modes and other pool settings is in the article on PHP-FPM pools and workers.
- opcache.memory_consumption settings and the number of cached files
- heavy extensions such as imagick, intl, gd add memory to every process
- framework bootstrap size: Laravel and Symfony load more classes than bare PHP
- session storage driver: file sessions are lighter than in-process memory sessions
- the size of the Composer autoload: more dependencies mean a heavier startup
A calculation example for several configurations
The table below shows only the arithmetic of division, not the result of measuring a real site. The average process size and available memory must come from your own server, otherwise the numbers are meaningless.
| RAM for PHP-FPM | Average process size | pm.max_children (rounded) |
|---|---|---|
| 1024 MB | 50 MB | 20 |
| 2048 MB | 60 MB | 34 |
| 4096 MB | 70 MB | 58 |
| 5120 MB | 60 MB | 85 |
Final pool parameters
The rest of the dynamic mode parameters follow from pm.max_children: pm.start_servers is usually taken as 25-30% of max_children, pm.min_spare_servers as roughly half of start_servers, and pm.max_spare_servers as one and a half times start_servers. How these parameters connect to Nginx through fastcgi_pass is described in the article on the Nginx and PHP-FPM link.
pm = dynamic
pm.max_children = 70
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 500What happens with the wrong value
Too low a pm.max_children means that during a traffic spike new requests queue up at the PHP-FPM socket waiting for a free process; if the wait exceeds the Nginx timeout, the client gets a 502 or 504. It looks like a shortage of processes, but the cause is configuration, not code.
Too high a value is more dangerous: if each process weighs 80-100 MB at peak and more are running than physical memory allows, the server starts swapping or the kernel kills processes with the OOM killer. Recovering from OOM takes longer than a short request queue.
Frequently asked questions
Is it fine to set pm.max_children with a large margin just in case?
No: extra processes do not speed up request handling, they just occupy memory that sits unused most of the time and raise the risk of OOM at the moment of peak traffic.
How often should the value be recalculated?
Recalculate after a change in RAM size, after a major framework or dependency upgrade, and after a noticeable change in traffic. The current pool load is easy to check through pm.status_path.
The site still returns 502 after tuning pm.max_children, what to check?
First rule out slow database queries and external API calls: if every request hangs for 10-15 seconds, increasing the number of processes only postpones the problem instead of solving it.