Check php-fpm-logfile¶
Overview¶
Scans the PHP-FPM error log for the events an administrator has to act on: rejected configurations, worker crashes, workers killed by the kernel's OOM killer, requests that ran into request_terminate_timeout or request_slowlog_timeout, workers that hang after the SIGTERM such a timeout sent, workers that waited on a connection the web server never sent a request over, pools that hit pm.max_children, PHP fatal errors, and the emergency reload PHP-FPM performs after repeated worker failures. Startups, reloads and shutdowns are counted alongside them, so a pool that keeps restarting is visible. Alerts when one of those events shows up, when the lines a slow site provokes cross the rates the thresholds set, and when a line arrives at a level --critical-level or --warning-level covers. Requests that ran long, a pool spawning workers in bursts and PHP fatal errors are counted within --lookback and judged by how many of them arrived, not by the fact that they did: one slow page is nobody's night, dozens within ten minutes say the pool is mis-sized. Those lines are counted there and nowhere else, so a site with one heavy report page does not leave the check permanently yellow. What is left is counted by the level PHP-FPM writes at the head of the line: ALERT and ERROR return CRITICAL and WARNING returns WARNING, which --critical-level and --warning-level move. The events named above carry their own state and are counted there and nowhere else, because the level says nothing about what happened. A message that merely contains the word "error" never counts. A worker that died on SIGKILL is looked up in the kernel log of the journal, because PHP-FPM ends an idle worker that does not leave when the pool shrinks the same way the OOM killer ends one, and its error log shows no difference. Only the OOM kill alerts; where the kernel log cannot answer, the check alerts and names both causes. The log is read either from a file, from a systemd unit (systemd:) or from a container (docker:/podman:/kubectl:). --server-log may be given several times, and everything named is then read as one window. Without it the file path is taken from the error_log directive of the PHP-FPM configuration, with the common locations of the distributions probed when that yields nothing, and the journal of the PHP-FPM unit is read along with it, because a master that fails to start writes why to its standard error and never reaches the error log. What both hold is counted once. The most recent rotated file is read along with the live one, so the window does not end where logrotate last ran. PHP's own messages go to the error_log file each pool names, which is read along with it; only its fatal errors are counted there. Requires root or sudo.
Important Notes:
- A line is counted once. A named event (a worker crash, a saturated pool, an emergency restart) carries its own state and is counted as that event, not as a
WARNINGorERRORline as well; the same holds for the rate-counted request lines. What the per-level counts hold are the lines no catalog claimed, which is what--critical-leveland--warning-levelact on. - PHP's own messages go to the
error_logof the pool. PHP writes its warnings and fatal errors into the file a pool names withphp_admin_value[error_log]; the RHEL family sets/var/log/php-fpm/www-error.login the default pool. The check finds these files in the pool configuration, reads them along with the PHP-FPM error log and counts the fatal errors against--fatal-errors-warningand--fatal-errors-criticalover--lookback. Warnings, notices and deprecations are read and left alone, because a busy application writes thousands of them a day. A pool without such a file, or with one its user cannot write to, hands PHP's messages to the web server instead, where they show up asAH01071: Got error 'PHP message: ...'in the Apache error log. What a worker writes to its own standard error reaches this log only withcatch_workers_output = yes, wrapped in aWARNINGline of the form[pool www] child 1234 said into stderr: "…". - A pool spawning in bursts is counted, not reported.
seems busy (you may need to increase pm.start_servers...)is written on every maintenance tick for as long as the pressure lasts, so roughly once a second, and only once the spawn rate has doubled its way to 8. A traffic peak therefore leaves a run of them behind and then silence. Filtering them away would throw the tuning signal out with the noise, so they are counted against--spawn-pressure-warningover--lookbackinstead. The same holds for requests that ran intorequest_slowlog_timeoutorrequest_terminate_timeout. - What is genuinely broken keeps its own state. A crashed worker, a worker the OOM killer took, a killed worker the kernel log cannot explain, a worker that waited on an idle connection until it timed out, a worker that hangs after its timeout, a pool that reached
pm.max_childrenand the emergency reload raise their state whatever the levels are set to, because PHP-FPM logs all of them atWARNINGand the level alone does not tell them apart from an ordinary warning. - A worker that died on SIGKILL is looked up in the kernel log. PHP-FPM writes
child N exited on signal 9 (SIGKILL)in two very different situations. Withpm = dynamicorpm = ondemandit sends an idle worker SIGQUIT when the pool shrinks, and SIGKILL a second later if the worker is still there, which a large or swapped-out worker often is. No request is lost that way. The kernel's OOM killer ends a worker with the same signal in the middle of a request. The check therefore reads the kernel log of the journal for an OOM kill at that moment. With one it reports an OOM kill (WARNING), without one a workerkilled with SIGKILL, not by the OOM killer, which is counted and shown but does not alert. Akill -9by hand looks like the latter. - Where the kernel log cannot answer, a killed worker stays a WARNING. That is the case on a host without systemd, when the journal does not reach back to the kill, for a log read from a container (
docker:/podman:/kubectl:), whose kernel log is not necessarily that of this host, and for a PHP-FPM in a container that logs into a bind-mounted file in another time zone than the host. The recommendation then names both causes. - A full pool is WARNING, a pool that stays full is CRITICAL. PHP-FPM logs
server reached pm.max_childrenonce and only again after the pool spawned a worker in between. One of them is a queue of a few seconds; a second one within--lookback, or one next to request timeouts or hung workers there, is a pool that stays full. - The window spans the last rotation. logrotate moves the old file aside (
error.log.1,error.log-20260828, then.gz) and PHP-FPM starts a fresh one, so a check reading the live file alone would report a healthy PHP-FPM an hour after it failed to start. The most recent rotated file is therefore read along with the live one, gzip, xz and bzip2 included, and the last section names every file it read. A rotator told to compress with something else, or to move its output to another directory, is out of reach; an event older than one rotation is too. - A reload counts as a startup as well. PHP-FPM reloads by re-executing its master process, so a reload shows up under both counters.
- The check reads a window of the log on every run and reports what that window holds, rather than only what is new. The summary names how many lines that window holds, because everything else is counted within it: right after logrotate the window is a single line, and a run reporting no startup at all is then telling the truth about that one line rather than about the day. That is what makes the startup, reload and shutdown counts meaningful, and it means an event keeps being reported until it leaves the window or the service is acknowledged (see
--icinga-callback). - Reading the error log needs root or sudo. The RHEL family installs it as
root:rootmode0600. --server-logis confined to/var/log. The check runs as root via sudo, so it refuses a path that resolves outside that directory, which stops it from being turned into an arbitrary root file read. This also applies to the path the PHP-FPM configuration names, because a pool file that an unprivileged user may edit must not be able to steer the check. To read a log stored elsewhere, bind-mount that location under/var/log(a symlink is rejected).- Both
--ignoreand--matchare matched against the lowercased log line, so write the patterns in lowercase (or use the(?i)flag).
Data Collection:
- Determines the PHP-FPM error log automatically from the global
error_logdirective in/etc/php-fpm.conf,/etc/php/*/fpm/php-fpm.confor/usr/local/etc/php-fpm.conf. - Reads the
error_logfiles the pools name withphp_value[error_log]orphp_admin_value[error_log]in/etc/php-fpm.d/*.conf,/etc/php/*/fpm/pool.d/*.confor/usr/local/etc/php-fpm.d/*.conf, as far as they exist; PHP creates such a file with its first message. - Falls back to probing
/var/log/php-fpm/error.log,/var/log/php-fpm.log,/var/log/php*-fpm.logand/usr/local/var/log/php-fpm.logwhen the configuration yields nothing. - Supports reading from a file path,
docker:CONTAINER,podman:CONTAINER,kubectl:CONTAINERorsystemd:UNITNAMEvia--server-log, which can be given several times; everything named is read as one window. A wildcard is not expanded, so name each file. - On a host that systemd runs, reads the journal of the PHP-FPM unit along with the file where
--server-lognames nothing, and counts an event the two share once. - When a worker died on SIGKILL, and only then, asks
journalctlfor the kernel messages aterrand worse since a day before the first such line, and for one entry from before that, which shows that the journal reaches back that far. - Reads at most the last 30000 lines of the source, the most recent rotated file included, and reports how many lines it actually saw, which files they came from, whether it stopped at that cap, and which stretch of time they cover.
- Lines can be narrowed down with
--matchand filtered out with--ignore, both Python regular expressions.
Fact Sheet¶
| Fact | Value |
|---|---|
| Check Plugin Download | https://github.com/Linuxfabrik/monitoring-plugins/tree/main/check-plugins/php-fpm-logfile |
| Nagios/Icinga Check Name | check_php_fpm_logfile |
| Check Interval Recommendation | Every 5 minutes |
| Can be called without parameters | Yes |
| Runs on | Linux |
| Compiled for Windows | No |
| Requirements | User with higher permissions |
| Uses State File | $TEMP/linuxfabrik-monitoring-plugins-php-fpm-logfile-*.db (only with --icinga-callback) |
Help¶
usage: php-fpm-logfile [-h] [-V] [--always-ok]
[--critical-level {ALERT,ERROR,WARNING,none}]
[--fatal-errors-critical FATAL_ERRORS_CRITICAL]
[--fatal-errors-warning FATAL_ERRORS_WARNING]
[--icinga-callback] [--icinga-password ICINGA_PASSWORD]
[--icinga-service-name ICINGA_SERVICE_NAME]
[--icinga-url ICINGA_URL]
[--icinga-username ICINGA_USERNAME] [--ignore IGNORE]
[--insecure] [--lookback LOOKBACK] [--match MATCH]
[--no-insecure]
[--no-match-severity {ok,warn,crit,unknown}]
[--no-perfdata] [--no-proxy] [--proxy PROXY]
[--request-timeouts-critical REQUEST_TIMEOUTS_CRITICAL]
[--request-timeouts-warning REQUEST_TIMEOUTS_WARNING]
[--server-log SERVER_LOG]
[--slow-requests-critical SLOW_REQUESTS_CRITICAL]
[--slow-requests-warning SLOW_REQUESTS_WARNING]
[--spawn-pressure-critical SPAWN_PRESSURE_CRITICAL]
[--spawn-pressure-warning SPAWN_PRESSURE_WARNING]
[--timeout TIMEOUT]
[--warning-level {ALERT,ERROR,WARNING,none}]
Scans the PHP-FPM error log for the events an administrator has to act on:
rejected configurations, worker crashes, workers killed by the kernel's OOM
killer, requests that ran into `request_terminate_timeout` or
`request_slowlog_timeout`, workers that hang after the SIGTERM such a timeout
sent, workers that waited on a connection the web server never sent a request
over, pools that hit `pm.max_children`, PHP fatal errors, and the emergency
reload PHP-FPM performs after repeated worker failures. Startups, reloads and
shutdowns are counted alongside them, so a pool that keeps restarting is
visible. Alerts when one of those events shows up, when the lines a slow site
provokes cross the rates the thresholds set, and when a line arrives at a
level `--critical-level` or `--warning-level` covers. Requests that ran long,
a pool spawning workers in bursts and PHP fatal errors are counted within
`--lookback` and judged by how many of them arrived, not by the fact that they
did: one slow page is nobody's night, dozens within ten minutes say the pool
is mis-sized. Those lines are counted there and nowhere else, so a site with
one heavy report page does not leave the check permanently yellow. What is
left is counted by the level PHP-FPM writes at the head of the line: `ALERT`
and `ERROR` return CRITICAL and `WARNING` returns WARNING, which
`--critical-level` and `--warning-level` move. The events named above carry
their own state and are counted there and nowhere else, because the level says
nothing about what happened. A message that merely contains the word "error"
never counts. A worker that died on SIGKILL is looked up in the kernel log of
the journal, because PHP-FPM ends an idle worker that does not leave when the
pool shrinks the same way the OOM killer ends one, and its error log shows no
difference. Only the OOM kill alerts; where the kernel log cannot answer, the
check alerts and names both causes. The log is read either from a file, from a
systemd unit (`systemd:`) or from a container
(`docker:`/`podman:`/`kubectl:`). `--server-log` may be given several times,
and everything named is then read as one window. Without it the file path is
taken from the `error_log` directive of the PHP-FPM configuration, with the
common locations of the distributions probed when that yields nothing, and the
journal of the PHP-FPM unit is read along with it, because a master that fails
to start writes why to its standard error and never reaches the error log.
What both hold is counted once. The most recent rotated file is read along
with the live one, so the window does not end where logrotate last ran. PHP's
own messages go to the `error_log` file each pool names, which is read along
with it; only its fatal errors are counted there. Requires root or sudo.
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
--always-ok Always returns OK.
--critical-level {ALERT,ERROR,WARNING,none}
Least severe PHP-FPM log level that returns CRITICAL.
Each level includes everything more severe than
itself, so `WARNING` covers `ERROR` and `ALERT` as
well. Case-sensitive. `none` lets no level return
CRITICAL, which leaves the events this check names as
the only way to reach it. Example: `--critical-
level=WARNING`. Default: ERROR
--fatal-errors-critical FATAL_ERRORS_CRITICAL
Number of PHP fatal errors in the `error_log` of the
pools within `--lookback` that returns CRITICAL. 0
turns the threshold off. Example: `--fatal-errors-
critical=50`. Default: 100
--fatal-errors-warning FATAL_ERRORS_WARNING
Number of PHP fatal errors in the `error_log` of the
pools within `--lookback` that returns WARNING. 0
turns the threshold off. Example: `--fatal-errors-
warning=1`. Default: 10
--icinga-callback Ask the monitoring server whether the service running
this check is acknowledged. Where it is, what this run
reports is remembered as already handled, so it no
longer raises an alert on the following runs. Requires
`--icinga-url`, `--icinga-username`, `--icinga-
password` and `--icinga-service-name`.
--icinga-password ICINGA_PASSWORD
Monitoring server API password.
--icinga-service-name ICINGA_SERVICE_NAME
Unique name of the service running this check, as the
monitoring server knows it. Take it from the `__name`
service attribute. Example: `monitoring-server!my-
service-name`.
--icinga-url ICINGA_URL
Monitoring server API URL. Example:
`https://monitoring.example.com:5665`.
--icinga-username ICINGA_USERNAME
Monitoring server API username.
--ignore IGNORE Ignore a log line matching this Python regular
expression. The log line is lowercased before
matching, so write the pattern in lowercase (or use
the `(?i)` flag). Can be specified multiple times.
Example: `--ignore='(?i)linuxfabrik'`.
--insecure Applies to the connection to the monitoring server
that `--icinga-callback` makes, which is the only
network connection this check opens. This option
explicitly allows insecure SSL connections.
--lookback LOOKBACK Request timeouts, slow requests and spawn pressure are
counted within this window rather than reported one by
one. Time window in seconds to look back over, ending
at the moment of the run. Only what falls within it is
counted, so what is reported is how often something
happened lately rather than a total that keeps growing
for as long as the source is kept. Example:
`--lookback=3600`. Default: 600 (seconds)
--match MATCH Only consider a log line matching this Python regular
expression. The log line is lowercased before
matching, so write the pattern in lowercase (or use
the `(?i)` flag). Can be specified multiple times. If
both `--match` and `--ignore` are given, an item must
match `--match` AND not match `--ignore` to be
reported (include first, exclude second). Example:
`--match='\[pool www\]'`.
--no-insecure Applies to the connection to the monitoring server
that `--icinga-callback` makes, which is the only
network connection this check opens. Verify the TLS
certificate against the system trust store, overriding
the insecure default of this check. Use it once the
endpoint presents a publicly trusted certificate, or
once its CA has been added to the system trust store.
--no-match-severity {ok,warn,crit,unknown}
State to report when no item matches the filters and
nothing is checked. Default: ok
--no-perfdata Suppress the performance data section from the output.
The status message and the exit code are unaffected,
so alerting keeps working while trending data is
dropped.
--no-proxy Applies to the connection to the monitoring server
that `--icinga-callback` makes, which is the only
network connection this check opens. Do not use a
proxy, not even one the environment names. Overrides
`--proxy`.
--proxy PROXY Applies to the connection to the monitoring server
that `--icinga-callback` makes, which is the only
network connection this check opens. Proxy to reach
the target through. The scheme defaults to `http` when
omitted. Overrides the proxy the environment names
(`http_proxy`, `https_proxy`, `all_proxy`) together
with the exceptions it lists in `no_proxy`, and is
itself overridden by `--no-proxy`. Without either
parameter the environment applies. Credentials belong
into the environment variable rather than here,
because a command-line argument is visible to every
user on the host. Example:
`--proxy=http://proxy.example.com:3128`.
--request-timeouts-critical REQUEST_TIMEOUTS_CRITICAL
Number of terminated requests within `--lookback` that
returns CRITICAL. 0 turns the threshold off. Example:
`--request-timeouts-critical=20`. Default: 50
--request-timeouts-warning REQUEST_TIMEOUTS_WARNING
Number of terminated requests within `--lookback` that
returns WARNING. 0 turns the threshold off. Example:
`--request-timeouts-warning=1`. Default: 5
--server-log SERVER_LOG
Log source to read from. Accepts a file path,
`docker:CONTAINER`, `podman:CONTAINER`,
`kubectl:CONTAINER` or `systemd:UNITNAME`. Can be
specified multiple times, and everything named is then
read as one window; a source named twice is read once.
If omitted, the check reads the `error_log` directive
of the PHP-FPM configuration, falls back to the common
locations of the distributions, and reads the journal
of the PHP-FPM unit along with it; what the two share
is counted once. It also reads the `error_log` file
every pool names for the messages of PHP itself.
Example: `--server-log=systemd:php-fpm.service`.
--slow-requests-critical SLOW_REQUESTS_CRITICAL
Number of slow requests within `--lookback` that
returns CRITICAL. 0 turns the threshold off. Example:
`--slow-requests-critical=100`. Default: 200
--slow-requests-warning SLOW_REQUESTS_WARNING
Number of slow requests within `--lookback` that
returns WARNING. 0 turns the threshold off. Example:
`--slow-requests-warning=5`. Default: 20
--spawn-pressure-critical SPAWN_PRESSURE_CRITICAL
Number of spawn pressure warnings within `--lookback`
that returns CRITICAL. 0 turns the threshold off.
Example: `--spawn-pressure-critical=50`. Default: 100
--spawn-pressure-warning SPAWN_PRESSURE_WARNING
Number of spawn pressure warnings within `--lookback`
that returns WARNING. A pool logs one of these per
second while it is spawning in bursts, so this counts
how many arrived rather than that any did. 0 turns the
threshold off. Example: `--spawn-pressure-warning=30`.
Default: 10
--timeout TIMEOUT Network timeout in seconds. Default: 8 (seconds)
--warning-level {ALERT,ERROR,WARNING,none}
Least severe PHP-FPM log level that returns WARNING.
Each level includes everything more severe than
itself, and a level that `--critical-level` already
covers returns CRITICAL instead. Case-sensitive.
`none` lets no level return WARNING, which leaves the
events this check names as the only way to reach it.
Example: `--warning-level=none`. Default: WARNING
Documentation:
https://linuxfabrik.github.io/monitoring-plugins/check-plugins/php-fpm-logfile/
Usage Examples¶
./php-fpm-logfile
./php-fpm-logfile --server-log=/var/log/php8.2-fpm.log
./php-fpm-logfile --server-log=systemd:php-fpm.service
./php-fpm-logfile --server-log=podman:php-fpm
# Watch a single pool on a host that runs several of them.
./php-fpm-logfile --match='\[pool shop\]'
# Silence the ptrace failure a container host produces when `request_slowlog_timeout`
# is set but the container may not trace its own children. Everything else stays visible.
./php-fpm-logfile --ignore='failed to ptrace'
# A site with one heavy report page, where a handful of slow requests an hour is normal
# and the pool is only worth a look once they pile up.
./php-fpm-logfile --lookback=3600 --slow-requests-warning=50
# A pool that is being tuned: report every burst of spawning, however small.
./php-fpm-logfile --spawn-pressure-warning=1
Output of a healthy host:
2026-08-28 15:21 .. 2026-08-28 15:21 (0s): No errors or warnings found. 1 startup detected (last: [28-Aug-2026 15:21:20] NOTICE: fpm is running, pid 205).
Read 3 lines from 1 source:
* `/var/log/php-fpm/error.log` (size: 185.0B)
Output of a host whose pool ran out of workers and whose application crashed a worker:
2026-08-28 15:20 .. 2026-08-28 15:20 (20s): 3 ERROR lines found [CRITICAL] (last: [28-Aug-2026 15:20:15] ERROR: failed to ptrace(ATTACH) child 178: Operation not permitted (1)). 4 WARNING lines found [WARNING] (last: [28-Aug-2026 15:20:21] WARNING: [pool www] child 184 said into stderr: " thrown in /srv/fatal.php on line 3"). Found 1 worker crash [WARNING], 1 killed worker [WARNING], 1 pool saturation [WARNING]. 0 request timeouts in the last 10m (2 in the window read). 0 slow requests in the last 10m (3 in the window read). 2 startups detected (last: [28-Aug-2026 15:20:22] NOTICE: fpm is running, pid 194). 1 reload detected (last: [28-Aug-2026 15:20:22] NOTICE: reloading: execvp("/usr/sbin/php-fpm", {"/usr/sbin/php-fpm", "--daemonize"})). 1 shutdown detected (last: [28-Aug-2026 15:20:25] NOTICE: exiting, bye-bye!).
Error lines:
* [28-Aug-2026 15:20:10] ERROR: failed to ptrace(ATTACH) child 171: Operation not permitted (1)
* [28-Aug-2026 15:20:10] ERROR: failed to ptrace(ATTACH) child 177: Operation not permitted (1)
* [28-Aug-2026 15:20:15] ERROR: failed to ptrace(ATTACH) child 178: Operation not permitted (1)
Warning lines:
* [28-Aug-2026 15:20:21] WARNING: [pool www] child 184 said into stderr: "NOTICE: PHP message: PHP Fatal error: Uncaught Error: Call to undefined function undefined_function_call() in /srv/fatal.php:3"
* [28-Aug-2026 15:20:21] WARNING: [pool www] child 184 said into stderr: "Stack trace:"
* [28-Aug-2026 15:20:21] WARNING: [pool www] child 184 said into stderr: "#0 {main}"
* [28-Aug-2026 15:20:21] WARNING: [pool www] child 184 said into stderr: " thrown in /srv/fatal.php on line 3"
Worker crash:
* [28-Aug-2026 15:20:17] WARNING: [pool www] child 178 exited on signal 11 (SIGSEGV - core dumped) after 4.769535 seconds from start
Killed worker:
* [28-Aug-2026 15:20:19] WARNING: [pool www] child 179 exited on signal 9 (SIGKILL) after 5.345946 seconds from start
Pool saturation:
* [28-Aug-2026 15:20:09] WARNING: [pool www] server reached pm.max_children setting (2), consider raising it
Read 33 lines from 1 source:
* `/var/log/php-fpm/error.log` (size: 3.1KiB)
Recommendations:
* Workers died on a fault; look for a core dump or a faulty PHP extension
* Workers were killed with SIGKILL, either by PHP-FPM because they did not end when the pool shrank or by the kernel when memory ran out; look for `Out of memory` in the kernel log around the timestamps above
* A pool ran out of workers and clients waited in the listen queue; before raising `pm.max_children` (or `process.max`), check whether workers were stuck (request timeouts or hung workers above, a stalled database or NFS mount) or a crawler drove the load (web server access log at that time), because a higher cap only lets either claim more memory
Output of a host where the OOM killer took a worker, next to an idle worker PHP-FPM ended itself:
2026-11-12 17:14 .. 2026-11-12 17:14 (21s): Found 1 OOM kill [WARNING]. 0 spawn pressure warnings in the last 10m (1 in the window read). 1 startup detected (last: [12-Nov-2026 17:14:09] NOTICE: fpm is running, pid 1). 1 worker killed with SIGKILL, not by the OOM killer (last: [12-Nov-2026 17:14:21] WARNING: [pool www] child 21 exited on signal 9 (SIGKILL) after 9.009506 seconds from start).
OOM kill:
* [12-Nov-2026 17:14:30] WARNING: [pool www] child 24 exited on signal 9 (SIGKILL) after 15.122709 seconds from start
Read 9 lines from 1 source:
* `/var/log/php-fpm/error.log` (size: 780.0B)
Recommendations:
* The kernel killed workers because memory ran out, and the requests they were serving are lost; compare `pm.max_children` times the memory one worker takes with what the host or its memory limit leaves for PHP-FPM, and look in the access log of the web server for the request that grew, at the timestamps above
States¶
- CRIT if the window holds
ALERTlines. PHP-FPM only writes those when it rejects its own configuration, and it refuses to start afterwards. - CRIT if the window holds
ERRORlines, and with--critical-levelalso onWARNING. - WARN if the window holds
WARNINGlines, which--warning-levelmoves. Everything a pool reports about a single request has a counter of its own and is not counted here, and neither is the exit of a worker PHP-FPM ended with SIGTERM after a request timeout. - CRIT if a worker hangs after the SIGTERM
request_terminate_timeoutsent, or if PHP-FPM reloaded itself after repeated worker failures. PHP-FPM logs both atWARNING, so neither would stand out by level alone. - WARN if a pool reached
pm.max_children, CRIT if it did so a second time within--lookbackor next to request timeouts or hung workers there. - WARN if a worker crashed, was killed by the kernel's OOM killer, or waited on a connection that never delivered a request until
request_terminate_timeoutended it. - WARN if a worker died on SIGKILL and the kernel log cannot say whether the OOM killer sent it.
- OK if a worker died on SIGKILL and the kernel log holds no OOM kill for it. PHP-FPM ends an idle worker that way when the pool shrinks; the worker is counted and shown.
- WARN or CRIT if more request timeouts, slow requests, spawn pressure warnings or PHP fatal errors arrived within
--lookbackthan--request-timeouts-warning/--request-timeouts-critical,--slow-requests-warning/--slow-requests-critical,--spawn-pressure-warning/--spawn-pressure-criticaland--fatal-errors-warning/--fatal-errors-criticalallow. The lines behind a counter that raised a state are listed. - WARN if the log file is configured but is not an existing regular file.
- WARN if a log this check was told to read could not be read at all. The run goes on with the other sources rather than reporting the state of the ones that happened to work.
- UNKNOWN if not a single line in the window carries a PHP-FPM log level. The source is then something else, the pool
slowlogor the access log for example. - UNKNOWN if no log file could be determined at all, or if
error_logis set tosyslog, which is not a file the check can open. - OK if the log file is empty, which is what a log looks like right after logrotate ran.
- OK with
--no-match-severityat its default when--matchdropped every line; set it towarn,critorunknownto have a filter that matches nothing reported instead. - The size of the log file is reported and trended but never alerted on. PHP-FPM defines no cutoff of its own, and an unrotated log is the business of
logrotateand of the disk-usage check. --always-oksuppresses all alerts and always returns OK.
Perfdata / Metrics¶
| Name | Type | Description |
|---|---|---|
| php_fpm_alert_lines | Number | Number of ALERT lines found in the log. |
| php_fpm_emergency_restarts | Number | Number of times PHP-FPM reloaded itself after emergency_restart_threshold worker failures. |
| php_fpm_error_lines | Number | Number of ERROR lines found in the log; what a pool reported about a single request is counted separately. |
| php_fpm_fatal_errors | Number | Number of PHP fatal errors in the error_log files of the pools, within the lookback window. |
| php_fpm_hung_workers | Number | Number of workers reported as timed out a second time without having exited, so the SIGTERM did not end them. |
| php_fpm_idle_connection_timeouts | Number | Number of workers ended by request_terminate_timeout while they waited on a connection the web server never sent a request over. |
| php_fpm_idle_kills | Number | Number of workers that died on SIGKILL without an OOM kill in the kernel log, which is how PHP-FPM ends an idle worker that does not leave when the pool shrinks. |
| php_fpm_log_rotations | Number | Number of times PHP-FPM re-opened the error log, which is what logrotate makes it do. |
| php_fpm_logfile_size | Bytes | Log file size. |
| php_fpm_oom_kills | Number | Number of workers the kernel's OOM killer ended. |
| php_fpm_pool_saturations | Number | Number of times a pool reached pm.max_children or process.max. |
| php_fpm_reloads | Number | Number of reloads found in the log. |
| php_fpm_request_timeouts | Number | Number of requests terminated after request_terminate_timeout, within the lookback window. |
| php_fpm_shutdowns | Number | Number of shutdowns found in the log. |
| php_fpm_slow_requests | Number | Number of requests that ran longer than request_slowlog_timeout, within the lookback window. |
| php_fpm_spawn_pressure | Number | Number of times a pool had to spawn workers in bursts, within the lookback window. |
| php_fpm_startups | Number | Number of startups found in the log. |
| php_fpm_warning_lines | Number | Number of WARNING lines found in the log; what a pool reported about a single request is counted separately. |
| php_fpm_worker_crashes | Number | Number of workers that died on a fault, a signal PHP-FPM never sends its workers. |
| php_fpm_worker_kills | Number | Number of workers that died on SIGKILL where the kernel log could not say whether the OOM killer sent it. |
Troubleshooting¶
The check reports a healthy log while the application is throwing errors¶
Only PHP's fatal errors are counted, and only from the error_log file a pool names. Warnings, notices and deprecations are left alone on purpose.
- Check that the pool sets
php_admin_value[error_log]to an absolute path. Without it, PHP hands its messages to the web server, and the Apache error log shows them asAH01071: Got error 'PHP message: ...'. - Check that the user the pool runs as can create and write that file. PHP falls back to the web server as well when it cannot open the file, which happens when the directory belongs to another user (on the RHEL family
/var/log/php-fpmisapache:rootmode0770, so a pool running as another user needs a log directory of its own). - Reload PHP-FPM after changing the pool.
Every counter reads zero right after the nightly logrotate¶
The distributions rotate this log daily, and PHP-FPM writes a single error log file re-opened line into the fresh file. The check reads the rotated predecessor along with it, so the window normally reaches back beyond that point and the last section names every file it read, one per line with its size. A window that really is one line means the predecessor is gone or unreadable, not that PHP-FPM is silent. Use systemd-unit to know whether PHP-FPM is running at all; this check reports on what the log says.
does not look like a PHP-FPM error log¶
Not one line in the window carried a PHP-FPM log level or the prefix PHP writes, so the source holds something other than an error log. The two files that get mixed up with it are the pool slowlog and the access log. Check --server-log and the error_log directive of the PHP-FPM configuration; php-fpm --test prints the configuration PHP-FPM actually loads.
error_log is set to syslog¶
PHP-FPM then hands its messages to the syslog daemon and writes no file of its own. Point the check at the unit instead, with --server-log=systemd:php-fpm.service, or at the file the syslog daemon writes them to.
failed to ptrace(ATTACH) child N: Operation not permitted¶
PHP-FPM attaches to a worker to write the backtrace of a slow request into the slowlog, and the kernel refused it. In a container this is the missing CAP_SYS_PTRACE capability; on a host it is usually a hardened kernel.yama.ptrace_scope. The slow request itself is still logged, only its backtrace is missing. Grant the capability, or silence the message with --ignore='failed to ptrace' if the backtraces are not wanted.
Refusing to read "…": resolved path is outside the allowed roots¶
The check runs as root and therefore only opens a log that resolves inside /var/log. Bind-mount the directory holding the log under /var/log; a symlink is rejected, because the confinement resolves symlinks before it decides.
Workers time out without a script¶
[pool www] child 764180, script '' (request: " ") execution timed out (248.831385 sec), terminating
The worker was not running a request. It waited on a FastCGI connection the web server keeps open for reuse, and PHP-FPM counts that wait against request_terminate_timeout like a request that ran too long. Apache does this with enablereuse=on on the ProxySet or SetHandler of the pool. Remove it (or set enablereuse=off) and reload Apache; the check reports these as idle connection timeouts and stays quiet once they stop. Raising request_terminate_timeout only stretches the wait, because the connection never delivers a request.
Hung workers¶
[pool www] child 2101, script '/var/www/nextcloud/remote.php' (request: "PROPFIND ...") execution timed out (5631.104655 sec), terminating, for the same worker again and again
PHP-FPM sent the worker a SIGTERM when it ran over request_terminate_timeout, and the worker is still there at the next check. A process ignores that signal only while it waits in the kernel in uninterruptible sleep, which is almost always I/O on a hung NFS or storage mount. Such workers hold their database connections, so Too many connections and a full pool usually follow.
- Look for PHP-FPM workers in state
Dwithps -eo pid,stat,wchan:32,argsand fornfs: server ... not respondingin the kernel log (sudo journalctl --dmesg). - Bring the storage back. The workers end on their own the moment it answers again; nothing in PHP-FPM can end them before that.
- Raising
pm.max_childrendoes not help: the new workers hang on the same mount.
A pool keeps reaching pm.max_children¶
Every request that arrives while all workers are busy waits in the listen queue, and the visible symptom is a slow site rather than an error page.
- Look at what the check lists next to the saturation. Request timeouts or hung workers at the same time mean the workers were stuck, on a stalled database, a hung NFS mount or a remote API, and more workers would only get stuck the same way.
- Read the web server access log for the minute the check names. A few hundred requests from one address within a few seconds, walking a list of configuration file names and collecting 404s, is a scanner and not a pool that is too small. Where the application renders its own 404 page through PHP, every one of those occupies a worker for as long as a real request does. Rate-limit it at the reverse proxy: raising the limit here would only hand the next burst more workers, and the memory to run them.
- Look at php-fpm-status for how saturated the pool runs on an ordinary day. A quiet status page does not contradict the log. Saturation lasting seconds is over before the next poll, the
max children reachedcounter of the status page resets whenever PHP-FPM restarts, and on adynamicpool the reported total is the live worker count rather thanpm.max_children. - Raise
pm.max_childrenonly as far as the memory of the host allows: multiply the value by the resident size of a worker and keep the result well below the memory available. - Where the requests themselves are slow, raising the limit only buys time. The slow requests in the pool
slowlogname the scripts to profile.
Credits, License¶
- Authors: Linuxfabrik GmbH, Zurich
- License: The Unlicense, see LICENSE file.