...

PHP-FPM Capacity Planning: Calculating Workers, RAM, and Concurrent Requests

To plan PHP-FPM capacity effectively, first calculate how many workers will fit within the available RAM budget. Separately, estimate how many workers are needed to handle the traffic actually arriving at the pool. pm.max_children It limits the number of workers available simultaneously per pool, but it is neither a visitor count nor a direct throughput value. CPU, database, and peak loads also determine which setting is viable in production.

What Limits a PHP-FPM Worker

PHP-FPM manages PHP processes in pools. A Worker processes one request at a time; the pool setting pm.max_children Limits the number of workers available at any given time and, consequently, the number of PHP requests that this pool can process simultaneously. If there are multiple pools, the limit applies to each individual pool, not automatically to the entire server.

This number is neither the number of logged-in visitors nor a direct measure of requests per second. A visitor can trigger multiple requests, while a logged-in user does not occupy a worker between two actions. Conversely, a single slow request can tie up a worker for longer than several short requests in a row.

Not every page view reaches PHP-FPM either. If the web server serves a static file directly or if an upstream cache fully fulfills a request, a PHP worker may not be necessary, depending on the architecture. Cache misses, logged-in users, and administrative pages, on the other hand, can still generate PHP requests and temporarily increase resource utilization.

For capacity planning, therefore, two separate questions are crucial: How many workers, including a reserve, can fit into the available RAM? And how many workers does the traffic actually arriving at the pool require so that requests do not have to wait excessively long? A high value for pm.max_children does not answer any of these questions on its own.

Even if sufficient memory is available, the CPU, database, or external services may limit the practical capacity. While additional workers allow for more parallel PHP processing, they can simultaneously increase the load on an already slow service. The pool value is therefore an operational limit and not a performance guarantee.

Understanding Process Managers and Queues

The Process Manager Specifies when PHP-FPM provisions workers. When static the reserved process count corresponds to the value of pm.max_children. This makes it easy to plan the number of processes, but it also ties up resources even when the system is underutilized. The upper limit itself must still be consistent with the available memory.

At dynamic FPM adjusts the number of processes: The initial count, as well as the minimum and maximum number of free workers, are also set. When ondemand Workers are created only when needed and are terminated after a configured idle period. This can save resources in pools that are rarely used; however, in the event of a sudden load spike, new processing capacity must first be created.

Comparison of Process Managers
ModeImportant SettingsApplication and Consideration
staticpm.max_childrenFixed number of workers; easy to plan, but resources are tied up when idle.
dynamicpm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_serversAdapting to changing demand; idle and newly started workers must be coordinated.
ondemandpm.max_children, pm.process_idle_timeoutWorkers on demand; saves resources in rarely used pools; process starts must be taken into account when the load increases.

If all workers are busy, additional requests can be placed in the Listener Queue wait for a free process. The FPM status shows, among other things, the current and maximum queue lengths. However, a single high maximum value does not indicate whether the pool is currently overloaded; to determine that, it needs to be viewed over time.

A larger queue does not create additional workers or speed up a slow database. It can temporarily absorb requests, but merely shifts the wait time to before PHP processing. Therefore, both the typical load pattern and the consequences of short spikes are important when deciding on a process manager.

Classifying Worker Memory Correctly

A worker's memory requirements cannot be reliably determined based solely on a PHP configuration limit. Process data collected under appropriate load conditions is more helpful for planning purposes. It makes a difference whether you are looking at the resident memory of a single process or trying to estimate the memory usage of an entire group of PHP-FPM workers.

RSS describes a process's resident memory. If multiple workers use the same memory pages, those pages may be included in each worker's RSS. If you add up the RSS values of all workers, shared pages may appear multiple times; therefore, the sum does not provide an accurate picture of the physical memory shared among them.

PSS distributes shared pages proportionally among the participating processes. The sum of the PSS values for a process group is therefore often more useful for its memory planning than the sum of the RSS values. Nevertheless, it remains a snapshot: If the number of processes sharing pages changes, their respective shares also change.

Consider multiple workers for both typical and memory-intensive requests. A newly started process or a single average may underestimate future demand. The total pool PSS for different numbers of workers and load conditions helps derive a conservative estimate; extrapolating this to more workers remains uncertain due to variable shared and private portions.

The PHP-memory_limit is not a reliable indicator of the total operating system memory used by an FPM process and does not replace this observation. You should not automatically deduct shared memory—such as that used by OPcache—a second time from the budget if it is already included proportionally in the used pool PSS. Other services and a reserve must still be taken into account.

Calculate RAM Budget and Number of Workers

The starting point is the actual available RAM: on a host, this is the memory available for services; in a container, it is the effective memory limit. Subtract from this the memory requirements of the operating system, the web server, any local database, and other services. Other FPM pools and a reserve for fluctuating usage should also be factored into the calculation.

An AI-generated stock image of a removed RAM module on an antistatic workshop mat.
AI-generated stock image: The amount of available RAM sets a limit on the budget for PHP-FPM pools.

The remaining portion is the Pool Budget. To estimate the number of workers, observe the PSS sum of the pool workers at various process counts and load conditions. Take into account both typical requests and memory-intensive endpoints. Divide the respective sum by the number of workers under consideration and derive a conservative estimate based on multiple observations.

The following calculation is an illustrative example; it is not a measurement. The values used are intended solely to demonstrate the calculation process; they are not a recommendation for a specific application. The result is rounded down to the nearest whole number of workers.

Illustrative RAM Planning for a Single Pool
Calculation stepAcceptanceClassification
Effective RAM Limit4,096 MiBHost or Container Boundary in the Example
Other Services, Pools, and Reserves2,048 MiBJointly Agreed Deduction
Budget for the pool in question2,048 MiB4,096 minus 2,048 MiB
PSS Approximate Value per Worker80 MiBAssumed conservative planning value
Calculated RAM Limit25 workers2,048 divided by 80, rounded down

The 25 workers do not guarantee a safe allocation. PSS allocates shared pages proportionally, and this allocation changes based on the number of processes sharing those pages. A different request mix can also affect private memory requirements; therefore, the projection should be verified against the observed total consumption and the existing limit.

If shared memory such as OPcache is already included proportionally in the PSS total used, do not deduct it again as a lump sum for the entire amount. Nevertheless, be sure to allow for a reserve to account for memory usage that your analysis does not capture. Especially with a container limit, what ultimately counts is the total consumption of all processes within that limit—not just the estimated total worker count.

A newly started worker alone is not very meaningful as a basis: It may not have processed any demanding requests yet. Therefore, compare multiple time points and note which endpoints and how many workers were involved in each case. This will help you determine whether the chosen approximation holds true even under less favorable but realistic operating conditions.

Estimate the number of workers needed based on requests

The RAM calculation estimates how many workers can likely be accommodated. In contrast, actual demand is determined by how many PHP requests actually arrive in the pool in question and how long they keep a worker occupied. As a first approximation, the average number of workers occupied simultaneously equals the number of PHP requests per second multiplied by the average worker occupancy time in seconds.

Illustrative calculation example, not a measurement: Assuming 20 PHP requests per second and an average processing time of 0.25 seconds, an average of five workers are busy. This is an average over the period under consideration. It does not imply that exactly five processes are always running, nor that five is sufficient as a pool limit for brief spikes.

When calculating the number of requests, do not include all page views without verification. If a cache returns many responses without calling PHP, these hits do not put a strain on the pool. Conversely, cache misses, logged-in visitors, and administrative accesses can sometimes trigger more PHP processing than a daily average would suggest.

The Worker Time Recorded also includes wait times during processing, such as when waiting for a database or an external service. It is not automatically equivalent to the response time visible in the browser. According to fastcgi_finish_request() PHP may have already finished sending the response to the client but still be performing additional work in the worker.

To ensure acceptable wait times, you need some headroom above the average utilization. How much headroom is needed depends, among other things, on load fluctuations, slow endpoints, and the desired response time target. Multiplying the peak requests per second by a high latency percentile can serve as a conservative scenario check, but it is not a precise queueing forecast.

Therefore, consider several time periods separately: A brief peak in activity and quiet nighttime traffic can result in the same daily average as more consistent operations, but they place different demands on the system. If the occupancy time increases due to a slow backend, the average number of tied-up workers also increases even if the arrival rate remains the same. The demand is therefore not solely a function of visitor volume.

Derive pool settings from the plan

Now compare the RAM-based upper limit with the estimated requirement, including a margin. If the requirement falls below that limit, choose a conservative starting value for pm.max_children, which does not have to use up the entire budget. In the example given, 25 would be the calculated RAM limit, but not necessarily the appropriate setting; the CPU and backend may impose limits even before that.

If the estimated demand exceeds the RAM limit, setting a higher limit will not resolve the issue: it may put the host or container under memory pressure. Conversely, a very low limit can cause requests to be queued. In this case, try optimizing execution time or cache usage, and check whether additional resources or a limit on incoming load are necessary.

At dynamic In addition to the upper limit, you specify the initial number as well as the minimum and maximum number of available workers. The initial and reserve values should match the expected load and remain within the upper limit. If too little capacity is reserved, a sudden increase in load may result in the need for additional workers; conversely, having too many idle processes over time consumes resources.

At ondemand Workers are created only when needed; the idle time until they are terminated is configured separately. This can be useful for pools that are rarely used, but requires special attention when there are rapid spikes in load. Here, too, it limits pm.max_children the number of workers in the pool that are available at the same time.

Multiple websites on the same host do not each receive all available RAM independently of one another. Allocate their potential pool limits together with the other services and the reserve. A global FPM process limit, if configured, may also apply, but it does not replace the individual pool budgets or the assessment of CPU and database capacity.

After making changes, check the actual load and wait times over appropriate time periods, rather than treating the initial value as final. With managed hosting, process data or pool settings may not be directly accessible; in that case, use the provider’s metrics and available options. The settings available also depend on the PHP version and operating environment.

Monitor FPM Status and Storage

The FPM status page shows whether the planned workers are sufficient under actual load. Protect the status endpoint Protected from public access: It may disclose sensitive information about requests and resources. A separately configured status listener can remain accessible even when the main pool is overloaded; check its availability and setup for your PHP version and web server configuration.

Analyzing FPM Status Values in a Temporal Context
Status valueClassificationFollow-up question
active processes / idle processesCurrent Employees and FreelancersWill there still be available workers during peak loads?
listen queue / max listen queueCurrent and all-time maximum queue lengthDoes the queue grow when responses are slow?
max children reachedCounter for when the worker limit is reached in "dynamic" or "ondemand" modeDoes the counter increase during the period under consideration?
slow requestsCounter for Slow RequestsDoes this increase coincide with longer response times?

Consider the values as time series along with request volume and response times. A past peak value for the queue does not indicate current overload; similarly, for counters, the increase since the last observation is more meaningful than the total value alone. Take into account pool starts or restarts, which can change the reference period.

AI-generated illustration of two strips of paper showing current and previous clusters of dots.
AI-generated illustration: Current status values and previous highs need to be put into a temporal context.

For a known FPM worker, you can read aggregated memory values on a Linux system with an available proc interface. Replace PID by the actual process ID; you need the appropriate access rights. Repeat the observation for multiple workers and load conditions, rather than deriving a permanent pool size from a single process.

Terminal
cat /proc/PID/smaps_rollup

Investigating Slow Requests and Queues

A store may show many active workers, even though PHP execution itself is not the original bottleneck. If requests are waiting for slow database queries, the workers remain tied up in the meantime. If additional requests arrive, the listener queue can grow and response times can increase, even if the CPU still has spare capacity.

A higher worker limit might allow more requests to reach the database simultaneously in this situation. This can Back-end Printing and further increase wait times. Therefore, compare the trends in FPM utilization and queue length with database load, slow queries, and external service calls before you increase the limit.

The FPM slowlog helps pinpoint long PHP calls: For requests exceeding a configured threshold, FPM can log a backtrace. This shows a single execution point and is not a complete profile of the entire request. Recurring points can be cross-referenced with request duration and backend metrics; the Guide to Analyzing the PHP-FPM Slowlog explains the further analysis.

A timeout must also be distinguished from the actual capacity. In nginx, this is referred to as fastcgi_read_timeout the allowed interval between consecutive read operations from the FastCGI server, not the total request duration as a whole. A higher value may allow for longer wait times, but it neither creates additional workers nor speeds up the database.

Capacity Limits and Next Steps

If the RAM budget is insufficient to meet the estimated requirements, a higher setting for pm.max_children Don't overlook the trade-off: Additional processes also require memory. Instead, check whether you can reduce the number of PHP requests or the duration of their worker usage. Which approach makes sense depends on where the observed load is originating.

A more effectively utilized cache can handle repeated requests, while faster PHP execution and database accesses free up workers sooner. Appropriate background tasks can be offloaded from the immediate request path; however, these tasks also require their own resources. A load limit protects the backend during peak times but can result in requests being queued or rejected.

Additional RAM can allow for a higher number of workers if memory is indeed the limiting factor. If the CPU, database, or an external service is already at full capacity, their capacity must also be taken into account. Therefore, more memory alone does not guarantee shorter response times or higher throughput.

If the memory requirements of individual workers exceed their lifetime, then pm.max_requests Arrange for them to be replaced regularly. This can help when dealing with memory leaks in third-party libraries, but it does not address the root cause. After making a change, continue to monitor pool memory and request behavior, rather than treating the restart mechanism as a substitute for debugging.

For a broader architectural decision, the Comparison of NGINX Unit and PHP-FPM Context: NGINX Unit has been archived and is no longer maintained. Regardless of the architecture you choose, you should make changes based on time series Check: For similar request volumes, compare the queue, worker utilization, memory usage, and response times. This will help you determine whether an adjustment has actually resolved the bottleneck or merely shifted it elsewhere.

Sources and Current State of Knowledge

Status of the research:

Research status: November 4, 2026. Check the availability of status listeners, process data, and pool settings for the PHP and Linux versions in use, as well as the hosting environment.

https://www.php.net/install.fpm.configuration

https://www.php.net/fpm.status.php

https://docs.kernel.org/7.0/filesystems/proc.html

https://www.kernel.org/doc/html/v6.12/filesystems/proc.html

https://www.php.net/manual/de/install.fpm.php

https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html

Current articles

Unoccupied workstation with a monitor for designing a hosting monitoring dashboard
Administration

Creating Grafana Dashboards for Hosting Infrastructures

Here's how to design Grafana dashboards for hosting infrastructures using Prometheus: meaningful panels, correct PromQL queries, appropriate variables, and clear boundaries for interpretation.

CrowdSec vs. Fail2Ban: Differences in Practical Use

CrowdSec and Fail2Ban block suspicious IP addresses, but they differ in terms of detection, enforcement, and operational overhead. This comparison shows which solution is best suited for VPS, proxy, and distributed setups.