Certificate expiry: why auto-renewal still fails
Renewal is automatic, so nobody watches it, and the day it fails browsers block the site. Here is why it fails, and how to hear in time.
The problem
Certificates used to be renewed by hand once a year, and someone kept a reminder for it. Automatic renewal changed that. Most certificates now last 90 days or less and renew themselves every two months, and expiry went from a yearly chore to something nobody thinks about.
That is exactly why it still bites. When renewal fails, it fails quietly, weeks before the old certificate ends, and nobody is watching because nothing has gone wrong in years. Then the day comes and browsers do not show a warning a visitor can click past. They show a full page error. Payments stop, the API clients your customers wrote refuse to connect, and mobile apps fail with an error their users cannot read.
The fix is not to stop automating. It is to check the result from the outside: the certificate a visitor is actually served, on every name you use, well before it ends.
Why renewal still fails
It renewed, but the server never loaded it
The most common case. The renewal job wrote a new certificate to disk and exited with success. The web server read its certificate when it started, months ago, and still serves the old one from memory. Everything looks fine on the server: the file is new, the log says renewed. The old certificate ends anyway. Load balancers, mail servers and anything else that reads certificates at start behave the same.
The challenge stopped working
To renew, the certificate authority has to see that you still control the name. It fetches a file over plain HTTP, or looks for a DNS record. A firewall rule that now blocks port 80, a redirect that sends the challenge somewhere else, a name moved to new DNS hosting without the API key the renewal used, or a proxy put in front of the site all break that check. The first renewal after the change fails, and the next ones fail the same way.
The timer is gone
The server was rebuilt from an image that did not include the renewal timer or the cron line. A container was moved to a platform where nothing runs the renewal at all. The job is there but its user lost permission to write the certificate directory. A rebuild is the moment to check, and the one most often forgotten.
Rate limits after repeated failures
A renewal that fails and is retried every few minutes can run into the authority's limits on failed attempts. Then even the fixed setup cannot renew for a while, and the deadline gets closer while you wait.
The name nobody remembered
The main site renews fine. The API host, the old www name that still gets traffic, the status page on its own subdomain, the mail server, or the second of three servers behind the load balancer does not. A certificate is per name and per machine, and the forgotten one is usually the one nobody looks at.
The one bought by hand
Some certificates are still bought for a year or more, for a customer contract or a device that cannot renew itself. Those depend on a person remembering, often a person who has since left.
Nobody else warns you any more
For years, the largest free certificate authority emailed people a few weeks before a certificate ended. It stopped sending those reminders on 4 June 2025. If that email was your safety net, it is gone, and nothing outside your own setup tells you a certificate is about to end.
Checking by hand
Ask the server what it serves, the way a browser does. This prints the start and end of the certificate on example.com:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
The answer looks like this. notAfter is the date that matters:
notBefore=Sep 12 08:14:03 2026 GMT notAfter=Dec 11 08:14:02 2026 GMT
The -servername flag matters. One address often serves many names, and without it the server hands back its default certificate, which may not be the one your visitors get.
Then compare with the file on disk. If the file ends later than what the server serves, the renewal worked and the reload did not:
openssl x509 -noout -enddate -in /etc/ssl/example.com/fullchain.pem
Behind a load balancer, ask each server by its own address with the public name, so a single stale machine cannot hide behind healthy ones:
for ip in 203.0.113.10 203.0.113.11; do echo "$ip: $(echo | openssl s_client -connect "$ip:443" -servername example.com 2>/dev/null | openssl x509 -noout -enddate)" done
And check every name, not only the main site. This small script prints a line for each name whose certificate ends within 14 days, which is 1,209,600 seconds for -checkend. Run it from cron on a machine outside the ones it checks:
#!/bin/sh
# cert-check: warn when a served certificate ends within 14 days.
for host in example.com www.example.com api.example.com mail.example.com; do
if ! echo | openssl s_client -connect "$host:443" -servername "$host" 2>/dev/null \
| openssl x509 -noout -checkend 1209600 > /dev/null; then
echo "$host: the certificate ends within 14 days, or could not be read"
fi
doneWhat works with any tool
- Watch what is served, from outside. The file on disk, the renewal log and the timer all say what should happen. Only a connection from outside says what a visitor gets.
- Warn well before, more than once. Renewal usually runs 30 days before the end, so a certificate with less than 30 days left already missed its renewal. Warnings at 30, 14 and 7 days give you time to fix the cause instead of renewing by hand at night.
- Treat a renewed file that is not served as a failure. If the end date you are served does not move forward after a renewal, the reload is broken, and it will be broken the next time too.
- Test the reload, not only the renewal. Most renewal clients can run a command after a successful renewal. Make it test the configuration and then reload the server, and run a dry renewal after every change to the setup:
nginx -t && systemctl reload nginx
- List every name. Keep the list of hostnames next to the check, and add a name to it the day it gets a certificate.
- Serve the full chain. A missing intermediate certificate passes in a browser that has it cached and fails in an API client that does not, which reads as an outage only some customers see.
The domain name has the same blind spot
A domain registration ends too, and when it does, the name stops resolving or starts pointing at a parking page. Automatic renewal at the registrar fails for a dull reason: the card on file expired, or the account belongs to someone who left. Check the date the registry holds:
whois example.com | grep -i 'expir'
The field is named differently by each registry, so read the line it prints. Keep auto-renewal on, keep a current card on the registrar account, put the date in a shared calendar a month ahead, and make sure the registrar's email reaches a mailbox that is read, not one person's inbox.
How Upglory does it
Every check that completes a secure connection records when the certificate it was served ends. Once a day we read the newest check from each location, take the certificate that ends soonest, and warn 14 days before it ends and again at 7 days, once each. The warning goes to the app and to the monitor's alert channels, and a monitor with no channel is still warned in the app.
Because the date comes from the certificate the server handed out, a renewal that sits on disk without being loaded is still warned about. When the new certificate is served, its new end date replaces the old one and the warnings count again from that date. If a certificate does end, the next check fails with a certificate error, and that is an incident like any other failure.
We watch the domain name as well. Once a day we ask the registry when each monitored domain's registration ends, and warn 30 days before the registration ends and again at 7 days, in the app and through the monitor's alert channels. A renewal that moves the date counts the warnings again from the new date. Some registries do not publish the date; for those the monitor page says the date is not available, so keep that renewal in your own calendar.
The uptime checks page shows what a check records, and the docs cover adding a monitor for each of your hostnames.