Monitoring PHP-FPM
PHP-FPM is recognised by its master process, and the pool telemetry below needs nothing
from you: no pm.status_path, no status page, no reload. The agent counts the workers the
kernel already knows about, reads the limits from the pool configs, and measures the one
number that says a pool is saturated, the requests waiting on its socket. The deeper
layers, traces and flame graphs, are provisioned by the agent and switched on by you.
How PHP-FPM is recognised
Any process whose name starts with php-fpm (php-fpm8.3, php-fpm83, plain php-fpm)
matches, so a host running several PHP versions side by side is covered by one match. Four
definitions ride on it: php-fpm (pools and facts), logs-php (the error log), apm-php
(method-level spans) and apm-profile (the CPU flame graph), the last one only where the
agent may read another process's memory, that is running as root or with
kernel.yama.ptrace_scope at 0.
The master's own process title names its configuration file
(php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)); the agent reads that file
for the pool directory (include=), the error_log and the pid file. The pool census
runs every 60 seconds, log shipping every 30, the APM probe and the profiler every 5
minutes.
What is collected
Pools. Each pool is found from its workers' titles (php-fpm: pool www) and reported
with its live worker count, how many of them sit in uninterruptible wait (stuck on IO or a
lock), the configured pm.max_children, whether the pool is static, and the backlog:
requests waiting in the pool's listen queue, read from the kernel for Unix sockets and TCP
ports alike. Limits come from the pool files of every common layout (/etc/php/*/fpm/pool.d,
/etc/php-fpm.d, Remi's /etc/opt/remi/php*/php-fpm.d, Alpine's /etc/php*/php-fpm.d).
Pools that share a name across PHP versions fold into one row with their limits summed;
up to 150 pools are reported per host.
Saturation means a growing backlog, never a high workers-to-limit ratio: a static pool
sits at its limit by design, and a busy dynamic pool at pm.max_children with an empty
queue is fine.
Facts shown on the host page: version, configuration file, pool directory, error log and pid file; plus, once APM is in play, the artifact and activation state per PHP version.
| Signal | Metric |
|---|---|
| Pools on the host | svc.php-fpm.pools |
| Live workers per pool | svc.php-fpm.pool.workers tagged pool |
| Workers stuck in uninterruptible wait | svc.php-fpm.pool.blocked |
| Configured worker limit | svc.php-fpm.pool.max_children |
| Requests queued on the listen socket | svc.php-fpm.pool.backlog |
| Static pool flag | svc.php-fpm.pool.static |
| Spans instrumentation active, per PHP version | svc.apm-php.instrumented tagged php |
| Profiler window: busy share, distinct stacks, CPU per owner | svc.apm-profile.busy_pct, .unique_stacks, .owner_pct tagged owner |
Names, tags and units are in the metrics reference.
Logs. The php-fpm error log named in the configuration ships by default at notice
level and above, so a pool reaching pm.max_children or a worker timing out reaches the
console. Lines are classified by the level php-fpm prints (WARNING, ERROR, NOTICE).
Alerts out of the box
| Rule | Fires when | Severity | Ships |
|---|---|---|---|
| PHP-FPM pool saturated | at least one request is waiting in a pool's listen queue for 5 minutes | warning | auto |
| Service floods error logs | 50 or more error lines in 15 minutes | warning | auto |
A request waiting on the socket means every worker of that pool is busy and the next visitor is queueing, which is the moment a site starts feeling slow. Five minutes rides out a burst and the socket recreation of a reload without waking anyone. The rule is per pool, so one saturated site on a shared host is named, not the host. Thresholds and hysteresis are in the rules catalog.
Going deeper
- Method-level traces. The
apm-phpdefinition fingerprints each php-fpm build (version, thread safety, architecture, libc), downloads the matching signed OpenTelemetry extension and SDK bundle, load-tests them aside, and stages the change: the ini is written into that version's ownconf.dand an "action required" appears with the exact reload command. Nothing changes until a human runs that reload; the agent then sees the extension loaded and marks the version active. A host with 8.1, 8.2 and 8.3 side by side gets one artifact, one staged ini and one reload per version. See APM: how it works. - CPU flame graph. The
apm-profiledefinition samples the live stacks of up to 24 workers from outside the process, 200 times a second for 10 seconds, with no extension and no reload. Stacks carry the pool they came from, and on WordPress the CPU share is attributed to the plugin, theme or core that burned it. - Once spans flow, two more rules arm themselves: a site whose p95 stays above 3 seconds for 15 minutes, and a site whose error rate stays above 20% for 15 minutes.
- Real user monitoring rides the same PHP prepend, see RUM.
When a path is not where PHP-FPM says it is
The error log path comes from the master's configuration file. To point the agent elsewhere, or at a different configuration file altogether:
sudo wakora --set php-fpm.error-log=/var/log/php-fpm/error.log
Keys (conf-file, error-log) are listed in Agent configuration.
Limitations
- Spans need PHP 8.1 or newer; older versions keep pools, logs and the profiler.
- The profiler covers non-thread-safe php-fpm builds on Linux; a thread-safe variant is planned.
- Staging of the spans ini is refused while any pool of that version restricts
open_basedirto paths that exclude the agent's artifact directory, because a version-wide prepend would break those pools. The agent names the pools; allow the path and it re-stages on the next cycle. - The version fact on the host page is read from a
/etc/php/<version>/configuration path; other layouts still get pools and logs, and the APM probe fingerprints the exact build on its own. - mod_php has no pools. Apache hosts running PHP inside the web server are covered on the Apache page.
Related
- Monitoring nginx and Monitoring Apache
- Monitoring MySQL and MariaDB for the database side of a LAMP box
- WordPress monitoring and monitoring for hosting providers on the main site