Check apache-httpd-logfile¶
Overview¶
Scans the Apache HTTP Server error log for the events an administrator has to act on: children that died on a signal, a server that ran out of workers, processes it failed to fork, backends a reverse proxy could not reach, and stapling switched on for a certificate it cannot work for. Startups, restarts and shutdowns are counted alongside them, so a server that keeps restarting is visible. Alerts when one of those events shows up, when the lines one client provokes cross the rates the thresholds set, and when a line arrives at a level --critical-level or --warning-level covers. What Apache logged about one request and one client - a denied access, a password that did not match, a request line it refused to parse - is counted within --lookback and judged by how many of them arrived, not by the fact that they did: one is a bot or a bad link, hundreds within ten minutes is somebody walking the site or guessing passwords. Those lines are counted there and nowhere else, so the background noise every internet-facing server produces does not keep the check permanently yellow. What is left is what Apache said about itself, and that is counted by the level it wrote at the head of the line: emerg, alert and crit return CRITICAL, error returns WARNING, and --critical-level and --warning-level move that split. The events named above carry their own state and are counted there and nowhere else, because the level says nothing about what happened and Apache logs some of them at notice anyway. A message that merely contains the word "error" never counts, and a LogLevel below warn hides a line from this check just as it hides it from the file. 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 check follows the configuration from the main file through its Include/IncludeOptional files and reads the server's ErrorLog together with every ErrorLog a virtual host sets, so a host whose sites log to their own files is watched where the sites write and not only where the server does; where no configuration can be read, the common locations of the distributions are probed instead. The journal of the Apache unit is read along with them, because a server which fails to start writes to its standard error instead of into the error log, and a rejected configuration or an address already in use is in the journal only. What the sources hold in common 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. Requires root or sudo.
Important Notes:
ErrorLog syslog:hides the lifecycle unlessLogLevelisnotice. A log file carries Apache'snoticelines whateverLogLevelsays; syslog does not, so the defaultwarndrops every start, restart and shutdown while errors still arrive. The check reports the combination when it finds it.- A server that never started is invisible in the error log, which is why the journal is read too. Apache writes a rejected configuration, a port it could not bind and a log it could not open to its standard error and then gives up, so none of it reaches the file; under systemd that output lands in the journal. Everything a running Apache logs goes the other way, into the file and not into the journal. The check therefore reads both by default and counts once what it finds in both, so one service answers both questions. Use systemd-unit to know whether Apache is running at all.
- A line is counted once. A named event (a child crash, a saturated server, stapling that cannot work) carries its own state and is counted as that event, not as an
errorline 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. - Apache's error log holds two different kinds of line, and this check treats them differently. Apache adds a
[client 198.51.100.7:4000]field whenever the line is about one request, and never otherwise - that is not a guess but the boundary in its own logging API. A line carrying that field says something went wrong with one request: a bot denied access to a protected path, a scanner posting/cgi-bin/.%2e/.%2e/bin/sh, a client sending one host name in SNI and another in theHostheader. Those are counted within--lookbackand reported as a rate, because one of them is worth nothing and hundreds of them within ten minutes are worth knowing about. A line without that field is Apache talking about itself, and that one is judged by its level. Deliberately independent of the message code, because the code moves: the same scanner request isAH00126on httpd 2.4.37 andAH10244on 2.4.62, while both are a[core:error]about one client. - What is genuinely the server's fault keeps its own state, even when it arrives per request. A child that died on a signal, a server out of workers and a start Apache refused all raise their own state, whatever their scope and whatever their level; the named events take precedence over the rate.
- A client does not get to choose which source it is counted under. Parts of the lines this check reads are the client's own text - the account it asked for, the identification string it sent - and a client that writes an address into them would otherwise move its own lines into somebody else's count, or spread them out to stay below a threshold. The address is therefore taken from where the server writes the peer and nowhere else, and the two spellings of one client (
198.51.100.7and::ffff:198.51.100.7) are counted as the one client they are. - A rate is counted per source address, not as a total. Six failures from one address within the window is somebody working on this host; six failures spread over six addresses is the open network going past, and only the first is worth reporting. What the state follows is therefore the busiest single source, which is also the quantity an intrusion prevention system counts before it blocks one - so the thresholds compare against the same thing that system does. The summary names that source and, where they differ, the total and how many addresses it came from. Lines that name no source are counted together as one, so a burst of unattributable lines still reports.
--no-per-sourcegoes back to judging everything that arrived, for a log that reaches this check through something that rewrites or drops the address of the peer. Counters that are not about who caused them - a backend that could not be reached, connections refused for want of slots - always judge the total, because the address on such a line says nothing about the cause. - The rate thresholds assume an intrusion prevention system in front of this check. A host reachable from the internet collects failed logins and probes around the clock, and the answer to those is a system that reads the same log, counts what a single source fails within a few minutes and blocks it. Such a system commonly lets five failures per source through before it steps in, so the defaults here sit just above that: what this check reports is what got past the blocking, not what the blocking is already handling. The window is
--lookback, ten minutes by default, which is the same window those systems count in. On a host without one, the counters see every attempt of every source and the defaults are far too tight - raise them until they sit clear of what the host collects on a quiet day, and keep the ratio rather than the absolute number: a threshold is useful when it is a multiple of the normal rate, not when it is a fraction of it.0switches a threshold off entirely. - An unreachable backend is counted, not reported. A
systemctl reload php-fpmleaves exactly oneAH02454: attempt to connect to Unix domain socket ... failedbehind, and a backend that is really gone writes one per request. Six of them across four days is a handful of reloads and must not page anybody; thirty within a minute is an outage. They are therefore judged by--proxy-failures-warningand--proxy-failures-criticalover--lookback. Whether the site answers at all is the job of an HTTP check, not of a log check. - Only
critand above ignore the scope split. Across the whole of Apache barely two dozen request-scoped messages are logged that high, and they are a broken LDAP, Lua or FastCGI backend rather than anything a client can provoke. A request-scopedemerg,alertorcritline therefore still alerts by its level. - The error log is not the access log.
ErrorLogandCustomLogare two different files in two different formats, and only the first one is what this check reads. Watch the access log with the generic logfile check, or the server's own state with apache-httpd-status. - A virtual host writes its own log, and it is read too. The RHEL family ships
ErrorLog logs/ssl_error_loginside the TLS virtual host ofconf.d/ssl.conf, the Debian family ships one inside every site undersites-enabled/, and a host serving several sites usually keeps a file per site. The check follows the configuration through itsInclude/IncludeOptionalfiles and reads all of them next to the server's own log, because a server that writes almost nothing but its own lifecycle into the main log would otherwise look quiet while a site it serves is being walked. To watch one site as a service of its own instead, name its log with--server-log. - The window spans the last rotation. logrotate moves the old file aside (
error_log-20260828on the RHEL family,error.log.1on the Debian family, then.gz) and reloads Apache, which opens a fresh one. A check reading the live file alone would report a healthy server an hour after it broke. 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 restart counts as a startup as well. Apache logs
resuming normal operationsafter every restart and every graceful reload, so both counters move. The nightly logrotate reloads Apache and therefore shows up as a restart. - Apache reports a saturated server once per generation.
AH00484: server reached MaxRequestWorkers settingis written once and then suppressed until the next restart, so the counter says how often the situation returned rather than how many requests waited for a worker. apache-httpd-status is what shows how close to the limit the server runs on an ordinary day. - 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 the restart alone, and a run reporting no startup at all is then telling the truth about that handful of lines rather than about the day. It also means an event keeps being reported until it leaves the window or the service is acknowledged (see
--icinga-callback). The counted events are the exception: their state follows--lookbackand falls back on its own as the burst ages out. - Reading the error log needs root or sudo. The RHEL family installs
/var/log/httpdasroot:rootmode0700. --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 Apache configuration names. The RHEL family'sErrorLog logs/error_logpasses because/etc/httpd/logsis a symlink to/var/log/httpd, which resolves inside. To read a log stored elsewhere, bind-mount that location under/var/log.- Both
--ignoreand--matchare matched against the lowercased log line, so write the patterns in lowercase (or use the(?i)flag).
Data Collection:
- Determines the log files automatically from the
ErrorLogdirectives in/etc/httpd/conf/httpd.conf,/etc/apache2/apache2.conf,/etc/apache2/httpd.confor/usr/local/apache2/conf/httpd.confand everything those files pull in withIncludeorIncludeOptional, the server's own log and every virtual host's. A relative path is resolved againstServerRootwherever the directive using it stands, and${APACHE_LOG_DIR}and the other variables of the Debian family are resolved against/etc/apache2/envvars. - Falls back to probing
/var/log/httpd/error_log,/var/log/apache2/error.log,/var/log/apache2/error_log,/var/log/httpd/error.logand/usr/local/apache2/logs/error_logwhen no configuration could be read, and picks up what looks like a site's log (<servername>-error.log) next to it. - 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. - Reads the journal of the Apache unit along with the files where
--server-lognames nothing, and counts an event they share once. - 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.
- Recognizes a line by the level Apache puts in it (
[core:error]) or by the message code every Apache message carries (AH00484), wherever the configuredErrorLogFormatplaces them. - Lines can be narrowed down with
--matchand filtered out with--ignore, both Python regular expressions. - Prints the source every count came from and the command that finds the same lines again, so a number can be checked instead of believed. A counter whose criterion is not a single pattern gets that criterion spelled out rather than a command that would find something else.
Fact Sheet¶
| Fact | Value |
|---|---|
| Check Plugin Download | https://github.com/Linuxfabrik/monitoring-plugins/tree/main/check-plugins/apache-httpd-logfile |
| Nagios/Icinga Check Name | check_apache_httpd_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-apache-httpd-logfile-*.db (only with --icinga-callback) |
Help¶
usage: apache-httpd-logfile [-h] [-V] [--always-ok]
[--auth-failures-critical AUTH_FAILURES_CRITICAL]
[--auth-failures-warning AUTH_FAILURES_WARNING]
[--client-denials-critical CLIENT_DENIALS_CRITICAL]
[--client-denials-warning CLIENT_DENIALS_WARNING]
[--critical-level {emerg,alert,crit,error,warn,none}]
[--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-per-source] [--no-perfdata] [--no-proxy]
[--per-source] [--proxy PROXY]
[--proxy-failures-critical PROXY_FAILURES_CRITICAL]
[--proxy-failures-warning PROXY_FAILURES_WARNING]
[--request-errors-critical REQUEST_ERRORS_CRITICAL]
[--request-errors-warning REQUEST_ERRORS_WARNING]
[--server-log SERVER_LOG] [--timeout TIMEOUT]
[--warning-level {emerg,alert,crit,error,warn,none}]
Scans the Apache HTTP Server error log for the events an administrator has to
act on: children that died on a signal, a server that ran out of workers,
processes it failed to fork, backends a reverse proxy could not reach, and
stapling switched on for a certificate it cannot work for. Startups, restarts
and shutdowns are counted alongside them, so a server that keeps restarting is
visible. Alerts when one of those events shows up, when the lines one client
provokes cross the rates the thresholds set, and when a line arrives at a
level `--critical-level` or `--warning-level` covers. What Apache logged about
one request and one client - a denied access, a password that did not match, a
request line it refused to parse - is counted within `--lookback` and judged
by how many of them arrived, not by the fact that they did: one is a bot or a
bad link, hundreds within ten minutes is somebody walking the site or guessing
passwords. Those lines are counted there and nowhere else, so the background
noise every internet-facing server produces does not keep the check
permanently yellow. What is left is what Apache said about itself, and that is
counted by the level it wrote at the head of the line: `emerg`, `alert` and
`crit` return CRITICAL, `error` returns WARNING, and `--critical-level` and
`--warning-level` move that split. The events named above carry their own
state and are counted there and nowhere else, because the level says nothing
about what happened and Apache logs some of them at `notice` anyway. A message
that merely contains the word "error" never counts, and a `LogLevel` below
`warn` hides a line from this check just as it hides it from the file. 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
check follows the configuration from the main file through its
`Include`/`IncludeOptional` files and reads the server's `ErrorLog` together
with every `ErrorLog` a virtual host sets, so a host whose sites log to their
own files is watched where the sites write and not only where the server does;
where no configuration can be read, the common locations of the distributions
are probed instead. The journal of the Apache unit is read along with them,
because a server which fails to start writes to its standard error instead of
into the error log, and a rejected configuration or an address already in use
is in the journal only. What the sources hold in common 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. 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.
--auth-failures-critical AUTH_FAILURES_CRITICAL
Number of authentication failures within `--lookback`
that returns CRITICAL. 0 turns the threshold off.
Example: `--auth-failures-critical=200`. Default: 60
--auth-failures-warning AUTH_FAILURES_WARNING
Number of authentication failures within `--lookback`
that returns WARNING. 0 turns the threshold off.
Example: `--auth-failures-warning=20`. Default: 6
--client-denials-critical CLIENT_DENIALS_CRITICAL
Number of denied requests within `--lookback` that
returns CRITICAL. 0 turns the threshold off. Example:
`--client-denials-critical=200`. Default: 60
--client-denials-warning CLIENT_DENIALS_WARNING
Number of denied requests within `--lookback` that
returns WARNING. 0 turns the threshold off. Example:
`--client-denials-warning=50`. Default: 6
--critical-level {emerg,alert,crit,error,warn,none}
Least severe Apache log level that returns CRITICAL.
Each level includes everything more severe than
itself, so `error` covers `crit`, `alert` and `emerg`
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=error`. Default: crit
--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='ah01630'`.
--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 Denied requests, failed passwords and failed requests
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='\[ssl:'`.
--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-per-source Judge a rate by everything that arrived within the
window, whatever source the lines name. Use this where
the log reaches this check through something that
rewrites or drops the address of the peer, or where
every source is as interesting as the next.
--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`.
--per-source Judge a rate by the busiest single source address
rather than by everything that arrived. A handful of
failures from one address within the window is
somebody working on this host; the same number spread
over as many addresses is the background of an open
network going past, and only the first is worth
reporting. Lines that name no source are counted
together as one, so a burst of those still reports.
Default: True
--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`.
--proxy-failures-critical PROXY_FAILURES_CRITICAL
Number of unreachable backends within `--lookback`
that returns CRITICAL. 0 turns the threshold off.
Example: `--proxy-failures-critical=50`. Default: 100
--proxy-failures-warning PROXY_FAILURES_WARNING
Number of unreachable backends within `--lookback`
that returns WARNING. One of them is a backend being
restarted, so this counts how many arrived rather than
that any did. 0 turns the threshold off. Example:
`--proxy-failures-warning=1`. Default: 10
--request-errors-critical REQUEST_ERRORS_CRITICAL
Number of failed requests within `--lookback` that
returns CRITICAL. Counts what Apache logged about one
request and one client, denied requests and failed
passwords excluded, as those have counters of their
own. 0 turns the threshold off. Example: `--request-
errors-critical=200`. Default: 60
--request-errors-warning REQUEST_ERRORS_WARNING
Number of failed requests within `--lookback` that
returns WARNING. Counts what Apache logged about one
request and one client, denied requests and failed
passwords excluded, as those have counters of their
own. 0 turns the threshold off. Example: `--request-
errors-warning=50`. Default: 6
--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 `ErrorLog` of the main
Apache configuration file and of every virtual host it
configures, falls back to the common locations of the
distributions, and reads the journal of the Apache
unit along with them; what they share is counted once.
Example: `--server-log=systemd:httpd.service`.
--timeout TIMEOUT Network timeout in seconds. Default: 8 (seconds)
--warning-level {emerg,alert,crit,error,warn,none}
Least severe Apache 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=warn`. Default: error
Documentation:
https://linuxfabrik.github.io/monitoring-plugins/check-plugins/apache-httpd-logfile/
Usage Examples¶
./apache-httpd-logfile
./apache-httpd-logfile --server-log=/var/log/apache2/error.log
./apache-httpd-logfile --server-log=systemd:httpd.service
./apache-httpd-logfile --server-log=podman:httpd
# Page on every `error` line as well, on a host where the configuration denies nothing
# and no application logs through Apache.
./apache-httpd-logfile --critical-level=error
# An internet-facing site behind a WAF that already answers scans. Report a walk of the
# site only once it is large enough to matter, and never at night.
./apache-httpd-logfile --client-denials-warning=1000 --client-denials-critical=0
# A login form that is being guessed at should be seen within the hour rather than
# within ten minutes.
./apache-httpd-logfile --lookback=3600 --auth-failures-warning=200
# A quiet internal server, where a single failed request is already worth a look.
./apache-httpd-logfile --request-errors-warning=1
# A reverse proxy in front of a single application, where the backend going away at all
# is worth knowing about.
./apache-httpd-logfile --proxy-failures-warning=1
# Silence the mod_ssl notice a container image produces when it was built against a
# different OpenSSL. Everything else stays visible.
./apache-httpd-logfile --ignore='ah01882'
# Watch the log of one virtual host as a service of its own.
./apache-httpd-logfile --server-log=/var/log/httpd/shop_error_log
# Read the server's log and one site's log as one window.
./apache-httpd-logfile --server-log=/var/log/httpd/error_log --server-log=/var/log/httpd/shop_error_log
Output of a healthy host:
2026-08-28 17:12 .. 2026-08-28 17:12 (3s): No errors or warnings found. 1 startup detected (last: [Fri Aug 28 17:12:25.860583 2026] [mpm_event:notice] [pid 1929:tid 1929] AH00489: Apache/2.4.62 (Rocky Linux) configured -- resuming normal operations). 1 shutdown detected (last: [Fri Aug 28 17:12:28.865860 2026] [mpm_event:notice] [pid 1929:tid 1929] AH00491: caught SIGTERM, shutting down).
Read 6 lines from 1 source:
* `/var/log/httpd/error_log` (size: 815.0B)
Output of a host that ran out of workers, lost a child to a segfault and could not reach its backend:
2026-08-28 17:11 .. 2026-08-28 17:12 (27s): 1 error line found [WARNING] (last: [Fri Aug 28 17:12:07.128098 2026] [cgid:error] [pid 1835:tid 1835] AH01239: cgid daemon process died, restarting). Found 1 child crash [WARNING], 1 worker saturation [CRITICAL]. 2 startups detected (last: [Fri Aug 28 17:12:08.167550 2026] [mpm_event:notice] [pid 1835:tid 1835] AH00489: Apache/2.4.62 (Rocky Linux) configured -- resuming normal operations). 1 restart detected (last: [Fri Aug 28 17:12:08.157150 2026] [mpm_event:notice] [pid 1835:tid 1835] AH00493: SIGUSR1 received. Doing graceful restart). 1 shutdown detected (last: [Fri Aug 28 17:12:11.175839 2026] [mpm_event:notice] [pid 1835:tid 1835] AH00491: caught SIGTERM, shutting down); Last 10m: 0 client denials (1 in the whole window). 0 proxy failures (2 in the whole window).
Error lines:
* [Fri Aug 28 17:12:07.128098 2026] [cgid:error] [pid 1835:tid 1835] AH01239: cgid daemon process died, restarting
Read 17 lines from 1 source:
* `/var/log/httpd/error_log` (size: 2.4KiB)
Where the numbers come from:
* 1 child crash:
grep -n -e '\bAH00050\b' -e '\bAH00051\b' -e '\bAH00052\b' -e '\bAH00060\b' /var/log/httpd/error_log
* 1 worker saturation:
grep -n -e '\bAH00161\b' -e '\bAH00286\b' -e '\bAH00484\b' -e '\bAH00288\b' -e '\bAH03490\b' /var/log/httpd/error_log
* 1 client denial:
grep -n -e '\bAH01630\b' -e '\bAH01797\b' /var/log/httpd/error_log
* 2 proxy failures:
grep -n -e '\bAH00860\b' -e '\bAH00866\b' -e '\bAH00896\b' -e '\bAH00898\b' -e '\bAH00939\b' -e '\bAH00940\b' -e '\bAH00957\b' -e '\bAH00958\b' -e '\bAH00959\b' -e '\bAH01079\b' -e '\bAH01102\b' -e '\bAH01110\b' -e '\bAH01114\b' -e '\bAH02452\b' -e '\bAH02454\b' -e '\bAH10101\b' /var/log/httpd/error_log
Recommendations:
* Children died on a signal Apache did not send them; look for a core dump, a faulty module, or the OOM killer in the kernel log
* The server ran out of workers; raise `MaxRequestWorkers` (and `ServerLimit` with it) or shorten the requests, otherwise clients wait in the listen queue
Output of a host somebody is walking:
2026-08-28 17:34 .. 2026-08-28 17:34 (4s): 1 startup detected (last: [Fri Aug 28 17:34:38.859461 2026] [mpm_event:notice] [pid 2335:tid 2335] AH00489: Apache/2.4.62 (Rocky Linux) configured -- resuming normal operations). 1 shutdown detected (last: [Fri Aug 28 17:34:42.936750 2026] [mpm_event:notice] [pid 2335:tid 2335] AH00491: caught SIGTERM, shutting down); Last 10m: 0 client denials (4 in the whole window). 0 authentication failures (2 in the whole window).
Read 12 lines from 1 source:
* `/var/log/httpd/error_log` (size: 1.8KiB)
Where the numbers come from:
* 4 client denials:
grep -n -e '\bAH01630\b' -e '\bAH01797\b' /var/log/httpd/error_log
* 2 authentication failures:
grep -n -e '\bAH01614\b' -e '\bAH01617\b' -e '\bAH01618\b' -e '\bAH01631\b' -e '\bAH01807\b' -e '\bAH01808\b' /var/log/httpd/error_log
States¶
- CRIT if the window holds
emerg,alertorcritlines, and with--critical-levelalso on the levels below them. - WARN if the window holds
errorlines, and with--warning-levelalso onwarn. - CRIT if the window holds a saturated server, a process Apache could not fork, or a start Apache refused to complete. Apache logs a saturated server at
errorand a refused start without any level at all, so neither of them would be reported by level alone. - WARN if the window holds a child that died on a signal, or a server that came within
MinSpareThreadsofMaxRequestWorkers. Apache logs a dead child atnotice. - WARN or CRIT if more denied requests, failed passwords, unreachable backends or other failed requests arrived within
--lookbackthan--proxy-failures-warning/--proxy-failures-critical,--client-denials-warning/--client-denials-critical,--auth-failures-warning/--auth-failures-criticaland--request-errors-warning/--request-errors-criticalallow. A single one of any of them never alerts, and none of them is counted by its level. - WARN if OCSP stapling is switched on for a certificate it cannot be set up for. Apache says so once per certificate on every start and then serves TLS without stapling.
- WARN if the log file is configured but is not an existing regular file.
- UNKNOWN if not a single line in the window carries an Apache log level or message code. The source is then something else, the access log for example.
- UNKNOWN if no log file could be determined at all, or if
ErrorLoghands the log to syslog or pipes it into a program and no virtual host names a file either, neither of the two being a file the check can open. Where the virtual hosts do name files, those carry the run and the output says that the server's own log is missing from it. - 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.
- 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. Apache 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 |
|---|---|---|
| apache_httpd_alert_lines | Number | Number of alert lines found in the log. |
| apache_httpd_auth_failures | Number | Number of failed passwords and unknown users within the rate window. |
| apache_httpd_child_crashes | Number | Number of children that died on a signal Apache did not send them. |
| apache_httpd_client_denials | Number | Number of requests the configuration denied within the rate window. |
| apache_httpd_crit_lines | Number | Number of crit lines found in the log. |
| apache_httpd_emerg_lines | Number | Number of emerg lines found in the log. |
| apache_httpd_error_lines | Number | Number of error lines about the server itself; what Apache logged about one request is counted separately. |
| apache_httpd_fork_failures | Number | Number of processes Apache failed to fork. |
| apache_httpd_logfile_size | Bytes | Log file size. |
| apache_httpd_proxy_failures | Number | Number of times a backend could not be reached or its reply broke off, within the lookback window. |
| apache_httpd_request_errors | Number | Number of requests Apache logged a problem about within the lookback window, denied requests and failed passwords excluded. |
| apache_httpd_restarts | Number | Number of restarts and graceful reloads found in the log. |
| apache_httpd_shutdowns | Number | Number of shutdowns found in the log. |
| apache_httpd_stapling_failures | Number | Number of lines about a certificate OCSP stapling could not be set up for. |
| apache_httpd_startup_failures | Number | Number of starts Apache refused to complete. |
| apache_httpd_startups | Number | Number of startups found in the log, restarts included. |
| apache_httpd_warn_lines | Number | Number of warn lines about the server itself; what Apache logged about one request is counted separately. |
| apache_httpd_worker_pressure | Number | Number of times the server came within MinSpareThreads of MaxRequestWorkers. |
| apache_httpd_worker_saturations | Number | Number of times the server reached MaxRequestWorkers or filled its scoreboard. |
Troubleshooting¶
The check is green while the site is down¶
The error log only holds what a running Apache wrote. A server that never came up wrote its reason to standard error and exited, and a server that is simply not running writes nothing at all.
systemctl status httpd(orapache2) says whether it is running, and systemd-unit is what monitors that.--server-log=systemd:httpd.servicepoints this check at the journal instead of at the file, where a rejected configuration and an address already in use are visible.apachectl configtestnames the directive Apache refused, without touching the running server.
The check keeps reporting the same lines¶
Every run reads a window of the log rather than only what is new, so a line keeps being reported until it leaves the window or logrotate moves it away. That is what makes the startup, restart and shutdown counts meaningful. Acknowledge the service and hand the check --icinga-callback together with the credentials of the monitoring server, and the lines it currently reports are remembered as handled and stop raising an alert.
The check is permanently yellow on an internet-facing host¶
Look at which lines it names. Anything Apache logged about one request is already counted as a rate and cannot do this, so what is left is a line about the server itself.
AH02218/AH02604mean OCSP stapling is switched on for a certificate whose issuer publishes no OCSP responder, which is every certificate Let's Encrypt issues today. TurnSSLUseStapling offfor that host; stapling is doing nothing there either way.AH01882(mod_ssl built against a different OpenSSL) andAH01909(certificate name does not match the server name) come from the image or the certificate and are fixed there, or dropped from this check with--ignore='ah01882'.- For any other recurring message, a per-module
LogLevel authz_core:critin the Apache configuration keeps it out of the file entirely, which is better than filtering it here. - Where the noise comes from an application behind
mod_proxy_fcgi(AH01071: Got error 'PHP message: ...'), the application's own log is the better place to watch it, with the generic logfile check.
The check does not alert although the log is full of error lines¶
Read the lines. If they carry a [client ...] field, Apache logged them about one request and this check counts them as a rate rather than by their level, which is what keeps a scanned server green. The Last ...: group says how many arrived recently, and the ... in the whole window note next to each how many the whole window holds.
- Lower
--request-errors-warningto have a smaller number of them reported. On a server nobody but the application talks to,--request-errors-warning=1is reasonable. - Widen
--lookbackto judge over a longer stretch, for instance--lookback=3600. - A line about the server itself is never affected by this: it counts by its level, unless it is one of the named events, which carry their own state and are counted there and nowhere else.
proxy failures under Last ... on a host where nothing is broken¶
mod_proxy logs one line per request it could not hand to a backend, so restarting the backend leaves one line behind and a backend that is gone leaves a stream of them. The count is what separates the two.
- Compare the number against how often the backend was restarted.
systemctl show php-fpm --property=ExecMainStartTimestampsays when it last came up. - Where a single one already matters,
--proxy-failures-warning=1reports it. - Whether the site answers at all is not something a log can say. Watch that with an HTTP check against the site itself.
does not look like an Apache error log¶
Not one line in the window carried an Apache log level or an AHnnnnn message code, so the source holds something else. The file that gets mixed up with it most often is the access log, which CustomLog writes in a completely different format. Check --server-log and the ErrorLog directive; apachectl -S prints the paths Apache actually uses, virtual hosts included.
ErrorLog hands the log to syslog, or pipes it into a program¶
ErrorLog syslog:local1 gives the messages to the syslog daemon, and ErrorLog "|/usr/sbin/rotatelogs ..." hands them to a program; in neither case is there a file this check can open. Where the virtual hosts write into files of their own, those are read anyway and the output says that the server's own log is not part of the run. Where they do not, point the check at the unit with --server-log=systemd:httpd.service, or at the file the syslog daemon or the rotator writes.
Logging to syslog also costs the lifecycle: Apache writes a notice line into a log file whatever LogLevel says, but through syslog the level applies, so the default LogLevel warn drops every start, restart and shutdown while errors still arrive. Measured on Rocky 9 (httpd 2.4.62, rsyslog 8.2510): with LogLevel warn a denied request (AH01630, level error) reaches the journal, while AH00489, AH00493 and AH00491 reach neither the journal nor the files rsyslog writes; with LogLevel notice all of them are there. The check says so when it finds that combination.
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 that points out of it is rejected, because the confinement resolves symlinks before it decides.
The server keeps reaching MaxRequestWorkers¶
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 apache-httpd-status for how saturated the server runs on an ordinary day.
- Raise
MaxRequestWorkersonly as far as the memory of the host allows, and raiseServerLimitwith it where the MPM needs more child processes to reach the new number. - Where the requests themselves are slow, raising the limit only buys time. A reverse proxy that waits for a backend and an application that waits for a database both show up here as saturation.
Credits, License¶
- Authors: Linuxfabrik GmbH, Zurich
- License: The Unlicense, see LICENSE file.