Key Takeaways
pm.max_children controls how many requests your server can handle simultaneously. Too high causes OOM crashes, too low causes 503 errors
The correct value is calculated from available RAM divided by average PHP process size, not guessed
Dynamic process management is correct for most WordPress sites. Static is only better under constant high traffic
Default PHP-FPM settings are designed for safety, not performance. They leave most of your server's capacity unused
Monitoring actual process usage over 24-48 hours tells you whether your settings are right
The setting that controls everything
There's one PHP-FPM setting that determines whether your WordPress site handles traffic smoothly or crashes under load: pm.max_children.
This setting controls the maximum number of PHP worker processes that can run simultaneously. Each incoming request to your WordPress site gets assigned to one worker. That worker processes the request (runs PHP, queries the database, generates HTML) and becomes available for the next request when it's done.
If all workers are busy and a new request arrives, the request waits in a queue. If the queue is full, the visitor gets a 503 error.
If pm.max_children is set too low, your server has idle RAM while visitors get 503 errors. If it's set too high, PHP workers consume all available memory and the server starts swapping to disk or the OOM killer terminates processes. Both of these crash your site.
Most WordPress sites run the default PHP-FPM settings: pm.max_children = 5 on many distributions. That's 5 simultaneous requests. On a 4GB server that could handle 30-40 workers, you're using 12% of your capacity.
Here's how to calculate the right number.
Step 1: measure your PHP process size
Every PHP-FPM worker uses memory. The amount depends on your WordPress installation: how many plugins are loaded, how complex your theme is, whether you're running WooCommerce, and what the request is doing.
Start by checking what your current workers use:
ps --no-headers -o rss,pid -p $(pgrep -d, php-fpm) | sort -rnThis shows each PHP-FPM process and its memory usage in KB. You'll see a range. A typical output:
65432 12345
62108 12346
58944 12347
55780 12348
54200 12349 <- master process (smaller)
Calculate the average worker size (exclude the master process, it's always smaller):
ps --no-headers -o rss -p $(pgrep php-fpm) | \
awk '{ sum += $1; n++ } END { print sum/n/1024 " MB" }'Typical ranges:
| WordPress setup | Average worker size |
|---|---|
| Simple blog, few plugins | 30-45 MB |
| Business site, 10-15 plugins | 45-65 MB |
| WooCommerce, 20+ plugins | 65-120 MB |
| WooCommerce + page builder + heavy plugins | 100-180 MB |
Important: measure this under real traffic, not on an idle server. An idle worker uses less memory than one actively processing a WooCommerce checkout request. Visit several pages on your site (including WooCommerce pages if applicable) to warm up the workers, then measure.
Step 2: calculate pm.max_children
The formula:
max_children = (Total server RAM - reserved memory) / average worker size
Reserved memory includes:
- Operating system: 200-300MB
- MariaDB/MySQL: check with
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';plus ~100MB overhead - Redis (if used): check your
maxmemorysetting - Other services: monitoring agents, backup tools, anything else running on the server
Example calculation
Server: 4GB RAM (4096 MB)
| Component | Memory |
|---|---|
| OS and system | 300 MB |
| MariaDB (buffer pool + overhead) | 500 MB |
| Redis | 128 MB |
| Other (monitoring, SSH, etc.) | 100 MB |
| Available for PHP-FPM | 3068 MB |
Average PHP worker size: 65 MB (measured on a WooCommerce site with 15 plugins)
max_children = 3068 / 65 = 47.2
Round down and leave a 10% buffer: set pm.max_children = 42
That's a big jump from the default of 5. On this server, the default left 37 potential workers sitting unused.
Don't over-provision
It's tempting to set max_children right at the calculated limit. Don't. Leave a 10-15% buffer for:
- Temporary memory spikes during WooCommerce checkout
- MariaDB needing extra memory for large queries
- OPcache using more memory as you add plugins
- WordPress cron running resource-heavy tasks
Running at 100% capacity means any spike in request size or a memory-intensive plugin causes the server to swap or crash.
Step 3: configure the related settings
pm.max_children doesn't work in isolation. The other pm.* settings control how PHP-FPM manages workers within that limit.
For pm = dynamic (recommended for most WordPress sites)
[www]
pm = dynamic
pm.max_children = 42
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500pm.start_servers: how many workers to start when PHP-FPM launches. Set this to ~25% of max_children. These workers are ready immediately without the latency of spawning new ones.
pm.min_spare_servers: minimum idle workers to keep running. When the number of idle workers drops below this, PHP-FPM spawns new ones. Set to ~10-15% of max_children.
pm.max_spare_servers: maximum idle workers. When idle workers exceed this, PHP-FPM kills the extras to free memory. Set to ~35% of max_children.
pm.max_requests = 500: after a worker handles 500 requests, PHP-FPM kills it and starts a fresh one. This prevents memory leaks from poorly written plugins from accumulating indefinitely. Set between 200-1000. Lower values use slightly more CPU (more process restarts) but are safer against memory leaks.
For pm = static (high-traffic sites with consistent load)
[www]
pm = static
pm.max_children = 42
pm.max_requests = 500Static mode keeps all 42 workers running at all times. No spawning latency, no scaling decisions. You use the full memory allocation permanently.
Use this only if:
- Your traffic is consistently high (not bursty)
- You can afford the constant memory usage
- You've verified the server doesn't swap under full worker allocation
For pm = ondemand (low-traffic sites saving memory)
[www]
pm = ondemand
pm.max_children = 42
pm.process_idle_timeout = 10s
pm.max_requests = 500Ondemand starts with zero workers and spawns them only when requests arrive. Workers shut down after process_idle_timeout seconds of inactivity.
This saves memory on sites with long idle periods but adds 50-100ms latency for the first request after idle (the time to spawn a worker). Not ideal for sites where speed matters for every request.
Step 4: PHP configuration for WordPress
Separate from PHP-FPM's process management, the PHP runtime settings also affect performance. Create a custom php.ini or add overrides:
; Memory limit per worker
memory_limit = 256M
; OPcache (compiled PHP bytecode cache)
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 60
opcache.validate_timestamps = 1
; Upload limits (adjust for WooCommerce product images)
upload_max_filesize = 64M
post_max_size = 64M
; Execution time
max_execution_time = 300
max_input_time = 300
; Realpath cache (reduces filesystem stat calls)
realpath_cache_size = 4096K
realpath_cache_ttl = 600OPcache explained
OPcache stores compiled PHP bytecodes in shared memory. Without OPcache, PHP parses and compiles every .php file on every request. With OPcache, the compiled version is reused.
The impact on WordPress is significant. WordPress loads 50-100+ PHP files per request (core, theme, plugins). OPcache eliminates the parsing overhead for all of them.
opcache.max_accelerated_files = 10000: the number of PHP files OPcache can cache. WordPress with 20 plugins easily has 3,000-5,000 PHP files. Set this higher than your total file count.
opcache.revalidate_freq = 60: how often (in seconds) OPcache checks if a file has changed. On production, set to 60 or higher. OPcache skips the filesystem check for 60 seconds, reducing stat calls. When you deploy code changes, clear OPcache manually or restart PHP-FPM.
Step 5: monitor and adjust
The calculations from Step 2 are a starting point. Real-world traffic patterns, plugin behavior, and seasonal load variations mean you should monitor and adjust over time.
Check for max_children warnings
grep "max_children" /var/log/php-fpm.logOr on systems using journald:
journalctl -u php-fpm --since "24 hours ago" | grep "max_children"The line you're looking for:
WARNING: [pool www] server reached pm.max_children setting (42), consider raising it
If you see this during normal traffic, increase pm.max_children (and make sure you have the RAM). If you only see it during traffic spikes, your setting is probably appropriate and the queue handled the burst.
Check PHP-FPM status page
Enable the status page in your www.conf:
pm.status_path = /fpm-statusRestrict access in your web server config (Caddy, Nginx) to localhost or your IP only, then query it:
curl -s http://localhost/fpm-statusKey metrics in the output:
active processes: workers currently handling requestsidle processes: workers waiting for requestslisten queue: requests waiting for a worker (should be 0 most of the time)max listen queue: highest queue length since last restart (should be low)max children reached: how many times max_children was hit (should be 0 or very low)
Monitor memory usage over time
Track your server's memory usage over 24-48 hours:
# Quick snapshot
free -m
# Watch over time (every 60 seconds)
while true; do
echo "$(date): $(free -m | awk 'NR==2{print $3"/"$2" MB"}')"
sleep 60
done >> /var/log/memory-usage.logIf available memory stays above 200MB under peak traffic, your settings are safe. If it dips below 100MB regularly, reduce pm.max_children or add RAM.
Before and after: what tuning actually does
On a client site running on a 4GB VPS with WooCommerce and 18 active plugins:
Before (default PHP-FPM settings)
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3Under a load test simulating 30 concurrent users:
- Average TTFB: 1.8 seconds
- Requests hitting queue: 83%
- 503 errors: 12% of requests
- Server memory usage: 1.1 GB of 4 GB (72% idle RAM)
After (tuned PHP-FPM settings)
pm = dynamic
pm.max_children = 35
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500Same load test, 30 concurrent users:
- Average TTFB: 0.3 seconds
- Requests hitting queue: 0%
- 503 errors: 0%
- Server memory usage: 3.1 GB of 4 GB (healthy headroom)
TTFB dropped from 1.8 seconds to 0.3 seconds. Same server. Same WordPress site. Same traffic. The only change was telling PHP-FPM to use the memory that was already there.
Common mistakes
Setting max_children based on CPU cores
Some guides suggest setting max_children to 2x or 4x your CPU cores. This ignores memory entirely. A 1-core VPS with 4GB RAM can handle more PHP workers than a 4-core VPS with 1GB RAM. Memory is the constraint, not CPU.
Using pm = static on a server that also runs the database
Static mode allocates all workers at startup. If MariaDB needs extra memory for a large query or buffer pool growth, there's nothing left. Use dynamic on shared-resource servers so idle workers can be killed to free memory when needed.
Never restarting PHP-FPM workers
Without pm.max_requests, workers run indefinitely. PHP plugins with memory leaks (and there are many) cause workers to grow from 60MB to 200MB+ over time. Eventually the server runs out of memory. Set pm.max_requests between 200-500 to automatically recycle workers.
Tuning PHP-FPM before fixing the database
If your database queries take 2 seconds each, a PHP worker is occupied for 2 seconds per request. You need 30 workers to handle 15 requests per second. After cleaning the database and adding indexes, those queries take 0.1 seconds. Now you need 2 workers for the same 15 requests per second. Fix the data first, then tune the server.
Related reading
- WordPress on Docker: A Production-Ready Stack. The complete containerized stack where these PHP-FPM settings are applied.
- How to Find and Fix Slow MySQL Queries in WordPress. Fix database bottlenecks before tuning PHP-FPM. Slow queries make PHP workers wait.
- WordPress Slow After 2 Years? Here's What's Actually Wrong. The database cleanup that should happen before any server tuning.
- How to Clean Your wp_options Table (The Right Way). Reduce the data WordPress loads into PHP memory on every request.

Written by
Barry van Biljon
Full-stack developer specializing in high-performance web applications with React, Next.js, and WordPress.
Ready to Get Started?
Have questions about implementing these strategies? Our team is here to help you build high-performance web applications that drive results.
