PHP-FPM monitoring: the metrics that actually matter
Most PHP teams don't monitor PHP-FPM until it causes an outage. Here's what to watch, why it matters, and how to set up alerts before things break.
PHP-FPM (FastCGI Process Manager) is the component that runs your PHP code. It manages a pool of worker processes, each of which handles one request at a time. When all workers are busy, new requests queue up. When the queue is full, PHP-FPM starts rejecting connections.
Most teams discover PHP-FPM saturation when users start complaining about 502 or 504 errors — not from a monitoring dashboard. This guide explains what to monitor, what the numbers mean, and how to set up proactive alerts.
How PHP-FPM works (the short version)
PHP-FPM runs in one of three process management modes:
- static — a fixed number of workers, always running. Predictable memory usage, but wastes resources during low traffic.
- dynamic — starts with a minimum number of workers and scales up to a maximum as demand increases. The most common mode.
- ondemand — spawns workers only when a request comes in. Minimal idle memory usage, but has startup latency on cold requests.
For production Laravel/Symfony apps, dynamic mode with sensible min/max values is usually the right choice.
The PHP-FPM status page
PHP-FPM exposes a status endpoint that shows real-time pool metrics. Enable it in your pool config (/etc/php/8.2/fpm/pool.d/www.conf):
pm.status_path = /fpm-status
Expose it via nginx (keep it internal — don't expose to the public internet):
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
Test it:
curl "http://127.0.0.1/fpm-status?json"
You'll see output like:
{
"pool": "www",
"process manager": "dynamic",
"accepted conn": 1482943,
"listen queue": 0,
"max listen queue": 14,
"listen queue len": 128,
"idle processes": 3,
"active processes": 2,
"total processes": 5,
"max active processes": 8,
"max children reached": 0,
"slow requests": 12
}
The metrics that matter — and what they mean
active processes / total processes
This is your utilization ratio. If active == total consistently, you've saturated the pool — new requests are queuing. The correct response is to either increase pm.max_children or add more servers.
listen.backlog connections). Once the queue is full, nginx returns a 502 to clients.
listen queue
The number of requests currently waiting for a free worker. This should always be 0 in normal operation. Any sustained non-zero value means your pool is saturated and users are experiencing latency spikes. If this hits listen.backlog (default 511 on Linux), connections start getting dropped.
max children reached
A counter that increments every time PHP-FPM tried to spawn a new worker but was already at pm.max_children. This is the single best early-warning indicator of saturation. Monitor this counter over time — if it's climbing, you need more workers or faster code.
slow requests
The count of requests that exceeded request_slowlog_timeout. Enable slow logging in your pool config:
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 5s
Slow logs record the full PHP call stack at the moment a request exceeds the timeout — invaluable for finding slow functions without full APM.
Setting up PHP-FPM monitoring in MonitorKit
The MonitorKit server agent polls the PHP-FPM status endpoint and ships metrics to your dashboard every 30 seconds. Enable it in config.toml:
[phpfpm]
enabled = true
status_urls = ["http://127.0.0.1/fpm-status?json"]
# Multiple pools? List all status URLs:
# status_urls = [
# "http://127.0.0.1/fpm-status-www?json",
# "http://127.0.0.1/fpm-status-api?json"
# ]
Restart the agent:
systemctl restart monitorkit-agent
PHP-FPM metrics now appear in the host detail panel under the "PHP-FPM" tab. You'll see active vs idle workers, listen queue depth, and requests per second — updated every 30 seconds.
Alerts to set up
Create these alert rules in the MonitorKit dashboard for proactive PHP-FPM monitoring:
1. php-fpm service down
Use a Service Down alert rule targeting php-fpm. This fires within 60 seconds if the PHP-FPM systemd service stops running — before nginx starts returning 502s.
2. High CPU (worker saturation proxy)
CPU > 85% for 5 minutes often correlates with PHP-FPM saturation. Set a threshold alert on cpu_percent > 85.
3. High memory
PHP-FPM workers each use ~20–80MB of RAM depending on your app. If pm.max_children is too high and all workers spin up simultaneously, you can exhaust RAM and trigger OOM kills. Alert on memory_percent > 85.
Tuning PHP-FPM for production
How many workers should you run?
The classic formula:
pm.max_children = (available RAM) / (average PHP process RAM)
# Example: 4GB RAM, 50MB per PHP process
# pm.max_children = 4096 / 50 = ~80 (leave headroom for OS, MySQL, nginx)
# Safe value: 60
Find your average PHP process size:
ps aux | grep php-fpm | awk '{print $6}' | sort -n | tail -20
This shows RSS in KB. Divide by 1024 for MB.
Recommended dynamic mode settings for a typical Laravel app on 4GB RAM
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500 ; recycle workers to prevent memory leaks
OPcache — don't skip this
OPcache compiles PHP files once and caches the bytecode. Without it, every request re-parses every file. Enable it in /etc/php/8.2/fpm/conf.d/10-opcache.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.revalidate_freq=0 ; 0 = never in production (restart to reload)
opcache.validate_timestamps=0 ; 0 = fastest, disable file change checks
With OPcache enabled, a typical Laravel request drops from 80–150ms (without) to 5–30ms (with). That translates directly to higher PHP-FPM throughput with the same number of workers.
Reading a PHP-FPM saturation incident
Here's what a typical saturation event looks like in the metrics:
- Traffic spike — CPU climbs, active processes approach max_children
- listen queue > 0 — requests start backing up, user-perceived latency increases
- max children reached increments — PHP-FPM can't spawn more workers
- nginx 502s — listen queue is full, PHP-FPM rejects connections
- Alert fires — if you've set up a php-fpm service alert or CPU alert
Monitoring PHP-FPM turns step 5 into step 1 — you get notified before users do.
Related guides
Try MonitorKit free
Everything in this article — APM, server monitoring, alerts — in one tool. 14-day trial, no credit card.
Start free trial →