Skip to content

Add --warn-days alerting to ssl-check, run it daily - #231

Open
jasperf wants to merge 3 commits into
mainfrom
feature/ssl-check-warn-days
Open

Add --warn-days alerting to ssl-check, run it daily#231
jasperf wants to merge 3 commits into
mainfrom
feature/ssl-check-warn-days

Conversation

@jasperf

@jasperf jasperf commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Let's Encrypt stopped sending expiry reminder emails in June 2025, so a failed auto-renewal on a host we don't manage now goes unnoticed until visitors hit a browser warning. ssl-check could report a certificate's dates but not alert on them.

Change

  • ssl-check --warn-days N exits 2 when any certificate expires within N days, has already expired, or doesn't match the hostname it was served for. The hostname check is deliberate: on Sep 11, 2026 a client site on shared hosting was serving the host's own wildcard certificate instead of its Let's Encrypt one — valid for months, so an expiry-only check would have passed it.
    • Several domains per run; exit 1 (certificate couldn't be retrieved) takes precedence over 2.
    • Expiry uses openssl x509 -checkend rather than parsing notAfter, so the alert doesn't depend on macOS/GNU date differences.
    • The hostname verdict is read from -checkhost's output; an openssl without -checkhost (macOS's LibreSSL /usr/bin/openssl) skips the check instead of failing it.
    • Without --warn-days, exit codes are unchanged; the only output change is a printed hostname mismatch.
  • .github/workflows/ssl-expiry.yml runs it daily (06:17 UTC, plus manual dispatch) against the SSL_CHECK_DOMAINS repository secret, threshold from the SSL_CHECK_WARN_DAYS variable (default 21). The failed scheduled run is the alert. The list is a secret because this repo and its logs are public: each domain and its parent domains are masked before anything prints, since a wildcard certificate's subject names the parent.
  • Catalog regenerated (entry count unchanged — no new command), scripts/README.md updated, CHANGELOG 5.24.0.

Setup after merge: add the SSL_CHECK_DOMAINS secret (domains separated by spaces, commas or newlines). Until it exists, the scheduled run fails with an explicit "secret is not set" error.

Testing

  • bash -n under macOS /bin/bash 3.2, shellcheck clean
  • Single domain: output identical to before, exit 0
  • --warn-days 21 against badssl.com (valid), expired.badssl.com and wrong.host.badssl.com → ALERT for the expired one, does NOT match for the wrong host, exit 2
  • Expired + unreachable domain → exit 1; two real client domains (85 and 88 days left) → exit 0; --warn-days 1000 on one of them → exit 2
  • LibreSSL /usr/bin/openssl against wrong.host.badssl.com → no verdict, exit 0 (check skipped, not failed)
  • Bad usage (no domain, non-numeric --warn-days, unknown option) → exit 1
  • Workflow YAML parses; masking loop dry-run under bash -eo pipefail masks each domain plus its parents
  • go generate ./..., go vet ./..., go test ./... and go test . all pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant