Table of Contents
- Introduction
- What is PHP-FPM?
- What is a PHP-FPM Pool?
- What Are My Configuration Options for PHP-FPM Pools (aka PHP Workers)?
- What’s a Good Standardized PHP-FPM Pool Configuration?
- How Can I Tell if a PHP-FPM Pool (aka Website) Is Hitting Its Max Children Limit?
- What Typically Causes a PHP-FPM Pool to Hit Its Max Children Limit?
- How to Prevent a PHP-FPM Pool from Hitting Its Limit
- Conclusion
Content Error or Suggest an Edit
Notice a grammatical error or technical inaccuracy? Let us know; we will give you credit!
Introduction
The reason I’m writing this article is simple: I’ve seen far too many servers hosting multiple sites with inconsistent PHP-FPM pool configurations. In many cases, a single poorly tuned site can bring an entire server to its knees. Instead of constantly explaining why load spikes occur or why all the sites on a server slow down, I want to share a standardized approach to PHP-FPM pool configuration.
By applying consistent, well-reasoned settings, you can isolate problems, keep one site from consuming all the server’s resources, and make troubleshooting much more straightforward.
What is PHP-FPM?
PHP-FPM (FastCGI Process Manager) is the service responsible for handling PHP requests on your server. Whenever a visitor hits a PHP-powered site (WordPress, Laravel, etc.), NGINX forwards that request to PHP-FPM, which assigns it to a worker process. That’s unless you have caching enabled, if there is cache available for the requested URL then the visitor will be served the cached content.
Think of PHP-FPM as a factory:
- If too many workers are allowed, the factory runs out of space, energy, or cash (i.e., memory/CPU).
- The workers are the employees.
- Each request is a customer order.
- If there aren’t enough workers, orders pile up.
What is a PHP-FPM Pool?
A pool is a group of PHP-FPM workers assigned to a particular site or group of sites. Each pool:
- Runs under a system user, it’s important that each site has it’s own system user (important for security).
- Has its own socket or port (so NGINX knows where to send requests).
- Can be tuned separately with worker limits, memory usage, and logging.
This isolation is critical. Without pools, all sites would share a single set of workers — meaning one busy site could overwhelm every other site on the server.
What Are My Configuration Options for PHP-FPM Pools (aka PHP Workers)?
The main options you’ll see in a pool config (/etc/php/*/fpm/pool.d/*.conf
) are:
pm
(Process Manager):static
: Fixed number of workers. Rarely used unless you know exactly how many you need.dynamic
: Common default. Spins up workers as needed up topm.max_children
.ondemand
: Spawns workers only when needed, then kills them after idle time. Not ideal since there is warm up penalty.
pm.max_requests
: Number of requests a worker will handle before being recycled. Prevents memory leaks.pm.max_children
: The maximum number of PHP workers. This is the single most important setting. Too low → requests queue up. Too high → server swaps/crashes.pm.start_server
: Number of workers started when the pool launches.pm.min_spare_servers
: Minimum idle workers kept ready.pm.max_spare_server
: Maximum idle workers allowed
What’s a Good Standardized PHP-FPM Pool Configuration?
While the “perfect” settings depend on your hardware, PHP version, and site workloads, here’s a safe starting point I recommend for most VPS/dedicated setups with multiple sites:
[example.com] user = example # Unique to example.com group = example # Unique to example.com listen = /run/php/php8.2-fpm-example.sock pm = dynamic pm.max_children = 8 pm.start_servers = 2 pm.min_spare_servers = 2 pm.max_spare_servers = 2 pm.max_requests = 5000
Why this works:
pm.max_children = 8
- Limits a site to 8 PHP workers, preventing runaway processes from consuming all memory. For a small brochure site, this is enough to handle backend edits and form submissions without overcommitting resources. but may need turning if a WooCommerce site or using Event Calendars which is known to take up resources.
pm.min_spare_servers = 2
→ Keeps 2 workers always available, reducing latency when handling incoming requests.pm.max_spare_servers = 2
→ Caps idle workers at 2, which is fine for low-traffic sites. For higher-traffic apps, consider setting this higher thanpm.min_spare_servers
to absorb bursts.pm.max_requests = 5000
→ Recycles workers after 5000 requests. This prevents memory leaks while still taking advantage of OPcache, which is stored in shared memory by the PHP-FPM master and persists across worker restarts.
From here, you can standardize across sites (e.g., capping most pools at 8–10 workers). For high-traffic or resource-intensive apps, increase these values carefully while monitoring memory usage.
How Can I Tell if a PHP-FPM Pool (aka Website) Is Hitting Its Max Children Limit?
Even with standardized settings, it’s important to know when a site is bumping into its limits. If a pool regularly reaches pm.max_children
, requests will queue up and visitors may see slow load times or even timeouts. Fortunately, there are several ways to monitor and confirm this.
1. System Monitoring (top / htop / atop)
Tools like htop
or atop
give you a quick look at CPU, RAM, and processes per user. Since each PHP-FPM pool typically runs under its own Linux user, you can filter by user to see if one site is hogging resources.
Examples:
htop
→ Press F5 (tree view), then filter by the pool’s user.top -u site1
→ Show only processes for thesite1
pool user.
If CPU is maxed out or memory is climbing during traffic spikes, it’s a strong indicator that workers are at their limit and requests are queuing.
See the following article for more information.
2. PHP-FPM Status Page
You can enable a per-pool status page to see active processes, idle workers, and queue size in real time.
Step 1 – Add to your pool config:
pm.status_path = /status
Step 2 – Expose it safely via NGINX (with IP restriction or basic auth):
location ~ ^/(status|ping)$ { allow 127.0.0.1; deny all; include fastcgi_params; fastcgi_pass unix:/run/php/php8.2-fpm-site1.sock; }
Step 3 – Query it via curl:
curl http://127.0.0.1/status
Example output:
pool: site1 process manager: dynamic start time: 01/Oct/2025:10:15:32 start since: 540 accepted conn: 231 listen queue: 0 max children reached: 5 active processes: 8 idle processes: 2
listen queue
> 0 → requests are waiting.max children reached
> 0 → pool is hitting its limit.
3. Error Logs
PHP-FPM logs warnings when a pool reaches its max children. Common message:
[WARNING] [pool site1] server reached pm.max_children setting (8), consider raising it
This means all 8 workers were busy, and new requests had to wait.
4. Pool-Specific PHP-FPM Logs
To make debugging easier, configure each pool with its own log file:
In your pool config:
php_admin_value[error_log] = /var/log/php8.2-fpm-site1.log
Example log lines:
[01-Oct-2025 12:45:01] WARNING: [pool site1] server reached pm.max_children setting (8), consider raising it [01-Oct-2025 12:45:02] NOTICE: [pool site1] child 12345 exited with code 0 after 5000 requests [01-Oct-2025 12:45:02] NOTICE: [pool site1] child 12346 started
How to grep for pool warnings:
grep "pm.max_children" /var/log/php8.2-fpm-site1.log
Or watch in real time:
tail -f /var/log/php8.2-fpm-site1.log | grep site1
This makes it clear when a pool is constrained and whether recycling (pm.max_requests
) is working as expected.
With these four methods — process monitoring, the status page, error logs, and dedicated pool logs — you’ll know exactly when a PHP-FPM pool is at capacity and what’s happening inside it.
What Typically Causes a PHP-FPM Pool to Hit Its Max Children Limit?
Hitting worker limits isn’t always a “bad” thing — sometimes it’s expected during traffic bursts. But common causes include:
- Poorly optimized plugins/themes (WordPress especially). Heavy queries or un-cached requests eat up workers fast.
- No caching layer (NGINX microcaching, Redis object cache, or full-page caching missing). Every request hits PHP unnecessarily.
- Too few workers set in the pool (legit traffic exceeds capacity).
- Bots or brute force attacks generating excess PHP requests (e.g., hitting
xmlrpc.php
). - Memory starvation → if you allow too many workers, they all fight for limited RAM, which tanks the server.
How to Prevent a PHP-FPM Pool from Hitting Its Limit
If a site is constantly reaching its pm.max_children
limit, it’s a sign that either the pool is under-provisioned or the site is generating more PHP requests than it should. You can address this in a few ways:
1. Optimize the Application (First Line of Defense)
- Enable caching: Full-page caching (NGINX microcache, Varnish, or a WordPress plugin like WP Rocket) prevents every request from hitting PHP.
- Use object caching: Tools like Redis or Memcached reduce repeated database calls.
- Audit plugins/themes: Some WordPress plugins (e.g., heavy page builders, analytics plugins) can generate dozens of PHP calls per page load. Disable or replace inefficient ones.
Tip: If you cut the PHP requests in half through caching, you effectively double the capacity of your pool without changing server configs.
2. Tune Pool Settings Thoughtfully
- Raise
pm.max_children
cautiously: If you have enough RAM, increasing workers gives the pool more capacity. But always calculate memory per worker (max_children × average worker size
) to avoid swapping. - Adjust spare servers: If bursts are frequent, set
pm.min_spare_servers
andpm.max_spare_servers
slightly higher to keep more workers ready.
3. Balance Traffic Across Sites
On a multi-site server, one pool should never be able to consume all resources. To prevent this:
- Give each site its own pool with sensible caps. (GridPane and other control panels do this already)
- Avoid giving one site an oversized
max_children
setting unless you know it’s justified. - If one site consistently outgrows its pool, consider moving it to a separate server or container.
4. Block Abusive Traffic
Often, limits are hit because of bots or brute-force requests, not real users.
- Block or rate-limit abusive paths (e.g.,
/xmlrpc.php
,/wp-login.php
) in NGINX. - Use fail2ban to block consistent abusers.
- Cloudflare, or a WAF to cut down unnecessary PHP hits.
5. Monitor and Iterate
- Use the status page to see if requests are queuing.
- Check logs for
pm.max_children
warnings. - Track memory usage so you know whether you’re truly at resource limits or just misconfigured.
Conclusion
You don’t just “raise the limits” blindly. You optimize the app, tune pools safely, isolate sites, block garbage traffic, and monitor usage. That way, PHP-FPM pools stay healthy, and one busy site doesn’t drag down the whole server.
Standardizing PHP-FPM pool settings is one of the simplest and most effective ways to keep multi-site servers stable. By setting consistent worker limits, isolating sites into their own pools, and monitoring logs for when limits are reached, you can prevent one busy or misbehaving site from taking down everything else.
From there, you can layer in optimization: caching, database tuning, and selective worker adjustments for high-traffic sites. The key is balance — enough workers to serve traffic efficiently, but not so many that you risk taking the whole server offline.