Files
silver-server-ops/scripts/check-cert-expiry.sh
Walusimbi Silver c16d6dcf75 Raise expiry threshold above certbot's renewal window
Certbot renews at 30 days remaining, so a healthy certificate never falls
below it and the old 14 day threshold could only fire after renewal had
been broken for 16 straight days, leaving 14 days to react. 25 days fires
about five days after the first failed renewal.
2026-09-03 14:32:02 +03:00

104 lines
3.0 KiB
Bash

#!/usr/bin/env bash
set -uo pipefail
CERT_DIR="${CERT_DIR:-/etc/letsencrypt/live}"
EXPIRY_DAYS="${EXPIRY_DAYS:-25}"
NTFY_URL="${NTFY_URL:-http://127.0.0.1:2586/certbot}"
NTFY_TITLE="${NTFY_TITLE:-SSL certificate warning}"
NTFY_PRIORITY="${NTFY_PRIORITY:-high}"
NTFY_TAGS="${NTFY_TAGS:-warning,lock}"
NTFY_TOKEN="${NTFY_TOKEN:-}"
ALERT_ON_NO_CERTS="${ALERT_ON_NO_CERTS:-true}"
host="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "unknown-host")"
now_epoch="$(date +%s)"
threshold_seconds=$((EXPIRY_DAYS * 86400))
send_alert() {
local message="$1"
local curl_args=(
-fsS
-H "Title: ${NTFY_TITLE}"
-H "Priority: ${NTFY_PRIORITY}"
-H "Tags: ${NTFY_TAGS}"
)
if [[ -n "${NTFY_TOKEN}" ]]; then
curl_args+=(-H "Authorization: Bearer ${NTFY_TOKEN}")
fi
curl "${curl_args[@]}" --data-binary "${message}" "${NTFY_URL}" >/dev/null
}
fail() {
local message="$1"
send_alert "${message}" || echo "Failed to send ntfy alert to ${NTFY_URL}" >&2
echo "${message}" >&2
exit 1
}
if ! command -v openssl >/dev/null 2>&1; then
fail "SSL certificate check failed on ${host}: openssl is not installed."
fi
if ! command -v curl >/dev/null 2>&1; then
echo "SSL certificate check failed on ${host}: curl is not installed." >&2
exit 1
fi
if [[ ! -d "${CERT_DIR}" ]]; then
fail "SSL certificate check failed on ${host}: certificate directory ${CERT_DIR} does not exist."
fi
warnings=()
cert_count=0
while IFS= read -r -d '' cert_path; do
cert_count=$((cert_count + 1))
cert_name="$(basename "$(dirname "${cert_path}")")"
enddate_line="$(openssl x509 -in "${cert_path}" -noout -enddate 2>/dev/null)"
if [[ -z "${enddate_line}" ]]; then
warnings+=("${cert_name}: could not read expiry date from ${cert_path}")
continue
fi
not_after="${enddate_line#notAfter=}"
expiry_epoch="$(date -d "${not_after}" +%s 2>/dev/null)"
if [[ -z "${expiry_epoch}" ]]; then
warnings+=("${cert_name}: could not parse expiry date '${not_after}'")
continue
fi
seconds_left=$((expiry_epoch - now_epoch))
days_left=$((seconds_left / 86400))
if (( seconds_left < 0 )); then
warnings+=("${cert_name}: EXPIRED on ${not_after}")
elif ! openssl x509 -checkend "${threshold_seconds}" -noout -in "${cert_path}" >/dev/null 2>&1; then
warnings+=("${cert_name}: expires in ${days_left} day(s), on ${not_after}")
fi
done < <(find "${CERT_DIR}" -mindepth 2 -maxdepth 2 \( -type f -o -type l \) -name cert.pem -print0)
if (( cert_count == 0 )); then
if [[ "${ALERT_ON_NO_CERTS}" == "true" ]]; then
fail "SSL certificate check found no cert.pem files under ${CERT_DIR} on ${host}."
fi
echo "No certificates found under ${CERT_DIR}; no alert sent."
exit 0
fi
if (( ${#warnings[@]} > 0 )); then
message="SSL certificate expiry warning on ${host}. Threshold: ${EXPIRY_DAYS} day(s).
$(printf '%s\n' "${warnings[@]}")"
send_alert "${message}" || echo "Failed to send ntfy alert to ${NTFY_URL}" >&2
echo "${message}" >&2
exit 1
fi
echo "All ${cert_count} certificate(s) are valid for more than ${EXPIRY_DAYS} day(s)."