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.confProcess Manager Modes (pm)
The pm parameter defines how PHP-FPM manages its child processes:
| Mode | Description | When to use |
|---|---|---|
static | Fixed number of processes | High-load servers with enough RAM |
dynamic | Process count changes dynamically | Most VPS setups (recommended) |
ondemand | Processes are spawned on demand | Low-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 = 500ps -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 = 5sRead 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 = /statusIn 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-fpmreload, 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 itAll 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:0kBThis 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
statickeeps a fixed process count all the time — on a server with sporadic traffic that is pure wasted RAM while idle.ondemandsaves 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_childrencalculated 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 field | What it tells you |
|---|---|
| active processes | How many workers are handling a request right now |
| idle processes | How many workers are idle and waiting |
| listen queue | Requests waiting for a free worker — normally 0 |
| max children reached | How 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.