Loading Smiley Hoster
Skip to content

Size PHP-FPM pm.max_children for your WordPress VPS memory

Smiley Hoster Team · · 2 min read
A 4 GiB memory budget is divided into other services, ten PHP workers and spare capacity.

Cloudflare's DNS-cache case study makes a useful point for smaller servers too: measure the memory actually occupied. For a WordPress VPS, that measurement can inform a concrete decision—how many PHP-FPM workers may run concurrently.

1. Sample the workers that serve WordPress

The examples assume Ubuntu 24.04, PHP 8.3 and a pool named www. Adjust the paths and service for your installation. PHP-FPM's pm.max_children caps requests processed concurrently in that pool; it is not a visitor limit.

Preserve the current configuration, then collect a baseline:

Example
sudo cp /etc/php/8.3/fpm/pool.d/www.conf \ /root/www.conf.before-sizingfree -mps -eo rss=,args= | \ awk '/php-fpm: pool www/ && !/awk/ {print $1}' \ | sort -n

The process values are RSS in KiB; divide by 1024 for MiB. Repeat the sample while exercising uncached pages, administration and checkout if relevant. An idle worker is a poor proxy for a busy one.

RSS includes shared pages, so adding worker RSS values can overestimate total consumption. It is a conservative first approximation; PSS measurements can refine it. PHP's memory_limit is not a measurement of the whole worker process.

2. Reserve memory before dividing it

Budget for the operating system, MariaDB, Nginx, shared caches, other services and their peaks first. Multiple pools must share the remaining allowance. Giving every pool the full server budget defeats the calculation.

Here is an illustrative calculation, not a measured recommendation for your site:

ItemBudget
Total RAM4096 MiB
Allowance outside PHP workers2048 MiB
Remaining worker budget2048 MiB
Sizing RSS per worker160 MiB

The arithmetic ceiling is floor(2048 / 160) = 12. Starting with ten workers leaves 448 MiB within the worker allowance. Real peaks may exceed the sampled footprint, so retain that headroom.

Open the pool configuration:

Example
sudoedit /etc/php/8.3/fpm/pool.d/www.conf

Replace the existing matching directives rather than appending duplicates:

Example
pm = dynamicpm.max_children = 10pm.start_servers = 2pm.min_spare_servers = 2pm.max_spare_servers = 4

These values fit this worked example. If your measured ceiling differs, keep the startup and spare-worker settings consistent with it.

3. Judge the result under representative traffic

Example
sudo php-fpm8.3 -tsudo systemctl reload php8.3-fpmsudo journalctl -u php8.3-fpm \ --since "15 minutes ago" --no-pagersudo tail -n 50 /var/log/php8.3-fpm.logfree -mvmstat 1 10

Run the reload only after the configuration test succeeds. Repeat the same WordPress operations and compare response times, errors, available memory and swap activity. Check both logging locations because the actual destination depends on configuration.

Common mistakes: raising concurrency whenever a max_children message appears, forgetting the database budget, or benchmarking only cached pages. A full pool may be caused by slow requests. Investigate those requests before allowing more of them to compete for resources. CPU capacity and database contention may impose a lower practical limit than RAM.

Acceptance requires successful WordPress operations through several representative peaks while memory headroom remains. If performance regresses, restore the saved pool file, validate syntax and reload. Change one sizing variable at a time so the next observation remains interpretable.

Sources: Cloudflare, PHP FPM configuration.

Did this article answer your question?
Your feedback shapes what we write next.

Your site online today

Free migration* · 30-day refund

Get started* A site under 30 GB, cPanel, WordPress and VPS plans.