A PHP worker pool is a managed group of PHP processes that wait for requests from a web server and execute PHP code, one request per worker at a time. In servers, and especially in server performance, the pool is the main concurrency limit of a PHP application: the number of workers defines how many PHP requests can run simultaneously, and their memory footprint defines how many fit on the machine.
In current deployments the pool is almost always provided by PHP-FPM, the FastCGI Process Manager. A web server such as Nginx or Apache passes each dynamic request over a socket to the pool, a free worker handles it and returns the response, and the worker then stays alive for the next request, keeping OPcache and other warm state in memory.
How a PHP worker pool handles requests
PHP-FPM runs a master process that reads the pool configuration, opens the listening socket and forks child worker processes. Each worker accepts one FastCGI request, runs the script to completion and becomes free again. Because PHP follows a shared-nothing model, request state is discarded after each request, while code compiled into OPcache is kept in shared memory for all workers.
When every worker is busy, new requests wait in the listen backlog of the socket. Short waits are harmless, but a full backlog leads to rejected connections and 502 or 504 errors from the web server. The pool is therefore a queue with a fixed number of servers, and its behavior under load follows queueing logic: once arrival rate exceeds service capacity, latency grows rapidly.
A server can host several pools, each with its own user, socket, limits and settings. Separate pools isolate sites from each other, so a slow or compromised application cannot consume the workers or files of another.
Process manager modes and key directives
PHP-FPM offers three process manager modes, chosen with the pm directive. They differ in how quickly the pool reacts to load and how much memory it holds when idle.
| Mode | Behavior | Best suited for |
|---|---|---|
| static | Fixed number of workers always running | Dedicated servers with steady load |
| dynamic | Keeps a range of spare workers, grows to a maximum | General hosting with variable load |
| ondemand | Starts workers only when requests arrive | Many low-traffic sites on one server |
The directives below control pool size and recycling. Their correct values depend on available memory, average worker size and request duration, so the ranges shown are starting points, not universal answers.
| Directive | Typical range | Effect |
|---|---|---|
| pm.max_children | 5 to 200 | Hard cap on concurrent PHP requests |
| pm.start_servers | 10 to 25% of max_children | Workers created at startup in dynamic mode |
| pm.min_spare_servers / max_spare_servers | 2 to 20 | Idle workers kept ready in dynamic mode |
| pm.max_requests | 500 to 10,000 | Recycles a worker to contain memory leaks |
| pm.process_idle_timeout | 10 to 60 s | Ends idle workers in ondemand mode |
| request_terminate_timeout | 30 to 300 s | Kills requests that run too long |
Sizing the pool for memory and CPU
The safe upper bound for pm.max_children is the memory available to PHP divided by the average resident memory of one worker. A typical framework application uses roughly 40 to 120 MB per worker, and heavy content management or e-commerce systems may exceed that. Counting shared OPcache memory once, not per worker, gives a more accurate figure.
CPU sets a second limit. If requests are mostly CPU-bound, adding workers beyond about one to two per core only increases context switching. If requests spend much time waiting on databases or remote APIs, more workers per core keep the CPU busy. The right number is where throughput stops rising while latency is still acceptable.
| Symptom | Likely cause | Corrective action |
|---|---|---|
| max_children reached warnings | Pool too small for peak load | Raise max_children if memory allows |
| Swapping or OOM kills | Pool too large for available memory | Lower max_children or reduce worker memory |
| High latency, idle CPU | Workers blocked on slow backends | Fix slow queries, add caching or timeouts |
| Worker memory grows steadily | Leaks in extensions or code | Set or lower pm.max_requests |
Worker pool compared with related performance concepts
The PHP worker pool interacts closely with other performance mechanisms in the server stack. Understanding the boundaries prevents tuning the wrong layer when a site slows down.
| Concept | What it limits or speeds up | Relation to the pool |
|---|---|---|
| Throughput | Requests completed per second | Pool size and request time set its ceiling |
| Connection pool | Reused database connections | Each worker may hold its own connection |
| OPcache | Repeated script compilation | Shared by all workers, shortens each request |
| Resource saturation | A fully used CPU, memory or I/O resource | Oversized pools cause memory saturation |
A frequent consequence is database connection pressure: 150 workers across three servers can open 450 database connections at peak. The database limit must be planned together with the pool size, or a persistent connection pooler placed between them.
Operational pitfalls
- Copying values between servers without measuring worker memory leads to swapping on smaller machines.
- Raising max_children to fix slowness often makes it worse when the real bottleneck is the database.
- Missing request timeouts let a few stuck requests occupy every worker and take the site down.
- One shared pool for many sites lets a single busy site starve all others.
The status page of PHP-FPM, together with the slow log, shows active and idle workers, the listen queue length and the scripts that run longest. Watching these values during real peaks is the most reliable basis for evidence-based tuning.
The simple explanation
A PHP worker pool is like a team of cashiers in a store. Each cashier serves one customer at a time. With more cashiers, more customers are served at once, and the line moves faster.
But every cashier needs space and a register, and the store has only so much room. Hiring too many means they get in each other’s way. The skill lies in choosing just enough workers for the busiest hour without running out of memory, which is the server’s equivalent of floor space.
How it is used in practice
An online store runs on a server with 8 CPU cores and 16 GB of RAM. Monitoring shows that each PHP worker uses about 90 MB, and the database, web server and operating system need around 5 GB. The administrator reserves 10 GB for PHP and sets pm.max_children to 110, with dynamic mode, 20 start servers and pm.max_requests at 1,000.
Before the change, the pool had been capped at 20 workers, and during a seasonal sale the log filled with max_children warnings while customers saw timeouts. After resizing and adding a 60-second request timeout, the next sale ran with peak usage of about 80 workers, no queued requests and stable memory, while the slow log pointed to one report query that the developers later optimized.