PHP-FPM pm.max_children is the hard ceiling on how many PHP worker processes a pool can start, so it is the first value to calculate when RAM is tight. This guide shows how to measure real worker memory, reserve RAM for the OS, web server and database, then apply process manager settings that fit your Linux VPS without causing 502 errors or the OOM killer.
Before you start
You need root or sudo access to the VPS and a maintenance window if you will restart PHP-FPM. A reload is usually enough for pool changes, but a restart drops in-flight requests.
Confirm which PHP-FPM service and binary you are running. The commands below use php-fpm8.3; replace it with your installed version, such as php-fpm8.1, php-fpm8.2 or php-fpm8.4.
systemctl list-units --type=service | grep -i php
php-fpm8.3 -v
php-fpm8.3 -tFind the pool files. On Ubuntu and Debian they are normally under /etc/php/<version>/fpm/pool.d/. On AlmaLinux and Rocky Linux they are normally under /etc/php-fpm.d/.
ls -l /etc/php/*/fpm/pool.d/ 2>/dev/null
ls -l /etc/php-fpm.d/ 2>/dev/null
grep -R "^pm" /etc/php/*/fpm/pool.d/ /etc/php-fpm.d/ 2>/dev/nullWarning: Back up the pool file before editing it. A bad pm.max_children or pm.start_servers value can stop PHP-FPM from starting and take every PHP site offline.
Measure real PHP-FPM worker memory
Do not guess memory per worker from the PHP memory_limit. A worker can use much less or much more depending on extensions, opcache, frameworks and request payloads. Measure the actual resident set size (RSS) while the site is under normal or peak load.
free -m
ps --no-headers -o rss= -p "$(pgrep -d, php-fpm)" | awk '{sum+=$1; n++} END {if (n) printf "processes=%d avg_rss_mb=%.1f total_rss_mb=%.1f\n", n, sum/n/1024, sum/1024}'
ps --no-headers -o rss= -p "$(pgrep -d, php-fpm)" | sort -n | tail -1 | awk '{printf "peak_rss_mb=%.1f\n", $1/1024}'The first ps command gives an average and total. The second gives the largest worker, which is safer for planning. RSS can overcount shared memory pages, so treat the result as a conservative estimate rather than an exact figure.
Run the measurement during your busiest period, or after sending representative traffic to a staging site. A quiet pool with one small request will give you a dangerously low number.
Set PHP-FPM pm.max_children for your RAM budget
Use this formula:
available_for_php = total_ram - os_and_services - web_server - database - spare
pm.max_children = floor(available_for_php / peak_worker_mb)For example, on a 4 GB VPS with a database and Nginx on the same machine, reserve about 1.5 GB for the OS, Nginx, MariaDB and normal cache growth. That leaves roughly 2.5 GB for PHP-FPM. If the largest worker is 48 MB, the raw calculation is about 53 workers. Set pm.max_children below that, for example 45, so short memory spikes do not push the server into swap or the OOM killer.
Do not fill the whole RAM budget with PHP workers. Leave headroom for the kernel page cache, backups, log rotation and package updates.
Choose a process manager mode:
pm = dynamicis a good default for most web servers. It starts a small number of workers and scales betweenpm.min_spare_serversandpm.max_spare_servers.pm = ondemandstarts workers only when requests arrive. It saves RAM on low-traffic VPS plans but adds latency on the first request after idle.pm = statickeeps a fixed number of workers. Use it only when traffic is predictable and you have measured the memory cost.
Edit the PHP-FPM pool
Open the pool file for the site or the default www pool. The example below uses the Ubuntu/Debian path.
sudo cp /etc/php/8.3/fpm/pool.d/www.conf /etc/php/8.3/fpm/pool.d/www.conf.bak
sudoedit /etc/php/8.3/fpm/pool.d/www.confSet values that match your calculation. This example is for a pool with a measured peak worker size of about 48 MB and a RAM budget of roughly 2.5 GB.
pm = dynamic
pm.max_children = 45
pm.start_servers = 8
pm.min_spare_servers = 5
pm.max_spare_servers = 12
pm.max_requests = 500
request_terminate_timeout = 60sKeep pm.max_children greater than or equal to pm.start_servers, pm.min_spare_servers and pm.max_spare_servers. If those values conflict, PHP-FPM may refuse to start or log warnings.
pm.max_requests = 500 recycles workers after 500 requests. That helps contain slow memory growth from extensions or application code. Lower it to 200–300 if you see workers growing steadily; raise it if worker startup overhead is visible in latency.
For a low-memory VPS with light traffic, an on-demand pool can look like this:
pm = ondemand
pm.max_children = 20
pm.process_idle_timeout = 10s
pm.max_requests = 300Warning: Reloading or restarting PHP-FPM interrupts requests. Use reload first. Use restart only if the reload does not apply the change or the service is already unhealthy.
Validate the configuration before applying it:
sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpmIf the reload fails, check the exact error in the service journal:
sudo journalctl -u php8.3-fpm --since "10 min ago" --no-pagerCheck it works
Confirm the service is active and the pool is running with the expected limits.
systemctl status php8.3-fpm --no-pager
ps --no-headers -C php-fpm8.3 | wc -l
free -mLoad a PHP page through the web server and check for 502 or 504 responses. Then watch memory and worker count for a few minutes under normal traffic.
curl -I http://203.0.113.10/
watch -n 2 'free -m; ps --no-headers -C php-fpm8.3 | wc -l'If you enabled pm.status_path, request the status page from an allowed address. The exact URL depends on your web server configuration.
curl http://127.0.0.1/status?fullLook for max children reached in the PHP-FPM log. That means the pool hit its ceiling. It is not automatically a fault, but repeated messages during normal traffic mean either pm.max_children is too low for the workload or requests are taking too long.
Troubleshooting
- 502 Bad Gateway after lowering
pm.max_children— all workers are busy and the queue is full. Check slow requests and database waits first. Raisepm.max_childrenonly iffree -mshows real headroom. forkfailed: Cannot allocate memory — the pool limit is above what the VPS can support, or another service grew. Lowerpm.max_children, checkdmesg -T | grep -i oom, and review database memory settings.seems busyormax children reachedwarnings — the pool is saturated. Inspect PHP slow logs,request_terminate_timeout, and slow SQL queries. Adding workers without fixing slow requests will only consume more RAM.- Changes are ignored — you edited the wrong pool file or restarted the wrong service. Run
php-fpm8.3 -ttto see which files are parsed, and confirm the service name withsystemctl list-units | grep php. - Worker memory keeps climbing — an extension or application leaks memory. Lower
pm.max_requeststo recycle workers sooner, then profile the application and review installed PHP extensions.
Next steps
Review the wider pool tuning method in Tune PHP-FPM pools for your RAM. If PHP itself is hitting its per-request limit, see Fix PHP "Allowed memory size exhausted". On a small VPS, database memory often competes with PHP-FPM, so tune it with Tune MySQL and MariaDB memory for a small VPS.
