Skip to main content

PHP-FPM: Pools, Workers and Performance Tuning

PHP · 19.04.2026 · 6 min read
Illustration for “PHP-FPM: Pools, Workers and Performance Tuning”

What PHP-FPM Is and Why You Should Tune It

PHP-FPM (FastCGI Process Manager) is a PHP process manager that runs PHP as a standalone service. Paired with Nginx it delivers high performance: Nginx serves static files while PHP-FPM handles dynamic requests. Proper PHP-FPM tuning lets a server handle 2–5 times more requests without adding RAM.

The Pool Configuration File

The main pool file lives at /etc/php/8.x/fpm/pool.d/www.conf. Open it for editing:

sudo nano /etc/php/8.2/fpm/pool.d/www.conf

Process Manager Modes (pm)

The pm parameter defines how PHP-FPM manages its child processes:

ModeDescriptionWhen to use
staticFixed number of processesHigh-load servers with enough RAM
dynamicProcess count changes dynamicallyMost VPS setups (recommended)
ondemandProcesses are spawned on demandLow-traffic server, saving RAM

Optimal pm Values for dynamic

pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 8
pm.max_requests = 500
Formula for pm.max_children: Divide the available RAM by the average size of a single PHP process. Example: 2 GB of RAM, average process 80 MB → max_children = 2048 / 80 ≈ 25. Check the average process size with: ps -o rss= -C php-fpm8.2 | awk '{ sum+=$1 } END { print sum/NR/1024 " MB" }'

Slow Log — Finding Slow Scripts

Enable the slow log to diagnose bottlenecks:

slowlog = /var/log/php8.2-fpm-slow.log
request_slowlog_timeout = 5s

Read the log with: tail -f /var/log/php8.2-fpm-slow.log

PHP-FPM Status Page

Add this to the pool config:

pm.status_path = /status

In Nginx, add a location block:

location ~ ^/status$ {
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Applying the Settings

sudo systemctl reload php8.2-fpm
sudo systemctl status php8.2-fpm
Do not restart PHP-FPM under heavy load — use reload, which applies the config without dropping active connections.

Nginx Configuration for PHP-FPM

location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    fastcgi_read_timeout 300;
}

Real PHP-FPM Errors

pm.max_children reached

[27-Sep-2026 14:02:11] WARNING: [pool www] server reached pm.max_children setting (20), consider raising it

All 20 workers in the pool are busy handling requests, and new requests queue up in the OS kernel's socket backlog. If that queue also fills up, Nginx gets a connection failure and returns a 502 or 504 to the client. The warning by itself is not an outage, but its frequent appearance in the log means pm.max_children is too low for real traffic, or scripts started taking longer than usual.

A worker killed by the OOM killer

Out of memory: Killed process 18432 (php-fpm) total-vm:512000kB, anon-rss:245000kB, file-rss:0kB

This line comes from the kernel's own log (dmesg or journalctl -k), not from the PHP-FPM log. It means the combined memory of all processes exceeded the server's physical RAM, and the kernel forcibly killed one of the workers. Calculating pm.max_children without accounting for peak load is a direct path to this error.

These two log entries are directly connected: if workers regularly hit pm.max_children, an administrator often panics and raises the limit without recalculating available memory — and ends up not with a request queue, but with the whole server going down from an out-of-memory condition.

When Typical pm Values Do Not Fit

  • static keeps a fixed process count all the time — on a server with sporadic traffic that is pure wasted RAM while idle.
  • ondemand saves memory but adds latency from forking a new process on the first request after idle time — for an API with strict response-time SLAs that may be unacceptable.
  • pm.max_children calculated purely from an average process size ignores spikes: a heavy report or export can temporarily balloon a single worker several times over — build in headroom instead of running right up against the RAM limit.

Verifying the Result

Status fieldWhat it tells you
active processesHow many workers are handling a request right now
idle processesHow many workers are idle and waiting
listen queueRequests waiting for a free worker — normally 0
max children reachedHow many times pm.max_children has been hit since startup
curl "http://127.0.0.1/status?full"

A nonzero and growing max children reached is a signal to raise pm.max_children, or first to find out why requests started taking longer, before simply increasing the limit.

Frequently asked questions

If pm.max_children is changed on a live server via reload, does that drop any active connections?

Reload applies the new configuration without dropping connections that are already established, which is exactly why the guide recommends it over a full restart under load. The new pm.max_children value and other pool settings take effect for new requests, while scripts already running finish out under the previous rules.

Does max_requests need adjusting if the site runs on a framework whose scripts leak memory?

Yes, when a framework's scripts leak memory, each process gradually accumulates more and more used memory over time, so max_requests, which forcibly restarts a worker after a set number of handled requests, becomes especially important. Without it, the leak slowly eats into every process's RAM and pushes the server toward the out-of-memory failure described in the guide.

How can you tell the current pm values are genuinely too low, rather than the server being briefly overloaded by something else?

Watch the listen queue field and the max children reached counter on the status page over time rather than a single snapshot: one isolated spike can come from an unrelated background process, while a steady rise in those numbers points to too few workers for traffic. Line up spike timing with other services' logs before changing the configuration.

Can ondemand mode be used together with the PHP-FPM status page?

Yes, ondemand mode and the pm.status_path status page work together fine — the status page shows the pool's current state regardless of whether processes are created dynamically, statically, or on demand. The only difference is that under ondemand, the idle processes count sits close to zero while the pool is quiet, since processes are not kept running without a reason.

Was this article helpful?
← Back to Knowledge Base Ask Support