Get started
← Blog

Nobody emails you before your certificate expires any more

Two things changed recently, and together they quietly moved certificate expiry from “solved problem” back onto the list of things that will take your site down on a Sunday.

First, the safety net is gone. Let’s Encrypt ended expiration notification emails in June 2025 — reasonably, since they were retaining millions of email addresses to send warnings that automation should make unnecessary. But plenty of teams did not realise those emails were the only thing standing between them and an outage.

Second, the window is shrinking. In April 2025 the CA/Browser Forum adopted Ballot SC-081v3, which walks the maximum lifetime of a public TLS certificate from 398 days down to 47 by March 2029: 200 days from March 2026 (in effect as you read this), 100 days from March 2027, 47 days from March 2029. Domain validation data reuse drops to 10 days along the way. Renewal stops being an annual chore you can survive doing by hand and becomes something that happens roughly every six weeks, forever.

More renewals means more chances for one of them to fail silently. Which brings us to the part people get wrong.

Automated renewal is not monitoring

A cron job running certbot renew tells you a process ran. It does not tell you that visitors are being served a valid certificate. Those are different claims, and the gap between them is where the outages live:

  • The reload never happened. nginx and Apache read the certificate into memory at startup. A renewal without a successful reload leaves the old certificate being served until it expires — from a file on disk that is perfectly up to date.
  • The deploy hook failed but the exit code didn’t. Renewal hooks are shell scripts. A typo’d service name, a permission change, a full disk: many of these end with a non-zero step inside a script that still exits 0.
  • One node out of several. The certificate renewed on web-1, and the load balancer sends a third of your traffic to web-2, whose renewal has been broken since a package upgrade. Check from a browser and you’ll be fine two times out of three.
  • The edge has its own copy. CDN, load balancer, API gateway, mail server, an IoT device with a certificate pasted in by hand in 2024. The origin is renewed and healthy; the thing customers actually connect to is not.
  • The DNS-01 credentials expired. API token rotated, zone moved, provider plugin deprecated. The renewal fails cleanly, logs to a file nobody reads, and — since June 2025 — nobody emails you about it.
  • The container image has the certificate baked in. Renewal happens outside the image; the running pod keeps serving what it was built with.

Every one of these leaves your monitoring green if your monitoring is “did the renewal job run”. They also share a shape with the general case of scheduled work: a job that succeeds while its outcome is false. We wrote about the other half of that problem in monitoring scheduled jobs — heartbeats watch the work, and something else has to watch the result.

Monitor the certificate on the wire

The only claim worth alerting on is the one your visitors experience: at this hostname, right now, the certificate presented is valid and not about to expire. That means checking from outside, over a real TLS handshake, and checking a few things people forget:

  • Every hostname, not every certificate. example.com, www.example.com, api.example.com, the status page, the staging host someone will eventually demo from. SNI means each name can get a different certificate, and one of them will be the one nobody owns.
  • The full chain, not just the leaf. A server that sends the leaf but not the intermediate works in your browser (which caches intermediates) and fails for API clients, mobile apps and anything running a fresh container. This breaks the moment your CA rotates an intermediate.
  • Every endpoint behind the name. Multiple IPs, multiple edges, blue-green deploys: a check that resolves once and connects once can easily talk to the healthy node every time.
  • From more than one place. A vantage point inside your own network can be routed to the origin, see a different certificate than the internet does, or be unable to reach you at all in exactly the incident you needed to detect.

By hand, the handshake looks like this:

echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -subject -dates -issuer

Wrap it in a script, subtract dates, alert under a threshold, run from cron — and you have a working certificate monitor with one flaw: nothing watches the monitor. That cron job lives on a host that can reboot, get reprovisioned, lose its crontab or simply run out of disk, and its silence looks exactly like good news. If you go this route, at minimum put a heartbeat on the script itself, so silence becomes an alert instead of reassurance.

Your thresholds are about to be wrong

Here is the part that catches people who already monitor expiry: the classic “warn 30 days before” was tuned for 398-day certificates, where 30 days is 7% of the lifetime and comfortably several renewal attempts.

On a 47-day certificate, 30 days is nearly two thirds of its life. A threshold like that fires constantly and gets muted within a week — the same death spiral as a grace period set too tight on a cron job.

Thresholds should be relative to the renewal cycle, not to the calendar:

  • Alert when the renewal window has passed, not when expiry is close. ACME clients try to renew at roughly a third of the lifetime remaining. If a certificate has less than that left, renewal has already failed once — that is the actionable moment.
  • Two levels, not five. One warning early enough for a human to fix it in working hours, one urgent alert when expiry is imminent. Anything in between is noise.
  • Watch the renewal job too. Then you learn about the failure at the first missed attempt, not at the last safe moment.

For a 47-day certificate that works out to roughly: warn at 14 days left, page at 5. For today’s 200-day certificates, 30 and 10 are still sensible. Rather than remembering to retune this every March, tie it to the certificate’s own lifetime.

How we do it

CronAlive treats the certificate as part of the HTTP check rather than a separate product: point a check at a URL, and along with status code, response time and keyword matching you get the expiry date of the certificate actually served, with warnings at 30, 14 and 7 days. Checks run from several regions and confirm an outage from a second region before alerting, so one flaky vantage point doesn’t page you. Alerts go to Telegram, email, Slack, Discord, Mattermost, PagerDuty or a signed webhook.

Pair it with a heartbeat on the renewal job itself and you cover both halves: the heartbeat tells you certbot stopped running, the HTTP check tells you it ran and achieved nothing. The free plan covers ten checks without a card, which is enough for a small fleet of hostnames.

Alternatives worth knowing

Let’s Encrypt’s own suggestion when they retired their emails was Red Sift Certificates Lite, free for up to 250 certificates and focused specifically on expiry notification — if that is the only thing you need, it is a good answer. Uptime Kuma covers certificate expiry among its checks and is excellent if you are happy self-hosting your monitoring (accepting that it shares fate with whatever else runs on that box). UptimeRobot and StatusCake both include TLS expiry on their uptime checks. And for a single domain, the openssl snippet above plus a heartbeat is genuinely fine — the pattern matters more than the tool.

What is no longer fine is relying on an email that stopped being sent last year, or on a renewal job whose success proves less than you think.


CronAlive monitors cron jobs, scheduled tasks and HTTP endpoints: if a job stops pinging on schedule, you get an alert. Start with the guide or see the plans.