SSL renewal is three operations and only one is automated
Renewal is not one thing. It is three, and they can fail independently.
Obtain a new certificate from the authority, which means passing a challenge that proves you still control the domain. Install it, which means writing files where the server expects them. Reload, which means telling the running process to read them, because a web server holds its certificate in memory and will happily keep serving an expired one from before the restart it never got.
Automation, in almost every setup I have inherited, covers the first step properly, the second usually, and the third only if somebody wrote the hook. That is why the classic expiry incident reads as impossible: the renewal log says success, the file on disk is new, and the site is serving a certificate that expired last night.
The check that answers the real question
Do not look at the file. Look at what the server hands to a stranger over the network.
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
| openssl x509 -noout -subject -dates
That is the only version of the question that matters, because it is the one your visitors ask. A file on disk with a fresh date proves the obtain step worked and nothing else.
Two details in that command earn their place. -servername sends SNI, without which a server hosting several sites gives you the certificate of whichever one is first, and you will spend an hour confused. And connecting by hostname rather than by IP means you are testing the path a visitor takes, including whatever sits in front.
Ask about www separately. A certificate covering the apex and not the subdomain, or the reverse, is one of the most common renewal regressions, and it is invisible if you only ever test the address you happen to type.
What actually breaks, in order of how often
The challenge path gets caught by a redirect. The HTTP-01 challenge requires that /.well-known/acme-challenge/ be reachable over plain HTTP. Then somebody adds a rule that sends all HTTP traffic to HTTPS, or a "coming soon" plugin that intercepts every request, or a firewall rule that blocks unfamiliar user agents. The rule is correct in every other respect. Renewal starts failing that day and nobody hears about it for two months.
Nothing reloads the server. The renewal ran from a cron entry somebody wrote by hand years ago, with no deploy hook. Or the hook exists and calls a service name that changed. Or the reload is there and fails silently because the config has an unrelated error that only surfaces on reload, so the process keeps running with its old memory image and its old certificate.
The timer stopped. A server migration, a container rebuild, an OS upgrade that replaced the unit file. The renewal was never disabled by a decision; it was simply not carried across. Check that it is scheduled and, more usefully, check when it last ran.
The DNS moved. Hosting control panels that issue certificates for you, cPanel's AutoSSL among them, silently skip a domain whose DNS no longer points at that server. A client moved their mail, changed nameservers, and left the A record pointing somewhere else for a week. The panel does the sane thing and stops issuing. Nothing tells you.
Credentials expired somewhere else. DNS-01 validation needs an API token for the DNS provider, and API tokens have their own expiry, their own rotation, and their own permission model that somebody tightened last quarter.
One node out of several. Two web servers behind a load balancer, renewal configured on the one that was set up first. Half the visitors get an expired certificate, which presents as an intermittent fault and is nothing of the kind.
The alert threshold everyone gets wrong
The usual advice is to alert seven days before expiry. That is far too late, and the reason is arithmetic rather than caution.
Let's Encrypt issues certificates valid for ninety days by default, and recommends renewing every sixty days, which leaves a thirty-day margin (Let's Encrypt FAQ, consulted 1 August 2026). Automation follows that recommendation. So on a healthy site, the remaining lifetime should never go below thirty days: it climbs back to ninety, drops to thirty, climbs back.
Which means a certificate showing twenty-five days left is not a certificate that is fine for another three weeks. It is a certificate whose renewal has already been failing, once a day, for several days. An alert at seven days finds you three weeks into a failure with a week to fix it, usually on a Friday.
Set the threshold just below the renewal margin. For the ninety-day, thirty-day-margin case, alert at around twenty days. You get three weeks of runway and, more to the point, you get told at the moment something changed rather than at the moment it becomes urgent. For the six-day certificates Let's Encrypt now offers, the same reasoning gives a threshold of hours, which is a good argument for not adopting them until your alerting is genuinely automatic.
Why this is an agency problem specifically
One site with a ninety-day certificate is four renewals a year. Forty client sites is a hundred and sixty renewal events a year, spread across whichever dates each site was first provisioned, on five different hosting stacks, three of which you do not control.
Nobody can hold that in their head, and the ones that bite are never the sites you think about. They are the small ones: the brochure site for a client who pays a little every month, the microsite from a campaign two years ago, the subdomain used for a form. Nobody logs into them. Nobody notices the browser warning until a customer sends a screenshot.
The mitigation is boring and it works. Read the served certificate for every hostname you are responsible for, including the ones nobody visits, on a schedule, from outside. Alert on the margin rather than on the deadline. And treat an expiry that actually reaches production as a process failure to be written up, not as bad luck, because it is the single most predictable outage in this business: the date was printed in the certificate from the day it was issued.
It is also the outage that does the most damage per minute. A site that returns a 502 looks broken. A site with an expired certificate shows a full-page interstitial that says, in the browser's own voice, that the connection is not private and someone may be trying to steal information. What that screen is actually telling you, and why the useful part is the error code hidden two clicks in, comes next in this series. The general point that a status code is a claim about a request rather than about a working site is in what a 200 does not prove, and certificate expiry is the cleanest example: on the day it happens, the server is healthy, the application is fine, and nobody can get in. If you want to see what an outside check reports for one of your hostnames, try it here.