"Certificate has expired": check the dates, renew it, and find out why it didn't renew
Fix "certificate has expired" and NET::ERR_CERT_DATE_INVALID: check the dates with openssl, renew with Certbot, reload the server, and fix why renewal or clock failed.
by OhSudo ··
Not sure this is your cause?Find it step by step: Website down →
Part of a learning path:Web servers: nginx and HTTPS in production
In a browser:
Your connection is not privateNET::ERR_CERT_DATE_INVALIDFrom curl or code:
curl: (60) SSL certificate problem: certificate has expiredTLS certificates have a validity period, and today falls outside it. Either the certificate really has expired, or the machine asking has the wrong date, or it’s an intermediate certificate that’s expired rather than yours. It takes one command to tell which, and it’s worth fixing why it didn’t renew too, or you’ll be back here in 90 days.
- Clienttoday's date, by its own clockchecks
- CertificatenotBefore … notAfterout of range(fails)
- Outside that periodcertificate has expired(breaks here)
TL;DR
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -subjectdate -usudo certbot renewsudo systemctl reload nginx1. Check the dates
Ask the server what it’s serving:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -subject -issuernotBefore=Aug 1 09:12:45 2026 GMTnotAfter=Oct 30 09:12:44 2026 GMTsubject=CN=example.comissuer=C=US, O=Let's Encrypt, CN=E7Now compare with the clock on the machine showing the error:
date -u| What you find | Cause | Go to |
|---|---|---|
notAfter is in the past |
It really expired | Section 2 |
| The date is right, and the file on disk is newer | The server wasn’t reloaded | Section 3 |
| The date on your machine is wrong | The clock | Section 4 |
| Only some clients fail | An old client, or chain | Section 5 |
-servername matters: a server with several sites gives you the wrong certificate without it. To read a long date as a timestamp, or the reverse, use the Timestamp Converter.
2. Renew it
With Let’s Encrypt:
sudo certbot certificatessudo certbot renewThe first shows every certificate and its expiry date. If renewal fails, the message says why: see Certbot failed to authenticate. Port 80 closed by a recent firewall change is a common one.
A certificate from a commercial authority has to be renewed with them, and the new files then installed by you.
3. Reload the server
This one catches people. A server reads its certificate at startup, and it keeps serving the old one until it’s told otherwise. So the file is new, and visitors still get an expired certificate:
sudo openssl x509 -noout -enddate -in /etc/letsencrypt/live/example.com/fullchain.pemsudo nginx -t && sudo systemctl reload nginxMake it automatic, so that it doesn’t happen again. Certbot runs anything in this folder after a successful renewal:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<'EOF'#!/bin/shsystemctl reload nginxEOFsudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shOther programs hold a copy too, such as HAProxy, mail servers, and databases. And check which file your config really uses:
sudo nginx -T 2>/dev/null | grep -E 'server_name|ssl_certificate '4. A wrong clock
If date shows the wrong year, then every certificate looks invalid, and yours isn’t the problem. It’s common on virtual machines, single-board computers with no battery, and containers on such hosts:
timedatectlsudo timedatectl set-ntp trueOn Windows, use w32tm /resync. A machine with a wrong clock often fails on apt as well, for the same reason.
5. Why didn’t it renew?
Find this out, or it comes back:
systemctl list-timers | grep -i certbotsudo journalctl -u certbot -n 30 --no-pagersudo grep -i error /var/log/letsencrypt/letsencrypt.log | tail -n 5sudo certbot renew --dry-runNo timer means that nothing ever tried, as can happen with installs by hand, or in a container. A package normally sets this up, and otherwise you can add a cron job. See cron job not running if it’s there and doing nothing.
An expired intermediate. Your own certificate is fine, and one above it in the chain has expired, or an old client doesn’t trust the newer one. Use fullchain.pem, not cert.pem, in your config. An old system with outdated root certificates needs an update: sudo apt install --reinstall ca-certificates. See unable to get local issuer certificate.
6. Set up an alert
Certificates fail at the worst times, and it’s easy to notice in advance. This exits with an error if there are under 14 days left, which suits a monitoring check:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -checkend 1209600Certificates are also getting shorter lives, with public ones set to drop well below a year in the coming years. So renewal by hand is less and less practical, and automating it now is time well spent. The Nginx Config Generator includes the Certbot steps.
Warning
Don’t get around this with curl -k, verify=False or NODE_TLS_REJECT_UNAUTHORIZED=0, even “just for now”. That turns off the check that proves who you’re talking to, and such settings tend to stay in code for years. In a browser, don’t click through on a site that handles logins or payments.
Shell, we have a problem!
Similar, but different: unable to get local issuer certificate is a missing part of the chain. ERR_CERT_COMMON_NAME_INVALID means the certificate is for another name. self-signed certificate means it wasn’t issued by a trusted authority. wrong version number usually means you’re speaking TLS to a port that’s plain HTTP. Certbot failed to authenticate is about getting one.
Found this useful? Share it.
Found a mistake? Let us know at contact@ohsudo.com.