Check when a certificate expires
An expired certificate takes your site down as surely as a crash, and browsers make sure every visitor notices. Checking takes one command. Making sure renewal actually works takes two more.
Published . Works with OpenSSL 3.x on any system; the certbot parts assume certbot 4.0 or later from your distribution or the snap.
- Why check from outside
- Check a live site with openssl
- Use it in a script
- List certbot's certificates
- Test renewal with --dry-run
- Find the renewal timer
- Let's Encrypt lifetimes are getting shorter
Why check from outside
The certificate file on disk and the certificate your server actually sends aren't always the same. Certbot can renew the file while nginx keeps serving the old one from memory, because nobody reloaded it. A load balancer or CDN in front may serve a different certificate entirely. So check what a visitor sees first, then look at the files.
Check a live site with openssl
From any machine with OpenSSL:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -enddate -subject -issuer
The output looks like this:
notAfter=Jan 28 09:14:02 2027 GMT
subject=CN=example.com
issuer=C=US, O=Let's Encrypt, CN=R11
-connectis the host and port to connect to.-servernamesets SNI, the name the server uses to pick a certificate. With several sites on one address, you need it to get the right one. OpenSSL fills it in from the-connecthost when that's a DNS name, but being explicit costs nothing.</dev/nullcloses the connection straight away instead of waiting for you to type.-enddateprints the expiry date (notAfter).-datesprints both start and end.
For mail and other services that switch to TLS after connecting, use -starttls, for example openssl s_client -connect mail.example.com:587 -starttls smtp.
Use it in a script
-checkend takes a number of seconds and exits non-zero if the certificate expires within that time. That makes a simple check for cron or a monitoring script:
# fails if the certificate expires within 14 days (14 * 86400 seconds)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -checkend 1209600 \
|| echo "example.com certificate expires within 14 days"
The same works on a file on the server: openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -enddate.
List certbot's certificates
If certbot manages your certificates, it can tell you about all of them at once:
sudo certbot certificates
For each certificate it prints the name, the domains it covers, the expiry date with the days left (for example VALID: 30 days), and the paths to the certificate and key. If a domain you serve isn't on any list, it isn't being renewed by certbot.
Test renewal with --dry-run
Knowing the date isn't enough. You need to know the next renewal will succeed. A dry run goes through the whole renewal against Let's Encrypt's staging environment without saving anything:
sudo certbot renew --dry-run
If it fails, the error usually points to the cause: port 80 blocked by a firewall, a DNS record that moved, or a web server config that no longer serves the challenge path. Fix it now, while the current certificate is still valid.
Find the renewal timer
Most certbot installs set up automatic renewal: a systemd timer or a cron job that runs certbot renew regularly. Check that yours exists:
systemctl list-timers | grep -i certbot
grep -rs certbot /etc/crontab /etc/cron.d/
The timer's name depends on how certbot was installed, so search rather than guess. certbot renew only renews certificates that are due. Since certbot 4.0, a certificate is due when less than a third of its lifetime is left; before that, it was a fixed 30 days.
A renewed certificate only takes effect once the web server loads it. A deploy hook runs a command once for each certificate that was actually renewed, which is the right place for a reload: --deploy-hook "systemctl reload nginx". Whatever your setup, check again from outside after the next renewal.
Renewal that works today can break silently. A firewall change, a DNS move or a new proxy can stop the challenge from reaching the server. Check expiry from outside on a schedule, so you hear about it weeks ahead, not from your visitors.
Let's Encrypt lifetimes are getting shorter
Let's Encrypt certificates have been valid for 90 days. Following the CA/Browser Forum's rules, Let's Encrypt has announced it will shorten that:
- February 10, 2027: the default (classic) profile switches to 64-day certificates. Authorization reuse drops from 30 days to 10.
- February 16, 2028: the default switches to 45-day certificates, and authorization reuse drops to 7 hours.
The opt-in tlsserver profile has issued 45-day certificates since May 13, 2026. Let's Encrypt's advice: let your client use ACME Renewal Information (ARI) if it supports it, otherwise renew about two thirds of the way through a certificate's lifetime, and don't hardcode a fixed interval. Renewing every 60 days won't be enough. Search your cron jobs and scripts for fixed day counts like 60, 80 or 83.
Certbot's default of renewing when a third of the lifetime is left already follows that rule. Hand-written renewal scripts, certificates copied to other machines by hand, and alerts set to "warn 30 days before" are the things to review. For the rest of what to check on a server, see the Linux server security checklist.
Certificate expiry warnings are coming to Stenvakt. Today its Review gives you a read-only security and health check of every server, and Watch (Pro) adds ongoing monitoring and alerts. It's a Mac app and isn't out yet: get early access.
Sources
- OpenSSL docs: openssl-s_client
- OpenSSL docs: openssl-x509
- Certbot user guide: managing certificates, renewal and --dry-run
- Let's Encrypt: Decreasing certificate lifetimes to 45 days
- Let's Encrypt: 64-day certificate lifetimes coming February 2027