PHP-FPM Children decide in WordPress whether requests run smoothly or get stuck in the queue. I'll show you how incorrect pm.max_children-values block pages, eat up RAM and how I calculate clean values.
Key points
Before I go any deeper, I will briefly summarize the key messages:
- pm.max_children determines how many simultaneous PHP requests are running.
- Too little Children generate queues, 502/504 and high TTFB.
- Too much leads to RAM bottlenecks, swap and OOM kills.
- Formulaavailable PHP RAM / process size × 0.7-0.8.
- Iterative Tuning with monitoring delivers the best long-term performance.
Why incorrect PHP-FPM Children pages block
Each dynamic WordPress request requires its own Worker, and it is precisely these processes that the pool controls via pm.max_children. If I set the value too low, requests pile up in a queue and the TTFB increases noticeably. If I set the value too high, each child process uses additional RAM and the server switches to swap. Everything slows down in the swap until Apache or Nginx report 502/504 or the OOM killer terminates processes. Healthy throughput is only achieved when the number of children matches the real RAM budget and the load of the project.
The formula for pm.max_children in practice
I start with the simple formula: available RAM for PHP divided by the average size of a child process, multiplied by one Safety factor I determine the RAM per process with ps and the RSS column; for typical WordPress stacks, 50-250 MB is often correct. On a 4 GB server, I reserve memory for Linux, database and cache services, leaving around 1.5-2 GB for PHP remain. For example, if the process average is 100 MB, 2,000 / 100 × 0.75 = 15 children. This number serves as a starting point, which I refine according to load profile, caching and plugin mix.
Start values for typical WordPress setups
For small blogs with 2 GB RAM, 8 children, pm = dynamic and a pm.max_requests of about 800. For medium-sized projects with 4 GB RAM, I set 12 children, start_servers 4, min_spare_servers 4. Large stores with 8 GB RAM or more benefit from 21-40 children; if the load is permanently high, pm = static can ensure constant throughput. I then check the ratio of CPU utilization, RAM usage and response times in order to make fine adjustments. If you want to delve deeper, you can find background information at optimal PHP-FPM settings.
Measuring processes: How to determine RAM requirements
I first determine the live size of the processes before I set values, because crystal balls don't help here and cost money. Performance. The command ps -ylC php-fpm -sort:rss returns the RSS sizes, which I monitor over a few minutes. Processes often grow during updates or cron jobs, which is why I include spikes in the calculation. I also use htop and free -h to check the RAM reserves and the amount of swap. I use this data to determine a reliable average and select a conservative safety factor.
Important parameters at a glance
In addition to pm.max_children, other pool options determine how cleanly WordPress processes requests and how well it frees up memory, which noticeably reduces the Stability pm regulates the mode: dynamic adjusts the number of processes to the load, static keeps a fixed number. pm.max_requests prevents memory bloat by restarting processes after X requests. request_terminate_timeout protects against hang-ups caused by faulty or slow scripts. With this set, I cover 90 percent of real practical cases.
| Parameters | Function | WordPress recommendation |
|---|---|---|
| pm | Process control mode | dynamic for variable load; static for permanently high traffic |
| pm.max_children | Maximum number of simultaneous workers | Available PHP RAM / process size × 0.75 |
| pm.max_requests | Recycling of processes | 300-1,000; rather lower with WooCommerce |
| request_terminate_timeout | Cancellation of long-running requests | 60-120 seconds against hangers |
Dynamic, ondemand or static - which mode is right for you?
I select the mode to match the load profile: dynamic is my default, because it flexibly adjusts the number of active processes and thus saves RAM when there is little going on. static I use it when the load is constant and I need hard commitments for latency and throughput - during campaigns or sales, for example. ondemand is suitable for servers with long idle phases: Processes are only created when required and terminated again after inactivity. The trade-off is cold starts; the first request per new process feels slower. For ondemand I set pm.process_idle_timeout cleanly (e.g. 10-20s), with dynamic I keep start_servers, min_spare_servers and max_spare_servers tight so that the pool scales quickly but does not „bloat“.
Configuration example for your pool
On Debian/Ubuntu, the pool file is usually located under /etc/php/8.x/fpm/pool.d/www.conf, which gives me a clear Structure for customizations. I set pm to dynamic, anchor a realistic value for pm.max_children and keep spare server tight. I set the recycling to 500 to cut leaks and RAM increases early on. After each change, I test the load and plug bottlenecks before increasing the values further. For background information on limit values, the insight at Optimize pm.max_children.
pm = dynamic
pm.max_children = 15
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 500
request_terminate_timeout = 90s
Multiple pools, sockets and clean insulation
For multiple projects or clearly separated roles (frontend vs. admin/REST), I set up separate pools with its own user and socket. This way, each pool limits its own children and one outlier does not block the rest. On a host I prefer Unix sockets compared to TCP (listen = /run/php/site.sock) - lower latency, less overhead. I use TCP between Nginx/Apache and PHP-FPM on different hosts/containers. I use listen.owner, listen.group and listen.mode consistent and, if necessary, raise listen.backlog so that short load peaks do not result in connection errors. With a dedicated admin pool, I can create a tighter request_terminate_timeout drive and pm.max_requests lower without slowing down the caching-strong frontend pool.
Recognize symptoms and react correctly
If the error log regularly states „server reached pm.max_children“, the pool is limiting the Parallelism and I increase it moderately. If 502/504 occur with high swap utilization at the same time, I reset pm.max_children and lower pm.max_requests. If the CPU increases with low RAM usage, then queries or PHP logic are usually blocking; I optimize the database and caching. If requests get stuck, a stricter request_terminate_timeout and log analysis with timestamps helps. I check conspicuous peaks against cronjobs, search indexes and admin actions.
FPM status and slowlog: precise diagnosis
I activate the Status per pool (pm.status_path) and read key figures such as active processes, max children reached, listen queue and max listen queue off. A permanently growing list queue clearly shows: too few children or blocking backends. I also set request_slowlog_timeout (e.g. 3-5s) and a slowlog-path. This is how I see stack traces of requests that are dawdling - often external HTTP calls, complex WooCommerce queries or image manipulations. With catch_workers_output warnings from the workers are collected in the logs. Based on this data, I decide whether more parallelism helps or whether I need to solve bottlenecks in the code/DB.
Monitoring: 3-5 days clean evaluation
After tuning, I observe load peaks over several days, because short-term fluctuations deceive. I log RAM, swap, 502/504, TTFB and the number of active processes in FPM status. Below 80 percent RAM utilization without swap and without queues, I am correct. If bottlenecks occur during actions such as checkout, search or imports, I specifically adjust pm.max_children and pm.max_requests. Each step is carried out in small adjustments and with a new measurement.
Memory calculation in detail: RSS, PSS and shared memory
The process variable is tricky: RSS (Resident Set Size) also contains shared segments such as OPcache and libraries. I therefore quickly overestimate the RAM consumption when I simply calculate „RSS × Children“. Better is the PSS-view (Proportional Set Size), which distributes shared memory fairly across processes - tools such as smem help here. The calculation includes OPcache (e.g. 256 MB + strings), APCu (e.g. 64-128 MB) and the master process. The PHP memory_limit is not an average, but the upper limit per request; individual peaks may occur, but the average value counts. I plan a buffer so that spikes, deployments and cronjobs do not immediately trigger swaps, and leave pm.max_requests consciously to limit memory bloat.
Reduce WordPress-specific load
I reduce PHP load first before increasing children further, because a faster cache hit rate saves real RAM. Full page caches drastically reduce PHP requests, which creates capacity for checkout, search and admin. OPcache with memory_consumption of 256 MB accelerates the bytecode and relieves the pool. In practice, I keep the PHP memory_limit at 256M so that individual plugins do not slow down the server. More insight into bottlenecks can be found in the guide PHP workers as a bottleneck.
Database and cache backends in balance
Every PHP worker potentially generates a Database connection. If I increase pm.max_children, the simultaneous DB load also increases. I therefore check MySQL/MariaDB: max_connections, buffer (innodb_buffer_pool_size), and the query planner. Redis/Memcached must keep up in parallel - maxclients, memory limit and latencies. A WordPress instance with 20 active children can easily saturate the DB if several expensive queries are running in parallel. That's why I tune the DB (indexes, slow queries) and set to persistent object caches, before I release further children. This increases throughput without overloading the backend.
WooCommerce, Cron and Admin: Special cases
Stores generate more simultaneous dynamic requests, which is why I use something air with pm.max_children. At the same time, I tend to lower pm.max_requests in order to continuously reduce memory bloat. For imports and cronjobs, I plan additional budget or execute tasks outside of peak hours. The admin area often spikes at short notice; caching provides less protection here, so efficient pool control counts. If there are signs of queues, I increase in small steps and monitor the metrics immediately afterwards.
Containers, vCPU quotas and OOM traps
In containers and VMs, the focus is on the effective RAM limit (cgroups), not on the host. I therefore calculate pm.max_children from the allocated limit and not from „free -h“. Container OOMs are merciless - the kernel terminates processes hard. CPU quotas also count: More children don't help if 1-2 vCPUs limit the computing time. As a rule of thumb, I scale IO-heavy WordPress workloads to around 2-4× the number of vCPUs; above this, context switches increase, but not the real throughput. In orchestrated environments, I roll out changes conservatively, observe pod restarts and keep readiness/liveness probes so that short warmup phases of FPM do not count as a failure.
Sources of error that are often overlooked
Many problems do not stem from the pool, but from Plugins, that multiply requests or generate long processes. Indexed searches, broken crawler rules and excessive heartbeat intervals drive up the load. I therefore always check the logs, query monitor and caching headers first. If the load only occurs with certain URLs, I interpret this as an indication of plugin or template bottlenecks. Only when these issues have been resolved do I scale Children further.
Understanding sessions, admin AJAX and locks
WordPress/plugins work partly with Sessions. File-based session locks can make requests serialized - a single slow request blocks the rest of the same session ID. I keep session usage lean and check whether admin AJAX bursts (wp-admin/admin-ajax.php) fire unnecessarily frequently. The heartbeat should be throttled sensibly, otherwise it generates load without added value. If locks or long file accesses occur, more parallelism will not help; caching, faster storage I/O or a different session handler will help here. I can recognize such patterns in logs by many similar requests starting at the same time with unusually long runtimes.
Nginx, Apache and FastCGI timeouts at a glance
The web server also sets limits, which I have to coordinate with the FPM values, otherwise they will fizzle out. Tuning. With Nginx, I pay attention to fastcgi_read_timeout and sufficient worker processes. Under Apache, I check mpm_event, keepalive settings and proxy timeouts. If these limits are not correct, users report timeouts even though FPM still has capacity. Uniform time budgets keep the path from the client to PHP consistent.
Rollout strategy, tests and operation
I roll out changes to pm.max_children step by step and test them under real load. A reload of FPM (graceful) takes over configurations without breaking the connection. Before larger jumps, I simulate peaks (e.g. sale start) and observe the following listen queue, CPU, RAM, 95th-99th percentile of latency and error rates. I document the assumptions made so that later team members understand why a value is chosen. I set alarms for: swap > 0, „max children reached“ in the status, increasing 502/504, and DB latency. This ensures that the platform remains stable even months later when the traffic and plugin mix changes.
Briefly summarized
Incorrectly set PHP-FPM-children slow down WordPress, either in queues or in the RAM limit. I determine the process size, reserve memory for system services and set pm.max_children with buffer. Then I check pm.max_requests, request_terminate_timeout and the mode pm = dynamic or static according to the load profile. Caching, OPcache and clean plugins noticeably reduce the number of PHP requests. If you implement these steps consistently, you will keep your pages responsive and your server reliable.


