Check cert¶
Overview¶
Inspects an X.509 certificate and alerts on days remaining until expiry, hostname mismatch and chain verification failures. Two sources via --source: url connects to a TLS endpoint, runs a TLS handshake and captures the server certificate; file reads one or many local certificate files, with glob expansion for batch monitoring of cert directories. With --source=url the plugin only runs the TLS handshake and reads the server certificate (no HTTP request is sent), so it works for any "TLS from start" service - HTTPS, IMAPS, LDAPS, SMTPS, AMQPS, MQTTS, custom TLS ports. STARTTLS-style upgrades on plaintext ports (SMTP 587, IMAP 143, LDAP 389) are not supported. p12 and jks sources are reserved for future expansion without renaming the plugin or breaking existing service templates.
Important Notes:
--source=url: Hostname mismatch and chain verification failures share one--severity(default WARN), so operators running internal CAs are not paged for trust issues that are expected in their environment. Set--severity=critto enforce strict trust.--source=file: chain and hostname checks are not performed. Only days remaining is evaluated. With a glob that matches many files, the worst state across all matches drives the plugin state.- Expired certificates are unconditionally reported as CRIT, regardless of the
--warningand--criticalthresholds. --insecureskips chain and hostname verification entirely (only meaningful for--source=url). The certificate is still fetched and inspected, the chain verdict is then reported as "verification skipped".- When a PEM file contains multiple certificates (for example
fullchain.pem, a CA bundle, or a chain file), each certificate becomes its own item in the output and is checked independently. So afullchain.pemwith leaf plus one intermediate produces two rows in the table; the worst state across all of them drives the plugin state. To check only the leaf, point--filenameat a single-cert file likecert.pem. - What "chain verified" actually covers: the leaf chains to a trust anchor in the system trust store (or
--ca-file) using the intermediates the server sent or that the local trust store has cached, every certificate in the verified path is within itsnotBefore/notAfterwindow, every signature in that path is cryptographically valid, and the value of--sni-hostname(or, when not set, the host part of--url) appears in the leaf certificate'ssubjectAltName(orCommonNamefor legacy certs). What it does not cover: OCSP responder lookups, CRL checks, Certificate Transparency / SCT validation, enforcement of the order in which the server sends the chain, detection of SHA-1 in the verified chain, and the completeness of the chain the server sent (a server that omits intermediates may still verify locally if they are cached, but break for clients with empty caches). For those compliance-style checks use a dedicated TLS scanner likesslyze.
Data Collection:
--source=url: opens a TCP connection to the host and port from--url, runs a TLS handshake and reads the server certificate. No HTTP request is sent. The chain is verified against the system trust store; pass--ca-fileto use a custom CA bundle.--sni-hostnameoverrides the SNI value sent during the handshake;--client-certand--client-keyattach a client certificate for mutual TLS.--source=file: reads each file matching--filename(PEM or DER, autodetected) and parses every certificate found. PEM bundles expand to one item per certificate.--filenamesupports glob (*,?,[abc]) and recursive glob (**). When the glob matches files that don't look like certificates (private keys, plain text), they are silently skipped so recursive scans of/etc/ssl/**are safe. Always quote the glob pattern, otherwise the shell expands it before the plugin sees it and only the first match reaches--filename.
Fact Sheet¶
| Fact | Value |
|---|---|
| Check Plugin Download | https://github.com/Linuxfabrik/monitoring-plugins/tree/main/check-plugins/cert |
| Nagios/Icinga Check Name | check_cert |
| Check Interval Recommendation | Every day |
| Can be called without parameters | No (--source plus --url or --filename) |
| Runs on | Cross-platform |
| Compiled for Windows | No (runs with Python interpreter) |
| 3rd Party Python modules | cryptography |
Help¶
usage: cert [-h] [-V] [--always-ok] [--ca-file CA_FILE]
[--client-cert CLIENT_CERT] [--client-key CLIENT_KEY] [-c CRIT]
[--filename FILENAME] [--insecure] [--lengthy]
[--severity {crit,warn}] [--sni-hostname SNI_HOSTNAME]
[--source {file,url}] [--timeout TIMEOUT] [--url URL] [-w WARN]
Inspects an X.509 certificate and alerts on days remaining until expiry,
hostname mismatch and chain verification failures. Sources via --source: `url`
fetches the certificate from a TLS endpoint and verifies the chain against the
system trust store by default (override with --ca-file); `file` reads one or
many certificate files via glob expansion (PEM or DER). PEM bundles expand to
one item per certificate, so a fullchain.pem produces a row for the leaf and a
row for each intermediate. With --source url the plugin only runs the TLS
handshake and reads the server certificate; no HTTP request is sent. That
means it works for any "TLS from start" service, not only HTTPS: IMAPS (port
993), LDAPS (636), SMTPS (465), AMQPS (5671), MQTTS (8883), custom TLS ports -
just point --url at the right host and port (`https://mail.example.com:993/`
inspects the IMAPS cert). STARTTLS protocols that upgrade an existing
plaintext connection (SMTP submission on 587, IMAP on 143, LDAP on 389) are
not supported. Hostname mismatch and chain verification failures share one
--severity (warn or crit) and only apply to --source url. Expired certificates
are unconditionally reported as CRIT. With --source file, the worst state
across all matched files drives the plugin state.
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
--always-ok Always returns OK.
--ca-file CA_FILE Path to a CA bundle in PEM format used for chain
verification. Default uses the system trust store.
--client-cert CLIENT_CERT
Path to a client certificate in PEM format for mutual
TLS.
--client-key CLIENT_KEY
Path to the client certificate private key in PEM
format.
-c, --critical CRIT CRIT threshold for days remaining until the
certificate expires. Supports Nagios ranges. Example:
`5:` (CRIT when below 5 days). Default: 5:
--filename FILENAME Path to a certificate file or a glob pattern matching
multiple certificate files. Required when
--source=file. Files are read as PEM or DER
(autodetected); when a PEM bundle contains multiple
certificates (typical for fullchain.pem or a CA
bundle), each certificate becomes its own row in the
output. Globs follow Python conventions: `*` matches
one path segment, `**` matches across directories.
Always quote the pattern in shells so that the shell
does not expand the wildcard before the plugin sees
it. Example: `--filename='/etc/ssl/certs/*.pem'`.
Recursive example:
`--filename='/etc/letsencrypt/live/**/cert.pem'`
--insecure Skip chain and hostname verification entirely. The
certificate is still fetched and inspected, but the
chain verdict is reported as "verification skipped".
--lengthy Extended reporting.
--severity {crit,warn}
Severity assigned to hostname mismatch and chain
verification failures. Defaults to warn so that
operators running internal CAs are not paged by trust
issues that are expected in their environment. Set to
`crit` to enforce strict trust.
--sni-hostname SNI_HOSTNAME
SNI hostname sent during the TLS handshake and used
for hostname verification. Useful when --url points at
an IP address or a load balancer that needs an
explicit SNI. Default uses the hostname from --url.
--source {file,url} Where the certificate is fetched from. `url` fetches
it from a TLS endpoint (requires --url). `file` reads
it from one or many local files (requires --filename,
supports glob patterns). `p12` and `jks` are reserved
for future expansion. Default: url
--timeout TIMEOUT Network timeout in seconds. Default: 8 (seconds)
--url URL URL of the TLS endpoint to inspect. Required when
--source=url. Example: `https://www.example.com/`
-w, --warning WARN WARN threshold for days remaining until the
certificate expires. Supports Nagios ranges. Example:
`14:` (WARN when below 14 days). Default: 14:
Usage Examples¶
./cert --source=url --url=https://www.example.com/
Output (default, OK):
www.example.com, 61d left, chain verified|'cert_days_left'=61d;14:;5: 'tls_handshake_time'=0.07s;;;0
Self-signed certificate, default --severity=warn:
./cert --source=url --url=https://internal.example.com/
Output:
internal.example.com, 725d left [WARNING], chain unverified (self-signed certificate)|'cert_days_left'=725d;14:;5: 'tls_handshake_time'=0.5s;;;0
Skip chain verification entirely (still inspect the cert), pin a custom CA bundle, attach mTLS client cert, override SNI:
./cert --source=url --url=https://api.example.com/ --insecure
./cert --source=url --url=https://api.example.com/ --ca-file=/etc/pki/ca-trust/source/anchors/internal.pem
./cert --source=url --url=https://api.example.com/ --client-cert=/etc/icinga2/client.pem --client-key=/etc/icinga2/client.key
./cert --source=url --url=https://10.0.0.5/ --sni-hostname=api.example.com
Inspect a local certificate file:
./cert --source=file --filename=/etc/letsencrypt/live/example.com/cert.pem
Batch-inspect every certificate under a directory via glob, including recursive descent. Note the single quotes - they keep the shell from expanding the wildcard before the plugin sees it:
./cert --source=file --filename='/etc/ssl/certs/*.pem' --warning=14: --critical=5:
./cert --source=file --filename='/etc/pki/**/*.crt'
./cert --source=file --filename='/etc/letsencrypt/live/**/cert.pem'
Inspect a non-HTTPS TLS service - point --url at the service's TLS port:
./cert --source=url --url=https://mail.example.com:993/ # IMAPS
./cert --source=url --url=https://ldap.example.com:636/ # LDAPS
./cert --source=url --url=https://smtp.example.com:465/ # SMTPS
Output (multiple files, one expired):
3 certificates checked, worst -42d left [CRITICAL]
File ! Subject CN ! Days Left ! State
--------------------------------------+-------------------------+-----------+-----------
/etc/ssl/certs/web1.pem ! www1.example.com ! 90 ! [OK]
/etc/ssl/certs/web2.pem ! www2.example.com ! -42 ! [CRITICAL]
/etc/ssl/certs/web3.pem ! www3.example.com ! 100 ! [OK]
With --lengthy:
www.example.com, 61d left, chain verified
Field ! Value
--------------------+--------------------------------------------------
Subject CN ! www.example.com
Issuer CN ! R13
Serial ! 5700EBF15B911BC3D902A0BFE488552C112
Signature Algorithm ! sha256WithRSAEncryption
Public Key ! RSA 4096
SANs ! www.example.com
Not Before ! 2026-04-11T22:19:04Z
Not After ! 2026-07-10T22:19:03Z
SHA-256 Fingerprint ! BF:DF:BB:23:93:A9:31:CD:8B:B9:A9:61:18:6D:0A:07
TLS Version ! TLSv1.3
Chain ! verified
States¶
- OK if the certificate is within
--warningand--criticalthresholds, the chain verifies and the hostname matches. - WARN if days remaining hits
--warning(default14:), or chain/hostname verification fails and--severityiswarn(default). - CRIT if days remaining hits
--critical(default5:), the certificate is expired, or chain/hostname verification fails and--severityiscrit. - UNKNOWN on connection errors, TLS handshake failures, missing
--url, missingcryptographyPython module, or invalid command-line arguments. --always-oksuppresses all alerts and always returns OK.--insecurereports the chain as "verification skipped" and never raises a chain-related state.
Perfdata / Metrics¶
| Name | Type | Description |
|---|---|---|
| cert_days_left | Days | Days remaining until the certificate's notAfter date. Negative when expired. |
| tls_handshake_time | Seconds | Wall-clock time for the TCP connect plus TLS handshake. |
Troubleshooting¶
Python module "cryptography" is not installed.
Install cryptography: pip install cryptography or dnf install python3-cryptography.
Cannot connect to host:port: ...
The TCP connection failed. Check DNS, firewall, the port number in --url and --timeout.
TLS handshake failed for host:port: ...
The server rejected the TLS handshake. Possible causes: TLS version mismatch, unsupported cipher, missing SNI (try --sni-hostname), missing client certificate (try --client-cert / --client-key).
chain unverified (...)
The chain did not verify against the system trust store. The reason in parentheses is the OpenSSL verification message; common values are self-signed certificate, unable to get local issuer certificate, certificate has expired and Hostname mismatch, certificate is not valid for .... Pass --ca-file for internal CAs, --sni-hostname for hostname mismatches caused by SNI, or --insecure to bypass the check entirely.
No files match "<pattern>"
The glob did not expand to any path. Two likely causes: the path is wrong, or the shell expanded the glob before the plugin saw it. Always quote glob patterns: --filename='/etc/ssl/**/*.pem' instead of --filename=/etc/ssl/**/*.pem.
No parseable certificates among N file(s) matching "<pattern>"
The glob matched files, but none of them looked like a certificate (no PEM BEGIN CERTIFICATE marker and no DER signature). Tighten the pattern (for example *.crt instead of *) or point --filename directly at a known cert file.
Cannot parse certificate from /path/to/file: ...
The file looks like a certificate (PEM marker present or DER prefix detected) but the content is corrupt or in an unsupported encoding. Inspect the file with openssl x509 -in /path/to/file -noout -text to see the underlying error.
Cannot read /path/to/file: ...
The plugin could not read the file. Typical cause is permissions: certificate files are sometimes mode 0600 and owned by the service account, in which case the monitoring user has no read access. Either grant read permission or copy the public certificate to a path the monitoring user can read.
Credits, License¶
- Authors: Linuxfabrik GmbH, Zurich
- License: The Unlicense, see LICENSE file.