Skip to content
Products
AI Website Builder New VPS Hosting Cloud Servers Web Hosting Dedicated Servers Domains
Company
About Documentation Support Center Contact Get Started Call +91 75795 45488
Login
Hosting Panel — cPanel & Billing Console Panel — VPS Management
ALL SYSTEMS OPERATIONAL
VPS

PHP-FPM Pool Tuning: Fix pm.max_children on a VPS

Getwebup 6 min read

If your VPS runs fine at low traffic but starts throwing 502s or just hangs the moment visitors spike, the culprit is often not Nginx or MySQL — it's a handful of PHP-FPM settings left at their install defaults. Here's how to actually size pm.max_children and pick the right process manager mode, instead of guessing and restarting the service until it "feels" better.

Symptom: Site Slows Down or 502s Under Load

The pattern usually looks like this: the site is snappy with a handful of visitors, then during a traffic spike, a product launch, or a crawler hitting every page at once, requests start queuing. Pages take 10-20 seconds to load, then start failing outright with 502 Bad Gateway or 504 Gateway Timeout. Restarting PHP-FPM or Nginx "fixes" it for a few minutes, then the same thing happens again once traffic returns.

Check your PHP-FPM error log for the smoking gun:

tail -f /var/log/php8.3-fpm.log

If you see a line like this, you've found it:

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

That message means every single PHP worker process was busy at the same time, so new requests sat in a queue until one freed up — or timed out and Nginx returned a 502 because nothing answered in time.

Cause: pm.max_children Isn't Sized to Your Server

PHP-FPM doesn't run one process per request — it keeps a pool of worker processes alive and hands each incoming PHP request to a free one. pm.max_children is the hard ceiling on how many of those workers can exist at once. The default shipped by most distros (often 5) was picked for a tiny reference server, not your actual RAM.

Two opposite mistakes cause the same symptom:

  • Set too low — the pool fills up under normal traffic, requests queue, and you get slow pages followed by 502/504 errors even though the server has RAM to spare.
  • Set too high — every worker actually spawns during a spike, memory runs out, the OOM killer starts terminating processes (often MySQL, since it's the biggest target), and the whole stack falls over instead of just slowing down.

The right number depends on how much RAM one PHP process actually uses, which varies a lot by app. A stock WordPress page load might use 40-60MB per worker; a WooCommerce checkout or a Laravel app with a heavier bootstrap can use 100-150MB or more.

Fix: Calculate and Set the Right Values

Step 1 — Find your average worker memory

With PHP-FPM running under normal load, check actual process memory usage:

ps --no-headers -o "rss,cmd" -C php-fpm8.3 | awk '{ sum+=$1; count++ } END { print sum/count/1024 " MB average" }'

This averages the resident memory (RSS) across every live php-fpm worker. Run it a few times under real traffic — a single sample right after a restart will read artificially low, since workers grow as they handle their first few requests.

Step 2 — Check available RAM

free -h

Use the "available" column, not "total." Subtract what MySQL, Nginx, Redis, or anything else on the box already reserves — don't size PHP-FPM as if it owns the whole server.

Step 3 — Calculate pm.max_children

The formula is simple:

pm.max_children = (RAM available to PHP-FPM in MB) / (average worker memory in MB)

Example: a 4GB VPS running WordPress, with MySQL and Nginx already reserving about 1.5GB, leaves roughly 2500MB for PHP-FPM. If each worker averages 50MB, that's 2500 / 50 = 50 — so pm.max_children = 50 is a reasonable ceiling, not the default 5.

Step 4 — Edit the pool config

Pool settings live per-PHP-version, not in the main php.ini. On Ubuntu/Debian:

sudo nano /etc/php/8.3/fpm/pool.d/www.conf

On a cPanel/WHM server using MultiPHP, the equivalent is under WHM > Service Configuration > PHP-FPM, or per-domain in MultiPHP FPM Configuration rather than a raw file.

Set values scaled to your calculation. For the example above:

pm = dynamic
pm.max_children = 50
pm.start_servers = 12
pm.min_spare_servers = 8
pm.max_spare_servers = 16
pm.max_requests = 500

pm.start_servers and the spare-server values should sit comfortably below max_children — they control how many idle workers stay warm, not the ceiling. pm.max_requests recycles a worker after that many requests, which helps paper over small memory leaks in long-running PHP extensions.

Step 5 — Restart and verify

sudo systemctl restart php8.3-fpm
sudo systemctl status php8.3-fpm

Confirm the new values are live:

sudo php-fpm8.3 -tt 2>&1 | grep max_children

Then watch the log during your next traffic spike — the "server reached pm.max_children" warning should stop appearing.

Which Process Manager Mode Should You Use?

pm controls how workers are spawned, and picking the wrong one wastes either RAM or response time:

ModeBehaviorBest for
staticExactly pm.max_children workers running at all timesHigh, steady traffic where you want zero spawn delay and have RAM to spare permanently
dynamicWorkers scale between min/max spare values based on loadMost sites — balances memory use against traffic spikes
ondemandWorkers spawn only when a request arrives, and idle ones are killed after pm.process_idle_timeoutLow-traffic sites, dev/staging boxes, or a VPS running many small sites where idle memory matters more than spawn latency

If you're hosting several low-traffic client sites on one VPS, ondemand often frees up enough RAM to let you safely raise pm.max_children on the sites that actually need it.

Prevention: Catch It Before Visitors Do

  • Enable the slow log — set slowlog and request_slowlog_timeout = 5s in the pool config to capture exactly which PHP script is holding a worker too long, instead of guessing during the next incident.
  • Watch the FPM status page — enable pm.status_path = /status and poll it with your monitoring tool to graph active vs. idle workers over time, so you see the pool filling up before it maxes out.
  • Re-run the sizing math after any plugin, theme, or framework upgrade — a heavier page builder or a new caching layer can quietly double average worker memory, which shifts the safe max_children number.
  • Add swap as a backstop, not a fix — a small swap file keeps an oversized pool from getting OOM-killed outright while you fix the real numbers, but PHP swapping under load is still slow. Treat it as insurance, not a solution.
  • Re-check after a VPS resize — if you upgrade your plan for more RAM, the pool config doesn't update itself. Redo Step 1-3 to actually use the extra memory instead of leaving the old ceiling in place.

Getting these values right once, based on your actual RAM and actual per-request memory, is what turns "the site falls over every time it gets popular" into a server that just quietly handles the spike.

Frequently asked questions

How do I know if pm.max_children is set too low?

Check /var/log/php*-fpm.log for a line reading 'server reached pm.max_children setting, consider raising it'. If that appears during traffic spikes alongside 502 or 504 errors, your pool is filling up and requests are queuing or timing out.

What happens if I set pm.max_children too high?

If every worker actually spawns during a spike, total PHP memory use can exceed available RAM. The Linux OOM killer then starts terminating processes to free memory - often MySQL or another service, not just PHP-FPM - which can take the whole site down instead of just slowing it.

Should I use pm = static or pm = dynamic?

Use dynamic for most sites - it scales worker count with traffic and reclaims idle memory. Use static only if you have high, steady traffic and want to eliminate the small delay of spawning a new worker. Use ondemand on low-traffic sites or when running several small sites per VPS to minimize idle RAM use.

Do these settings apply the same way on a cPanel VPS?

The concept is identical, but you edit it through WHM instead of a raw pool file - go to WHM > MultiPHP FPM Configuration and adjust the per-domain pool settings there rather than /etc/php/*/fpm/pool.d/www.conf.

Why did this start happening after I upgraded my VPS plan?

Pool settings don't scale automatically when you resize a VPS. If you doubled your RAM but never revisited pm.max_children, PHP-FPM is still capped at the old ceiling and isn't using the extra memory you're now paying for.

#php-fpm #vps #nginx #performance-tuning #502-bad-gateway

Keep reading

Chat with Support