...

PHP-FPM modes in comparison: Static, Dynamic and Ondemand

This article compares the PHP-FPM modes static, dynamic and ondemand and shows how they start processes, bind RAM and influence latency. I explain in a practical way when which mode is convincing, provide sensible start values, name typical stumbling blocks and show monitoring tricks so that you can optimize your PHP-pools safely.

Key points

To help you get started quickly, I'll summarize the most important statements in a compact format. The focus is on process control, RAM requirements, latency and fields of application. Each selection has clear strengths, but also limitations. With a few key figures, you can make reliable decisions. So you can focus on Tuning and save time.

  • StaticFixed process number, maximum consistency with constant load.
  • DynamicAutomatic scaling between minimum and maximum values.
  • OndemandStart on demand, economical idling, cold start latency.
  • RAM planning: Allow 20-50 MB per process, avoid OOM.
  • MonitoringStatus page, logs and htop for well-founded decisions.

How the Process Manager works

The PHP-FPM Process Manager controls how many Worker-processes process requests and when they start or end. Each worker instance holds interpreters, extensions and parts of the bytecode in memory, which typically means a few Megabyte binds. The three modes significantly change the start behavior, life cycle and idle behavior. Static keeps a fixed number active, Dynamic balances between lower and upper limits, Ondemand only creates processes when requests are received. This control has a direct effect on RAM-profile, latency when turning up and system load peaks.

Important parameters form the backbone of your configuration: pm defines the mode, pm.max_children limited simultaneous workers hard. With Dynamic pm.start_servers, pm.min_spare_servers and pm.max_spare_servers which control the width of the buffer. Ondemand relies on pm.process_idle_timeout, to end dormant processes again. With sensible values, you ensure that load peaks do not lead to bottlenecks and the machine does not come under memory pressure.

I check the footprint per process, the average simultaneous load and the peak distribution over the day in advance. From these variables, I derive the maximum value for pm.max_children multiply by the measured process memory and leave a reserve for the web server, database, cache and kernel. This simple calculation prevents out-of-memory errors and ensures Stability under pressure. If you take this to heart, you will save yourself the hassle of readjusting later.

Static mode: constant power for even load

Static mode keeps a fixed number of PHP workers permanently active, which Start-overhead is eliminated. With constant traffic profiles, this setup achieves very low latency fluctuations and a consistent CPU-load. The downside: When idle, RAM remains occupied even though there are no requests. That's why I only choose Static on hosts with plenty of RAM and a calculable request volume. On heavily used stores or API backends, Static often delivers the cleanest response curve.

The decisive factor is a realistic pm.max_children, which is based on the process footprint. For the first estimate, I roughly calculate 20-50 MB per PHP process including extensions and OPcache. I verify the final value with load tests and the system monitor. If you want to deepen the calculation, you can find practical steps at Optimize pm.max_children. This ensures that your fixed pool size matches the hardware.

[www]
pm = static
pm.max_children = 50
pm.max_requests = 500

NoteAfter changes, I restart PHP-FPM, check logs and observe the load under real traffic. If there is still a lot of RAM free, I increase it carefully. If I see increasing swap usage or OOM killer entries, I reduce immediately. This little routine protects the Availability reliable.

Dynamic mode: flexible with fluctuating demand

Dynamic starts with just a few processes and scales the Worker-number into the defined range as required. This reduces idle consumption during quiet phases, while short peaks are cushioned. The process generates some overhead during spawning, but scores points with good Resources-efficiency. In mixed environments with daily profiles, Dynamic often delivers the best compromise. This mode remains the first choice for many CMS installations in particular.

I set the start, minimum and maximum values so that no constant spawn events occur under typical load. Frequent log messages such as „seems busy, spawning children“ indicate that the limits are too tight. For WordPress stacks, it helps to set caching and OPcache correctly and then increase them moderately. A compact guide covers the most important levers: optimal WordPress settings. This allows you to achieve short response times without the RAM-reserve.

[www]
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35

TipObserve the idle workers and the average active processes throughout the day. If the average value is close to the upper end, increase moderately. If many processes remain idle, lower the range. With just a few iterations you can hit the Sweet spot-setting.

Ondemand mode: economical in idle mode, start on request

Ondemand only creates processes when a Request and terminates it after an idle time. This reduces the RAM requirement to a minimum in quiet phases, which favors many small sites on one machine. During cold starts, however, additional latency is incurred because the worker first starts up and warms up. For development environments, cron-only apps and infrequently accessed pages, this logic is a Profit. I would not use Ondemand under continuous load.

[www]
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
pm.max_requests = 500

I usually set the idle time to between 10 and 30 seconds, depending on the call pattern and memory budget. A shorter period saves RAM, but increases the chance of cold starts. A longer period keeps processes warm, but costs memory. I therefore monitor the call frequency, measure the 95th percentile latency and then make fine adjustments. This keeps the Response time calculable without burdening the system.

Comparison table: Properties of the three modes

The following overview compares typical properties. I use it as a basis for discussion before going into specific sizing. The table does not replace a measurement under real load, but it does provide a structured Starting point. If you adjust values, you should always keep an eye on the memory profile and latency distribution. So you stay with Peaks and avoid bottlenecks.

Criterion Static Dynamic Ondemand
Processes Fixed number, permanently active Automatically between min/max Start only when required
RAM usage Constantly high Variable (e.g. 200-600 MB) Minimum in idle mode (e.g. 50-700 MB)
Performance Very even Good and adaptable Good for low traffic
Ideal for Constant high-traffic profiles Variable demand Many dormant sites / Shared
Overhead Low Medium (spawn/despawn) Higher for cold starts

The table helps to calibrate expectations and clearly identify priorities. If you need maximum consistency of response, often Static. Counts efficiency with fluctuating load, works Dynamic usually more pleasant. If economy is a priority, there is no way around it. Ondemand over. Measured values decide in the end, not assumptions.

Resource calculation and sizing

I first estimate the memory footprint per Process multiply it by the intended number of workers and add 20-30 % reserve. I also include space for Nginx/Apache, database, Redis/Memcached and the kernel. This total must not exceed the physical RAM capacity minus the security margin. I plan dedicated memory for OPcache so that bytecode is not displaced. With this simple Formula I consider OOM risks to be low.

In the next step, I measure simultaneous requests via web server status and APM. The peak competition for PHP workers determines how high pm.max_children must be. If the RAM is not sufficient, I increase cache hits, reduce query times or move work to queues. Only when these levers take effect do I increase the pool. This keeps the Efficiency high and the machine responds reliably.

Monitoring and troubleshooting

Good decisions are based on Data. I activate the PHP-FPM status page and read out active and idle processes, queue length and accepted connections. I also check error logs for spawn warnings and timeouts. In htop, I monitor CPU waits, load and swap in order to find bottlenecks more quickly. These signals make tuning steps comprehensible and avoid flying blind.

<?php
$status = @file_get_contents('http://localhost/status');
$data = json_decode($status, true);
echo "Active: " . $data['active processes'] . "\n";
echo "Idle: " . $data['idle processes'] . "\n";
?>

APM tools show traces and bottlenecks at function or query level. If I find outliers there, I start with caching and I/O first. Then I check whether the pool limits match the actual parallelism. Only when application bottlenecks have been resolved is it worth doing more Capacity in FPM. This process saves time and keeps the architecture lean.

Avoid common tuning errors

I often see overly high max_children-values regardless of the RAM. This creates unnecessary swap, long garbage collection phases and ultimately OOM killers. Limits that are too low are also harmful because they build up queues and stretch response times. A lack of OPcache also wastes CPU time and increases the process footprint. With a few Checks These traps are kept out of the way in advance.

A second classic: inappropriate time limits with Ondemand, which lead to many cold starts. A short A/B test with 10, 20 and 30 seconds idle timeout helps here. With Dynamic, on the other hand, too small spare values cause constant spawning. Logs quickly reveal these patterns and guide the next Customization on. This keeps your stack responsive.

PHP-FPM in the context of other PHP handlers

PHP-FPM is often compared to old CGI variants or modern alternatives such as LSAPI. The choice of handler influences process management, Resources-characteristics and fault isolation. If you understand the differences, you can plan buffers and limits more realistically. For a quick overview, it is worth taking a brief PHP handler comparison. After that, the decision for FPM modes is clearly more targeted from.

I usually stick with FPM because it is mature, logs cleanly and works well with Nginx/Apache. It's not just benchmarks that are decisive, but also operational aspects such as observability and failover. If these basics are right, you will get more out of Static, Dynamic or Ondemand. Every option deserves to be tested under real load. This is how you gain confidence in your Settings.

Practical decision-making strategy

I start with Dynamic as Default, I measure load profiles and observe peaks. If I find very constant utilization, I switch to Static and set the fixed pool size. If I encounter rarely used sites, I select Ondemand with an appropriate idle timeout. At the same time, I optimize OPcache, object cache and database queries so that FPM is under less pressure. I then fine-tune the limits so that Queues not arise in the first place.

This sequence reduces risk and effort. First measure, then adapt rules, finally take hardware into account. I briefly document each change with the time, values and target. This makes it easier to make corrections later and ensures clean Transparency. This keeps the stack manageable, even if traffic patterns change.

From key figures to reliable values: this is how I calculate

I translate load profiles into concrete pool sizes using a simple rule of thumb: How many requests arrive per second and how long does it take to process them on average or at the 95th percentile? I use the following as a guide Little's Law in simple form: simultaneous processing ≈ throughput × average processing time. Example: 120 requests/s at 80 ms on average result in around 9.6 simultaneous executions. I add 30-50 % buffers for peaks and check whether the resulting pm.max_children fit into my RAM budget. For hard peaks, I also include the 95th percentile to avoid queues.

It is important to Character of the workloads: With I/O-heavy apps (many remote calls, DB accesses), slightly more workers often bring advantages because waiting times are overlapped. With CPU-heavy code, I limit more so that the processes don't slow each other down and the run queue doesn't explode.

pm.max_requests: clean recycling against fragmentation

Long-running PHP processes can be stopped by Fragmentation or memory leaks grow. With pm.max_requests you define after how many processed requests a worker is terminated and restarted. This keeps the footprint stable. I usually start at 300-1000, depending on the extensions and code base. Observe the RSS/PSS values of the processes: If they grow significantly, reduce the value. Since the OPcache shared bytecode is retained during worker recycling; most apps therefore hardly notice the recycling.

[www]
; targeted recycling without too frequent restarts
pm.max_requests = 800

Who regularly Deployments benefits from a reload of the pool. I prefer to use a graceful reload via the service manager (e.g. „systemctl reload php-fpm“) so that running requests end cleanly and new workers start with an updated config.

Slowlog and timeouts: making bottlenecks visible in a targeted manner

Most latency peaks are caused by a few slow requests. I therefore activate the Slowlog with a moderate threshold value (e.g. 2-5 s) and look at stack traces. This is how I find problematic functions, external calls or expensive queries.

[www]
request_slowlog_timeout = 3s
slowlog = /var/log/php-fpm/slowlog-www.log

In keeping with this, I match the Timeouts of the web server. An upstream timeout (Nginx/Apache) that is too short compared to PHP's max_execution_time leads to 502/504 errors, although FPM continues to work. I keep the chain consistent: connect, read and send timeouts of the web server just above the typical PHP request duration, but below hard upper limits.

Correctly interpreting the queue, backlog and status values

In the FPM status, I pay particular attention to „listen queue“ and „max listen queue“. If these values increase regularly, the pool is too small or blocked. Short-term peaks are normal, but permanent congestion indicates undersizing. In heavily congested environments, I increase the socketbacklog moderately, watch the queue and make sure that kernel limits (e.g. somaxconn) are not the bottleneck.

If the monitoring shows „seems busy, spawning children“ very frequently, the reserve parameters (Dynamic) are too tight. With Ondemand, a recurring high proportion of cold starts is an indication to extend the idle timeout or to keep a minimum buffer during the day.

Multiple pools: Fairness, isolation and quotas

On multi-tenant or Shared-hosts, I separate applications into their own pools with individual limits. This prevents a memory-hungry project from crowding out others. For critical services (e.g. login/API endpoints), I plan dedicated pools with a fixed minimum reserve. Clear naming („www-shop“, „www-api“, „www-cron“) and separate logs facilitate analysis and Errorsearch.

Make sure that the sum of all pm.max_children fits the machine across all pools. I also purchase Downstream limits on: DB-max_connections, Redis/Memcached threading and external API rates. A PHP pool that fires more simultaneous queries than the database can handle only buys itself longer queues.

Tame OPcache warmup, preload and cold starts

To Ondemand-to mitigate cold starts, I keep OPcache stable (sufficient memory_consumption and interned_strings_buffer) and, if useful, set to Preload central classes/frameworks. This means that bytecode is available after the first hit and repetitions remain warm. In addition, a larger realpath cache and a structured autoloader help to reduce file system lookups. All in all, this significantly shortens the start-up time of freshly started workers.

Web server interaction: Connecting Nginx/Apache cleanly

I make sure that the web server and FPM settings match: Buffer and Timeouts must be symmetrical, keep-alive must not block FPM with zombie connections, and the upstream socket (Unix or TCP) must be configured consistently. Many 502/504 errors are due to incorrectly set read timeouts or exhausted backlogs. Anyone addressing FPM via TCP should consider the latency of the network stack and the risk of semi-open Keep an eye on connections; I usually prefer Unix sockets for local deployments.

Container/VM special features

In containers, the cgroup-limits, not necessarily the host values. I dimension pools explicitly for the container RAM and use artificial load peaks to test whether OOM killers could take effect. A limit that is too tight leads to hard terminations. Swapping in containers is also often undesirable - so it's better to be a little more conservative with pm.max_children plan and prioritize application caching.

Recognize CPU and I/O character

I use htop/iostat to assess whether workloads are CPU- or are I/O-bound. High CPU utilization with low I/O waits indicates a computing load - here I limit the workers closer to the number of cores. High I/O waits justify more workers because waiting times on the database, network or file system are overlapped. You can recognize the limit by the fact that the latency no longer decreases despite additional workers, but the load increases significantly.

Quickly decode typical 502/504 patterns

  • 504 Gateway timeout: Web server timeout less than PHP execution time or blocked pool (queue full).
  • 502 Bad gateway: FPM not reachable (socket/port), crash/restart during request or buffer too small.
  • Spikes shortly after deploy: OPcache cold, check autoloader/composer optimizations, schedule warmup.

I correlate the web server error log, FPM error log and status page in the same time window. This shows whether the problem occurred before, in or to FPM is located.

Measuring trade: Correctly record storage costs

For RAM planning, I don't just look at RSS, but also at the PSS (Proportional Set Size) because it distributes split pages (e.g. OPcache) fairly across processes. Tools like smem or pmap help to determine realistic process-related values. In practice, however, random samples under load are often sufficient: mark several processes, calculate the average value, compare with Reserve multiply - this reflects reality better than theoretical values from forums.

Mini checklist for quick iterations

  • Record load profile (RPS, 50/95/99 percentile, parallelism).
  • Measure process footprint (PSS, not just RSS) and pm.max_children with reserve.
  • Select the mode to match the pattern: Static (constant), Dynamic (alternating), Ondemand (lots of idle time).
  • pm.max_requests set, observe the growth of the workers, readjust if necessary.
  • Dimension OPcache and check warmup/preload to dampen cold starts.
  • Activate status page and slowlog, evaluate queue and spawn messages.
  • Synchronize web server timeouts and buffers against FPM and app times.
  • Harmonize limits with downstream systems (DB, caches, external APIs).
  • Document changes, measure and iterate after deployments.

Compact summary

Static delivers the smoothest response time and suits constant traffic with plenty of RAM. Dynamic balances flexibility and efficiency and scores highly with changing patterns. Ondemand saves memory when idle and is suitable for many dormant sites, but comes at the cost of cold start latency. With clean resource calculation, monitoring and small iterations, you can make reliable decisions. Keep processes as small as necessary, use OPcache and choose the mode that suits your real needs. Profile fits.

With these guard rails, you can achieve stable performance with reasonable consumption. Configuration is no guessing game when numbers are on the table. Small steps often have the greatest effect. Measure, adjust and document. So your PHP-FPM-pools quickly, economically and predictably.

Current articles