Skip to content

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 WARNING or ERROR line 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-level and --warning-level act on.
  • PHP's own messages go to the error_log of the pool. PHP writes its warnings and fatal errors into the file a pool names with php_admin_value[error_log]; the RHEL family sets /var/log/php-fpm/www-error.log in 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-warning and --fatal-errors-critical over --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 as AH01071: Got error 'PHP message: ...' in the Apache error log. What a worker writes to its own standard error reaches this log only with catch_workers_output = yes, wrapped in a WARNING line 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-warning over --lookback instead. The same holds for requests that ran into request_slowlog_timeout or request_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_children and the emergency reload raise their state whatever the levels are set to, because PHP-FPM logs all of them at WARNING and 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. With pm = dynamic or pm = ondemand it 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 worker killed with SIGKILL, not by the OOM killer, which is counted and shown but does not alert. A kill -9 by 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_children once 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:root mode 0600.
  • --server-log is 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 --ignore and --match are 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_log directive in /etc/php-fpm.conf, /etc/php/*/fpm/php-fpm.conf or /usr/local/etc/php-fpm.conf.
  • Reads the error_log files the pools name with php_value[error_log] or php_admin_value[error_log] in /etc/php-fpm.d/*.conf, /etc/php/*/fpm/pool.d/*.conf or /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.log and /usr/local/var/log/php-fpm.log when the configuration yields nothing.
  • Supports reading from a file path, docker:CONTAINER, podman:CONTAINER, kubectl:CONTAINER or systemd:UNITNAME via --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-log names nothing, and counts an event the two share once.
  • When a worker died on SIGKILL, and only then, asks journalctl for the kernel messages at err and 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 --match and 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 ALERT lines. PHP-FPM only writes those when it rejects its own configuration, and it refuses to start afterwards.
  • CRIT if the window holds ERROR lines, and with --critical-level also on WARNING.
  • WARN if the window holds WARNING lines, which --warning-level moves. 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_timeout sent, or if PHP-FPM reloaded itself after repeated worker failures. PHP-FPM logs both at WARNING, 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 --lookback or 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_timeout ended 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 --lookback than --request-timeouts-warning / --request-timeouts-critical, --slow-requests-warning / --slow-requests-critical, --spawn-pressure-warning / --spawn-pressure-critical and --fatal-errors-warning / --fatal-errors-critical allow. 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 slowlog or the access log for example.
  • UNKNOWN if no log file could be determined at all, or if error_log is set to syslog, 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-severity at its default when --match dropped every line; set it to warn, crit or unknown to 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 logrotate and of the disk-usage check.
  • --always-ok suppresses 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.

  1. 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 as AH01071: Got error 'PHP message: ...'.
  2. 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-fpm is apache:root mode 0770, so a pool running as another user needs a log directory of its own).
  3. 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.

  1. Look for PHP-FPM workers in state D with ps -eo pid,stat,wchan:32,args and for nfs: server ... not responding in the kernel log (sudo journalctl --dmesg).
  2. Bring the storage back. The workers end on their own the moment it answers again; nothing in PHP-FPM can end them before that.
  3. Raising pm.max_children does 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.

  1. 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.
  2. 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.
  3. 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 reached counter of the status page resets whenever PHP-FPM restarts, and on a dynamic pool the reported total is the live worker count rather than pm.max_children.
  4. Raise pm.max_children only 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.
  5. Where the requests themselves are slow, raising the limit only buys time. The slow requests in the pool slowlog name the scripts to profile.

Credits, License