SSL Certificate Expired: What to Do and How to Prevent It
Your SSL certificate expired? How to confirm it with openssl, renew and install it correctly, fix the chain, reload the server, and automate renewals for good.
Published: · 6 min read
An expired certificate takes a site offline as effectively as a crashed server. Browsers show a full-page warning (NET::ERR_CERT_DATE_INVALID in Chrome, SEC_ERROR_EXPIRED_CERTIFICATE in Firefox), and if the site uses HSTS there is not even a "proceed anyway" link. API clients, mobile apps, webhooks and payment callbacks simply fail. This guide covers what to do in the first half hour and how to make sure it does not happen again.
Step 1 — Confirm what is actually wrong
Not every date error is an expired certificate on your server. Check from a machine whose clock you trust:
$ openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
subject=CN = example.com
issuer=C = XX, O = Example CA, CN = Example CA R3
notBefore=Jun 20 08:00:00 2026 GMT
notAfter=Sep 18 08:00:00 2026 GMT
notAfter in the past confirms it. Things that look similar but are not:
- Wrong clock on the client. A laptop with a dead battery that thinks it is 2019 rejects every certificate. If only one visitor reports the error, ask about the clock first.
- An expired intermediate in the chain the server sends, while the leaf is fine.
- Only one of several servers has the old certificate. Behind a load balancer or a CDN, check each node, and check the origin separately from the edge.
- Another service on the same name. Port 443 was renewed, but mail (
:465,:993,:587with STARTTLS) still has the old file:
$ openssl s_client -connect mail.example.com:587 -starttls smtp </dev/null 2>/dev/null \
| openssl x509 -noout -enddate
The -servername option matters: without SNI many servers return a default certificate that is not the one browsers see.
Step 2 — Renew, depending on how you got the certificate
ACME (Let's Encrypt and similar)
The certificate should have renewed itself; find out why it did not.
$ sudo certbot certificates # what certbot knows and when each expires
$ sudo certbot renew --dry-run # test the renewal path
$ sudo certbot renew # renew everything that is due
$ systemctl list-timers | grep -i certbot
Usual causes: the timer or cron job is missing after a server migration; port 80 is closed or redirected in a way that breaks the HTTP-01 challenge; the DNS API token used for DNS-01 was rotated; an AAAA record points at a machine that does not answer the challenge; a restrictive CAA record does not list the CA; or the domain now points at a different server entirely.
Certificate from a commercial CA
- Generate a new key and CSR. Do not reuse the old key out of habit.
$ openssl req -new -newkey rsa:2048 -nodes \
-keyout example.com.key -out example.com.csr \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com"
- Submit the CSR, complete domain validation (email, DNS record or HTTP file), download the certificate and the intermediate chain.
- List every hostname you need in the SAN field.
example.comdoes not coverwww.example.com, and a wildcard*.example.comcovers neither the bare domain nora.b.example.com.
Managed by a host, CDN or load balancer
Look in the provider's panel. Managed certificates usually fail to renew for one reason: the domain no longer validates. The DNS record that pointed at the provider was changed, a required CNAME for validation was deleted, or a CAA record blocks the provider's CA.
Step 3 — Install it with the full chain
Servers must send the leaf certificate followed by the intermediate(s). The root is not sent. With certbot, that is fullchain.pem:
# nginx
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
A missing intermediate is treacherous because desktop browsers often paper over it by fetching or caching intermediates, while curl, Android apps and other servers fail. Test with a strict client:
$ curl -sSI https://example.com >/dev/null && echo chain ok
Check that certificate and key belong together:
$ openssl x509 -noout -pubkey -in fullchain.pem | openssl sha256
$ openssl pkey -pubout -in privkey.pem | openssl sha256
The two hashes must be identical.
Step 4 — Reload the service
The single most common reason for "I renewed and it is still expired": the web server still has the old certificate in memory.
$ sudo nginx -t && sudo systemctl reload nginx
$ sudo apachectl configtest && sudo systemctl reload apache2
For ACME clients, make the reload part of the renewal, for example certbot renew --deploy-hook "systemctl reload nginx". Do the same for Postfix, Dovecot, HAProxy and anything else that reads the file.
Step 5 — Verify from outside
Repeat the openssl s_client command from Step 1 and look at the new notAfter. Then check:
- both
example.comandwww.example.com, and any other names in use - IPv4 and IPv6 separately (
openssl s_client -4/-6) if you publish both - every node behind the load balancer
- mail and other TLS ports
Browsers may keep an open connection for a while; test in a fresh private window before concluding that it did not work.
Why this keeps happening: lifetimes are getting shorter
Publicly trusted certificates have been limited to 398 days since September 2020, and Let's Encrypt has issued 90-day certificates from the start. In April 2025 the CA/Browser Forum adopted a schedule that reduces the maximum further: 200 days from 15 March 2026, 100 days from 15 March 2027, and 47 days from 15 March 2029. The period during which a completed domain validation may be reused shrinks as well.
The practical meaning: a calendar reminder and a manual renewal once a year is a process with an end date. With 47-day certificates, nobody renews by hand. Note also that Let's Encrypt stopped sending expiry warning emails in 2025, so that safety net is gone too.
Prevention checklist
- Automate issuance and renewal with ACME wherever the platform allows it. Many commercial CAs support ACME as well.
- Renew early. ACME clients renew by default with about a third of the lifetime remaining, which leaves weeks to notice a failure.
- Monitor from outside, independently of the renewal mechanism. The check must look at the certificate the server presents, not at the file on disk. Alert at 21, 14 and 7 days.
- Inventory every endpoint: subdomains, mail servers, VPN gateways, internal tools, the origin behind the CDN. The forgotten one is the one that expires.
- Test the deploy hook. A renewal without a reload is an outage that is merely postponed.
- Keep validation working: port 80 reachable for HTTP-01, DNS API credentials valid for DNS-01, CAA records listing the CAs you use.
- Send alerts to a team address, not to one person's mailbox.
Common mistakes
- Renewing the certificate and not reloading the server.
- Installing the leaf certificate without the intermediate.
- Forgetting
www, or the bare domain, in the SAN list. - Renewing the edge (CDN) certificate while the origin certificate quietly expires; depending on the CDN's mode this produces 5xx errors from the edge rather than a browser warning.
- Telling users to click through the warning. It teaches exactly the habit attackers rely on.
- Turning off certificate verification in an API client "temporarily".
- Assuming a certificate problem when the real cause is a DNS change that sent users to a different server. If the certificate presented belongs to somebody else, check where the name points first.
Check it with OrbitProbe
The OrbitProbe SSL checker connects to your hostname and reports the certificate that is actually presented: issuer, validity dates and days remaining, the hostnames it covers, the negotiated TLS protocol, whether the chain is trusted, and HSTS. Run it after every renewal, once for the bare domain and once for www. If mail is part of the same incident, the DNS checklist for email going to spam covers the TLS and DNS side of mail delivery.