Your WordPress VPS has plenty of RAM on paper, but checkout still times out during a sale and wp-admin throws 502 errors when two people edit at once. The culprit is often not "we need a bigger box." It is PHP-FPM running with defaults that made sense on a tiny shared host, not on a busy WooCommerce site.
PHP-FPM is the worker pool that actually runs your PHP. Nginx or Apache hands it requests; each worker handles one request at a time until it finishes. Tune the pool wrong and you either waste RAM on idle workers or queue traffic until browsers give up.
What you are actually tuning
Most WordPress VPS setups use a www pool (or one pool per site). The settings that matter day to day:
- pm: how workers are spawned (
dynamic,ondemand, orstatic) - pm.max_children: ceiling on concurrent PHP requests
- pm.start_servers, pm.min_spare_servers, pm.max_spare_servers: for dynamic mode, how many idle workers to keep warm
- pm.max_requests: recycle workers after N requests (helps with leaky plugins)
On managed Canadian VPS hosting we usually start from measured traffic, not copy-paste configs from a forum thread written for PHP 7.0.
A sane way to estimate max_children
Each PHP worker uses RAM. Rough rule:
max_children ≈ (available RAM for PHP) / (average RAM per worker)
"Available RAM for PHP" means what is left after the OS, MySQL/MariaDB, Redis, and anything else on the box. Do not assign every gigabyte to FPM or the database will swap and everything gets slower.
Average RAM per worker varies wildly. A lean marketing site might sit around 80–120 MB per request. WooCommerce with a heavy plugin stack can spike to 200–350 MB during admin or checkout. Measure with ps or your host's metrics while real users hit the site, not while you are the only one browsing.
Example: 8 GB VPS, 2 GB for MariaDB, 512 MB for Redis, 1 GB headroom for OS and spikes leaves about 4.5 GB for PHP. At 150 MB per worker, that is roughly 30 workers max in theory. In practice we leave margin and set pm.max_children in the low 20s unless monitoring says otherwise.
dynamic vs static vs ondemand
dynamic is the common default. Workers scale between min and max spare servers. Good for sites with traffic swings if you set spare servers high enough that the first rush does not spawn workers one by one.
static keeps a fixed number of workers always running. Predictable RAM use, slightly faster under steady load, wasteful if the site is quiet overnight. Some high-traffic editorial WordPress installs use static when traffic is flat and well understood.
ondemand spins workers up only when needed. Saves RAM on dev boxes; production WordPress with bursts often feels laggy on the first requests after idle periods. We rarely use ondemand for customer-facing WooCommerce.
Symptoms of a pool that is too small
- 502 / 503 from nginx while CPU is not pegged
- Slow TTFB on uncached pages during traffic spikes
server reached pm.max_childrenin PHP-FPM logs- Checkout or cart AJAX hanging while static assets still load fine
Fixing this is not always "raise max_children." Sometimes you need object cache, fewer plugins, or a separate pool for long-running admin tasks. But if logs show max_children constantly, the pool is the bottleneck.
Symptoms of a pool that is too large
- Swap usage climbing under normal traffic
- MySQL connections errors or slow queries because disk I/O is fighting for RAM
- OOM kills in kernel logs
More workers than RAM can feed makes every request slower. That is worse than queueing a few extra seconds at the edge.
max_requests and leaky plugins
Setting pm.max_requests to something like 500–1000 forces PHP workers to restart periodically. That catches extension and plugin memory leaks that show up only after hundreds of requests. Too low adds restart overhead; too high lets one bad deploy eat RAM for days.
After a major plugin update, watch memory per worker for 24 hours. If it stair-steps upward, max_requests is a band-aid; find the plugin or schedule nightly FPM reloads during low traffic.
Separate pools for admin (optional)
Agencies sometimes run a second pool on a different socket for /wp-admin with lower max_children but higher per-request memory. Front-end traffic cannot starve admin during a product import. That is nginx routing work, not a WordPress setting, but it is worth it when five editors share one VPS.
Do not forget the rest of the stack
FPM tuning helps only when PHP is the limiter. Also check:
- OpCache enabled and sized (256 MB+ on busy sites is common)
- Real cron instead of wp-cron on every page view
- MariaDB
innodb_buffer_pool_sizein the same ballpark as your data set - Page cache for anonymous visitors so PHP runs less often
We have seen clients double FPM children when the real problem was a missing index on wp_postmeta. Fix queries first when slow logs point at MySQL.
Where the config lives
On Ubuntu-style VPS: /etc/php/8.x/fpm/pool.d/www.conf (version varies). After edits: sudo systemctl reload php8.x-fpm. On cPanel-managed VPS the UI may expose PHP-FPM settings per domain; still validate with logs, not only the slider in the panel.
Always test reload on staging. A typo in pool config takes down every PHP site on that pool instantly.
Bottom line
PHP-FPM pool tuning is budgeting: how many concurrent WordPress requests your RAM can afford, with spare workers for spikes without starving the database. Measure worker memory under real load, set pm.max_children from math plus margin, watch FPM logs for max_children warnings, and pair pool changes with caching and database hygiene.
Running a busy WordPress site on a Canadian VPS and not sure if FPM or something else is the ceiling? Talk with Swift Host. We can read your metrics and tell you whether to tune the pool or fix the plugin stack first.