PHP-FPM pm.max_children Reached on cPanel Servers

Rate this post

See Also: Bulk PHP-FPM Pool Tuner for Existing Accounts

If you manage cPanel servers, you have almost certainly encountered this log entry at some point:

[pool username] WARNING: server reached pm.max_children setting (5), consider raising it

It looks simple enough. PHP-FPM is telling you it ran out of worker processes to handle incoming requests. But on cPanel servers, especially VPS and dedicated environments, properly tuning this value is more nuanced than just bumping a number. The configuration paths are poorly documented, the defaults are conservative to the point of being inadequate, and the interaction between per-domain overrides and global pool defaults can trip up even experienced admins.

This guide covers what pm.max_children actually controls, why the defaults fail on anything beyond shared hosting, and exactly where and how to configure it on cPanel systems.

What pm.max_children Actually Does

PHP-FPM (FastCGI Process Manager) maintains a pool of PHP worker processes for each domain (or user, depending on your pool configuration). When a request comes in that requires PHP processing, it gets handed to one of these workers. The pm.max_children directive sets the hard ceiling on how many worker processes can exist simultaneously within a given pool.

When all workers in a pool are busy and a new PHP request arrives, that request has to wait. If this happens frequently, visitors experience slow page loads, timeouts, and 502/504 gateway errors. The warning message in your error log is PHP-FPM telling you that it hit that ceiling and had to queue (or reject) requests.

The key thing to understand is that pm.max_children is a per-pool limit, not a global one. On cPanel servers running in the default configuration, each cPanel user account gets its own PHP-FPM pool. So a server with 50 accounts has 50 independent pools, each with its own pm.max_children value.

Why the Default Value of 5 Is Insufficient

Many cPanel-based hosting providers, including InMotion Hosting, ship PHP-FPM pools with pm.max_children set to 5. This default makes sense in a dense shared hosting context where hundreds of accounts share a single server and memory is the primary constraint. Five workers per account, multiplied across hundreds of users, can already consume significant RAM.

But on a VPS or dedicated server where you are hosting a handful of sites (or even just one), a value of 5 is almost always too low. Consider what eats up those five workers:

A single WordPress page load can spawn multiple PHP requests: the main page, admin-ajax calls, REST API endpoints, and cron tasks. If WooCommerce or any plugin that makes loopback HTTP requests is involved, a single visitor session can easily occupy two or three workers simultaneously. Add a second concurrent visitor, a running cron job, and a background plugin update check, and you have saturated the pool.

The symptoms are not always obvious. You might not get outright 502 errors until the situation is severe. More commonly, you will notice intermittent slowness, AJAX calls failing silently, wp-cron jobs not completing, and order processing delays in WooCommerce stores. The PHP-FPM warning only appears in the pool’s error log, which many admins never check proactively.

Calculating a Reasonable Value

Before blindly increasing pm.max_children, you need to understand your server’s memory constraints. Each PHP-FPM worker process consumes RAM, and the amount varies based on the application. A lean WordPress site might use 30 to 40 MB per worker. A WooCommerce store with several plugins could easily use 80 to 120 MB per worker.

To estimate what your workers actually use, check the resident memory of running PHP-FPM processes:

ps -eo pid,rss,command | grep php-fpm | grep -v grep | awk '{print $2/1024 " MB", $0}'

For a more precise per-pool breakdown:

ps --no-headers -o rss,args -C php-fpm | grep "pool " | awk '{sum[$NF]+=$1; count[$NF]++} END {for (pool in sum) printf "%-20s avg: %6.1f MB  workers: %d  total: %6.1f MB\n", pool, sum[pool]/count[pool]/1024, count[pool], sum[pool]/1024}'

Once you know the average memory per worker, the formula is straightforward:

max_children = (Available RAM for PHP) / (Average memory per worker)

On a VPS with 4 GB of total RAM, you might allocate 2 to 2.5 GB for PHP-FPM after accounting for the OS, MySQL, Apache/LiteSpeed, and other services. If each worker averages 60 MB, that gives you roughly 35 to 40 total workers across all pools. If you are running a single primary site, assigning 25 to 30 of those to that pool is perfectly reasonable. If you have five active sites, distributing 7 to 8 per pool is a good starting point.

For single-site VPS or dedicated servers, values of 20 to 50 are common. For busy WooCommerce or LMS sites, values of 30 to 80 are not unusual on servers with 8 GB or more of RAM.

Where cPanel Stores PHP-FPM Configuration

This is where things get poorly documented. cPanel does not use standard PHP-FPM pool configuration files in the way you might expect from a manual PHP-FPM installation. Instead, it uses a YAML-based configuration system that generates the actual pool .conf files. Editing the generated .conf files directly is pointless because cPanel will overwrite them.

There are two levels of configuration: global defaults and per-domain overrides.

Global Defaults: system_pool_defaults.yaml

The global defaults file controls the baseline PHP-FPM settings applied to every pool on the server unless overridden at the domain level. It lives at:

/var/cpanel/php-fpm.d/system_pool_defaults.yaml

If this file does not exist on your server, you can create it. A typical configuration looks like this:

---
pm_max_children: 25
pm_max_requests: 500
pm_process_idle_timeout: 10

After creating or editing this file, you need to rebuild the PHP-FPM configuration and restart the service:

/scripts/php_fpm_config --rebuild
systemctl restart cpanel-php-fpm-82

Replace 82 with your active PHP version (e.g., 81 for PHP 8.1, 83 for PHP 8.3). If you are running multiple PHP versions, restart each one:

for version in 80 81 82 83; do
    systemctl restart cpanel-php-fpm-${version} 2>/dev/null
done

A few important notes about this file. The directive names in the YAML use underscores, not dots. So it is pm_max_children, not pm.max_children. Getting this wrong will silently fail, and cPanel will just use its compiled-in defaults. Also, changes to global defaults do not override existing per-domain configurations. They only apply to pools that have not been explicitly customized.

Per-Domain Overrides: The User YAML Path

To configure PHP-FPM settings for a specific domain, the configuration file is:

/var/cpanel/userdata/USERNAME/DOMAIN.TLD.php-fpm.yaml

For example, for the domain example.com owned by the cPanel user exampleu:

/var/cpanel/userdata/exampleu/example.com.php-fpm.yaml

A per-domain override file might look like this:

---
pm_max_children: 40
pm_max_requests: 1000
pm_process_idle_timeout: 10
pm: dynamic
pm_start_servers: 10
pm_min_spare_servers: 5
pm_max_spare_servers: 20

After editing, rebuild and restart as shown above.

This per-domain file takes precedence over the global defaults for that specific domain’s pool. Any directive not specified in the per-domain file will fall back to the global default, and if no global default exists, to cPanel’s internal default.

Checking What Is Actually Applied

After making changes, verify that the generated pool configuration reflects your settings:

cat /opt/cpanel/ea-php82/root/etc/php-fpm.d/USERNAME.conf | grep pm

You should see your values reflected in the output:

pm = dynamic
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 1000

If your changes are not showing up, the most common causes are: a typo in the YAML file (use ruby -e "require 'yaml'; YAML.load_file('yourfile.yaml')" or python3 -c "import yaml; yaml.safe_load(open('yourfile.yaml'))" to validate syntax), forgetting to run the rebuild command, or an existing per-domain file overriding your global defaults.

Tuning the Related pm Directives

While you are adjusting pm.max_children, it is worth tuning the related process manager directives as well. These only apply when pm is set to dynamic, which is the recommended mode for most cPanel servers.

pm.start_servers controls how many workers are spawned when the pool starts. Setting this to roughly 25% of pm.max_children is a solid starting point. Too low and you will see latency spikes after a restart as workers spin up on demand. Too high and you waste RAM on idle workers.

pm.min_spare_servers is the minimum number of idle workers PHP-FPM will try to maintain. If the idle count drops below this number, new workers are spawned. Setting this equal to or slightly above pm.start_servers ensures there is always capacity ready for incoming requests.

pm.max_spare_servers is the upper limit on idle workers. If more workers than this number are sitting idle, PHP-FPM will kill the extras. Setting this to roughly 50 to 75% of pm.max_children is typical.

pm.max_requests sets the number of requests a single worker will handle before being recycled (killed and replaced). This is important for preventing memory leaks in long-running PHP applications. A value of 500 to 1000 is standard. Setting it to 0 disables recycling, which is not recommended.

A balanced configuration for a moderately busy site on a 4 GB VPS might look like:

---
pm: dynamic
pm_max_children: 30
pm_start_servers: 8
pm_min_spare_servers: 5
pm_max_spare_servers: 20
pm_max_requests: 500
pm_process_idle_timeout: 10

Using the WHM Interface (With Caveats)

WHM does expose some PHP-FPM settings through its MultiPHP Manager interface. You can navigate to WHM > MultiPHP Manager, select a domain, and adjust the pool settings through the UI. However, the WHM interface has some limitations. It does not expose all available directives, the interface can be slow and unintuitive for bulk changes, and the changes it writes end up in the same YAML files described above. For managing more than a handful of domains, working with the YAML files directly is faster and more reliable.

If you prefer the GUI for occasional single-domain adjustments, it works fine. Just be aware that what you set through WHM and what you set through the YAML files is the same underlying system. There is no conflict, but there is also no separate “GUI configuration” to worry about.

Monitoring After Changes

After adjusting your pm.max_children value, monitor the results to confirm you have resolved the issue without over-provisioning.

Watch the PHP-FPM status page if you have it enabled:

curl -s http://127.0.0.1/status?full 2>/dev/null || echo "Status page not enabled"

To enable the status page for a pool, add pm_status_path: /status to the YAML configuration.

Monitor for the warning message recurring:

tail -f /opt/cpanel/ea-php82/root/usr/var/log/php-fpm/error.log | grep max_children

Check overall memory usage to make sure you have not over-allocated:

watch -n 5 'free -m; echo "---"; ps -eo rss,args --sort=-rss | head -20 | awk "{printf \"%6.1f MB  %s\n\", \$1/1024, \$2}"'

If the pm.max_children warning disappears, your response times improve, and your server’s RAM usage stays within a comfortable margin, you are in good shape.

Quick Reference

Scenario Suggested pm.max_children Notes
Shared hosting (cPanel default) 5 Adequate only for low-traffic shared accounts
Single site on 2 GB VPS 10 to 15 Monitor RAM closely
Single site on 4 GB VPS 20 to 35 Good for most WordPress sites
WooCommerce on 8 GB dedicated 40 to 60 Adjust based on plugin complexity
High-traffic site on 16 GB+ 60 to 100+ Profile memory per worker carefully
File Purpose
/var/cpanel/php-fpm.d/system_pool_defaults.yaml Global defaults for all pools
/var/cpanel/userdata/USER/DOMAIN.php-fpm.yaml Per-domain override
/opt/cpanel/ea-phpXX/root/etc/php-fpm.d/USER.conf Generated config (do not edit)
/opt/cpanel/ea-phpXX/root/usr/var/log/php-fpm/error.log PHP-FPM error/warning log

Final Thoughts

The pm.max_children warning is one of those issues that sits right at the intersection of “easy to fix” and “easy to get wrong.” Raising the value without understanding your server’s memory budget can lead to OOM kills and swapping, which is worse than the original problem. But leaving it at the default of 5 on a VPS or dedicated server is almost guaranteed to cause performance issues under any real traffic load.

The cPanel YAML configuration system is functional but not well documented outside of scattered knowledge base articles and forum posts. Knowing the exact file paths, the underscore-based directive naming convention, and the rebuild workflow saves a lot of trial and error. Bookmark those paths. You will need them again.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top