From 3b797bb4e562388a2f782ad0181ff6d197d367e7 Mon Sep 17 00:00:00 2001 From: Walusimbi Silver <107974377+swalusimbi@users.noreply.github.com> Date: Thu, 3 Sep 2026 14:35:28 +0300 Subject: [PATCH] Add optional healthcheck heartbeat The check is silent by design on a healthy host, so an absent alert is indistinguishable from a cron that stopped running. Ping HEALTHCHECK_URL on a clean run and the /fail endpoint when the check alerts or cannot complete. Empty by default, which disables the pings. --- README.md | 18 ++++++++++++++++++ scripts/check-cert-expiry.sh | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/README.md b/README.md index 76e91cd..ea3db5f 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ ALERT_ON_NO_CERTS=true CHECK_SERVED=true SERVED_ADDR=127.0.0.1:443 SERVED_TIMEOUT=10 +HEALTHCHECK_URL= ``` ### Served certificate check @@ -102,3 +103,20 @@ alerting path. For jobs where silence is also a failure, pair ntfy with a dead man's switch such as Healthchecks. ntfy tells you what the script found; Healthchecks tells you when the script did not run at all. + +Set `HEALTHCHECK_URL` to enable it. The script pings that URL after a clean run +and `$HEALTHCHECK_URL/fail` when it alerts or cannot complete the check, so a +dead cron, a bad chmod or a host that never came back up stops looking like a +healthy fleet: + +```cron +15 8 * * * HEALTHCHECK_URL=https://hc-ping.com/ /opt/scripts/check-cert-expiry.sh >>/var/log/cert-expiry-check.log 2>&1 +``` + +Leaving `HEALTHCHECK_URL` empty disables the pings entirely. + +This matters more than it looks for a certificate check specifically. The script +is designed to stay silent on a healthy host: certbot renews at 30 days and the +threshold is 25, so a correctly working fleet produces no notifications, ever. +Without a heartbeat, "no alert" and "the check has not run since March" are the +same observation. diff --git a/scripts/check-cert-expiry.sh b/scripts/check-cert-expiry.sh index a0ea392..b7b0388 100644 --- a/scripts/check-cert-expiry.sh +++ b/scripts/check-cert-expiry.sh @@ -13,6 +13,7 @@ ALERT_ON_NO_CERTS="${ALERT_ON_NO_CERTS:-true}" CHECK_SERVED="${CHECK_SERVED:-true}" SERVED_ADDR="${SERVED_ADDR:-127.0.0.1:443}" SERVED_TIMEOUT="${SERVED_TIMEOUT:-10}" +HEALTHCHECK_URL="${HEALTHCHECK_URL:-}" host="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "unknown-host")" now_epoch="$(date +%s)" @@ -59,9 +60,22 @@ served_expiry_epoch() { date -d "${enddate#notAfter=}" +%s 2>/dev/null } +# Dead man's switch. ntfy reports what the check found; this reports that the +# check ran at all. Without it, a dead cron, a failed boot or a bad chmod all +# look exactly like a healthy fleet. +ping_healthcheck() { + local suffix="${1:-}" + + [[ -z "${HEALTHCHECK_URL}" ]] && return 0 + + curl -fsS -m 10 -o /dev/null "${HEALTHCHECK_URL}${suffix}" \ + || echo "Failed to ping healthcheck at ${HEALTHCHECK_URL}${suffix}" >&2 +} + fail() { local message="$1" send_alert "${message}" || echo "Failed to send ntfy alert to ${NTFY_URL}" >&2 + ping_healthcheck "/fail" echo "${message}" >&2 exit 1 } @@ -127,6 +141,7 @@ if (( cert_count == 0 )); then fi echo "No certificates found under ${CERT_DIR}; no alert sent." + ping_healthcheck exit 0 fi @@ -136,8 +151,10 @@ if (( ${#warnings[@]} > 0 )); then $(printf '%s\n' "${warnings[@]}")" send_alert "${message}" || echo "Failed to send ntfy alert to ${NTFY_URL}" >&2 + ping_healthcheck "/fail" echo "${message}" >&2 exit 1 fi echo "All ${cert_count} certificate(s) are valid for more than ${EXPIRY_DAYS} day(s)." +ping_healthcheck