What is an SSL certificate, and what does "expired" mean?
An SSL certificate is a small signed file that proves a server is allowed to answer for a hostname. "Expired" means the current time is later than the end date written inside that file. From that second, browsers stop trusting it.
The correct name today is a TLS certificate. TLS replaced the older SSL protocol, but most people still say SSL, so this guide uses both.
What is inside a certificate
A certificate follows a format called X.509, defined in RFC 5280. The parts that matter here are:
- The hostnames it covers, in a list called the Subject Alternative Name.
- A public key, which the browser uses to set up the encrypted connection.
- The issuer: the certificate authority (CA) that signed it. A CA is an organisation that browsers already trust.
- Two dates:
notBeforeandnotAfter. The certificate is valid only between them.
What the browser does
When you open an HTTPS page, the server sends its certificate. The browser checks four things: the hostname matches, the signature leads back to a CA it trusts, the certificate has not been revoked, and the current time is between the two dates.
If the time check fails, the browser shows a full page warning in place of your site. Nothing is loaded. Apps, API clients and payment callbacks fail too, and they usually fail with no screen for anyone to click through.
If your site sends the Strict-Transport-Security header (HSTS), browsers also remove the option to continue past the warning. Visitors cannot get in until you fix the certificate.
The dates are checked against the visitor's own clock. A visitor whose computer has the wrong date can see an expiry error on a perfectly good certificate.
How do you check when a certificate expires?
Connect to the server with openssl s_client and pass the certificate to openssl x509 -noout -dates. This reads the certificate the server sends to real visitors.
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
The output is two lines:
notBefore=Aug 14 09:12:37 2026 GMT
notAfter=Nov 12 09:12:36 2026 GMT
Read it like this. The certificate became valid on 14 August 2026 and stops being valid at 09:12:36 UTC on 12 November 2026. That is a 90 day certificate. On 12 October 2026 it has 31 days left: 19 more days in October plus 12 in November.
What each part of the command does
-connect example.com:443is the server and port to open.-servername example.comtells the server which site you want. One IP address often serves many sites, each with its own certificate.</dev/nullcloses the connection straight after the handshake, so the command does not wait for input.2>/dev/nullhides the connection messages.-noout -datesprints only the two dates and not the encoded certificate.
More commands worth keeping
Days left, as a number (Linux):
end=$(openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
echo $(( ($(date -d "$end" +%s) - $(date +%s)) / 86400 ))
Who issued it and which hostnames it covers:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -ext subjectAltName
A certificate file on disk:
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -dates
A yes or no answer for scripts. This asks "will it expire in the next 30 days?" (2592000 seconds is 30 days). It exits with status 0 if the certificate is safe and 1 if it is not:
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -checkend 2592000
A mail server that upgrades to TLS with STARTTLS:
openssl s_client -connect mail.example.com:587 -starttls smtp </dev/null 2>/dev/null | openssl x509 -noout -dates
One server behind a load balancer, by IP address:
openssl s_client -connect 203.0.113.10:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -serial
If you do not have a terminal to hand, the SSL checker shows the same dates in the browser.
How long does a certificate last?
A new public certificate can last at most 200 days today, and certificates from Let's Encrypt last 90 days. Both numbers are going down on a published schedule.
The maximum for public certificates
The limit is set by the CA/Browser Forum, the group of certificate authorities and browser makers that writes the rules for public certificates. In April 2025 it passed ballot SC-081v3, which cuts the maximum in three steps.
| Certificate issued | Maximum lifetime |
|---|---|
| Before 15 March 2026 | 398 days |
| From 15 March 2026 | 200 days |
| From 15 March 2027 | 100 days |
| From 15 March 2029 | 47 days |
The limit applies on the day a certificate is issued. A 398 day certificate issued in early March 2026 stays valid until its own end date, so you can still meet these until spring 2027.
Let's Encrypt
Let's Encrypt certificates last 90 days by default. Let's Encrypt has published its own schedule: the default drops to 64 days on 10 February 2027 and to 45 days on 16 February 2028.
What this means for you
Count the renewals. A 398 day certificate needed about 1 renewal a year. A 47 day certificate that is renewed with a third of its life left needs about 12 a year.
Renewing by hand does not survive that. Every certificate you own needs to renew without a person, and every one of those renewals needs something watching it.
Private certificates from your own internal CA are not covered by these limits. They still expire, and they are the ones most often forgotten, because no public tool can see them.
Why does automated renewal still fail?
Automated renewal fails because it depends on things outside the renewal tool: open ports, DNS records, API tokens, a server reload, and every place that holds a copy of the certificate. When one of those changes, renewal breaks without an error anyone sees.
Most automation uses the ACME protocol. The CA asks you to prove you control the hostname by passing a challenge. There are two common ones, described in the Let's Encrypt challenge documentation:
- HTTP-01: the CA fetches a file from your server over port 80.
- DNS-01: the CA looks up a TXT record at
_acme-challenge.example.com. Wildcard certificates need this one.
These are the failures that come up again and again.
| Failure | What went wrong | How to check |
|---|---|---|
| Port 80 closed | A firewall rule, a new proxy or a "HTTPS only" setting blocks the HTTP-01 check, which always starts on port 80 | curl -I http://example.com/.well-known/acme-challenge/test from outside your network. A 404 is fine. A timeout is not. |
| DNS challenge broke | The API token for your DNS provider expired, or DNS was moved to another provider | dig _acme-challenge.example.com TXT +short during a test renewal |
| Renewed, not reloaded | The new file is on disk but the web server still holds the old certificate in memory | Compare -serial of the file with -serial of the served certificate |
| Forgotten subdomain | A hostname such as api or status was set up by hand on another server and is in no renewal job | Check every hostname you own, one by one |
| Rate limit | A script asked for the same certificate again and again | Read the error text from your ACME client |
| Expired certificate higher in the chain | Your own certificate is fine, but one above it has expired or is no longer trusted | openssl s_client -showcerts and look at "Verify return code" |
| Old copy on a load balancer or CDN | The certificate was uploaded by hand to the edge. Renewing on the origin server does not update that copy. | Check the public hostname, not the origin |
| CAA record blocks the CA | A CAA record in DNS names a different CA from the one you now use | dig example.com CAA +short |
| The job is gone | The server was rebuilt and the renewal timer was not set up again | systemctl list-timers | grep -i certbot |
Three of these in more detail
Renewed but not reloaded is the most confusing one, because every check on the server says the certificate is new. Web servers such as nginx read the certificate when they start or reload. Add a reload to the renewal itself:
certbot renew --deploy-hook "systemctl reload nginx"
Rate limits at Let's Encrypt include 5 certificates for the exact same set of hostnames in 7 days, and 5 failed validations for one hostname in an hour. A deploy script that requests a fresh certificate on every run can use these up in an afternoon. The rate limit documentation has the full list.
An expired certificate higher in the chain is rare but wide. On 30 September 2021 the root certificate "DST Root CA X3" expired, and older devices that relied on it began to reject sites using Let's Encrypt, even though those sites had valid certificates of their own.
Do not count on a warning email from your CA either. Let's Encrypt stopped sending expiry notification emails on 4 June 2025.
What is the certificate chain, and how does it break?
The chain is the list of certificates that links yours to a CA the browser already trusts. It breaks when your server does not send the middle certificates, or when one of them has expired.
A chain has three levels:
- Leaf: your certificate, with your hostname in it.
- Intermediate: a certificate owned by the CA, which signed yours. There can be more than one.
- Root: the CA's top certificate. It is already stored in the browser or operating system. Your server does not need to send it.
Your server must send the leaf and every intermediate. The client then joins them up to a root it holds.
The missing intermediate
The common mistake is to configure the server with only the leaf. With Certbot, that means pointing at cert.pem when you should point at fullchain.pem:
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
This mistake is hard to spot, because desktop browsers can often fill the gap themselves. The site looks fine on your laptop. Then curl, a mobile app or another server's API client fails with "unable to verify the first certificate" or "unable to get local issuer certificate".
How to test the chain
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
Look at two places in the output:
- "Certificate chain" near the top. Entry
0is your leaf. You should see at least one more entry after it. - "Verify return code" near the bottom. You want
0 (ok).
| Verify return code | Meaning |
|---|---|
0 (ok) | The chain is complete and trusted |
10 (certificate has expired) | A certificate in the chain is past its end date |
18 (self-signed certificate) | The server sent one certificate that signed itself |
20 (unable to get local issuer certificate) | The issuer of a certificate in the chain was not found |
21 (unable to verify the first certificate) | Only the leaf was sent. The intermediate is missing. |
What is a hostname mismatch error?
A hostname mismatch means the name in the address bar is not in the certificate's list of names. The certificate can be in date and correctly signed and still fail this check.
Browsers compare the hostname with the Subject Alternative Name list. They no longer read the older Common Name field. Print the list with:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com
What a wildcard covers
A wildcard name such as *.example.com covers exactly one level of subdomain. It does not cover the bare domain and it does not cover two levels.
| Name in the certificate | example.com | www.example.com | api.example.com | a.b.example.com |
|---|---|---|---|---|
example.com | Yes | No | No | No |
*.example.com | No | Yes | Yes | No |
example.com and *.example.com | Yes | Yes | Yes | No |
Common causes
- The certificate has
www.example.combut notexample.com, or the reverse. Request both names. - A new subdomain points at an old server that answers with its default certificate for another site.
- The DNS record points at the wrong IP address. You reach a different server, and it sends its own certificate. Check the record first. The guide to DNS records shows how.
- You connected by IP address. Public certificates are normally issued for hostnames, so
https://203.0.113.10does not match.
What do the browser certificate error codes mean?
Each code names the one check that failed, so it tells you where to look. Only the date codes are about expiry.
| Code | Where you see it | What it means | Usual fix |
|---|---|---|---|
NET::ERR_CERT_DATE_INVALID | Chrome, Edge | The current time is outside the certificate's dates. It has expired, is not valid yet, or the visitor's clock is wrong. | Renew and reload. If only one person sees it, check their clock. |
NET::ERR_CERT_COMMON_NAME_INVALID | Chrome, Edge | The hostname is not in the certificate's list of names. | Issue a certificate that includes the name, or fix the DNS record. |
NET::ERR_CERT_AUTHORITY_INVALID | Chrome, Edge | The browser cannot link the certificate to a CA it trusts. It is self-signed, from a private CA, or an intermediate is missing. | Serve the full chain, or use a public CA. |
NET::ERR_CERT_REVOKED | Chrome, Edge | The CA has cancelled the certificate before its end date. | Issue a new certificate with a new key. |
NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM | Chrome, Edge | The certificate was signed with a method that is no longer accepted, such as SHA-1. | Issue a new certificate. |
NET::ERR_CERT_VALIDITY_TOO_LONG | Chrome, Edge | The gap between the two dates is longer than the browser allows. | Issue a certificate with a shorter lifetime. |
ERR_SSL_VERSION_OR_CIPHER_MISMATCH | Chrome, Edge | The browser and server share no TLS version or cipher. This is not a date problem. | Enable TLS 1.2 and TLS 1.3 on the server. |
SEC_ERROR_EXPIRED_CERTIFICATE | Firefox | The certificate is past its end date. | Renew and reload. |
SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE | Firefox | A certificate above yours in the chain has expired. | Serve the CA's current intermediate. |
SSL_ERROR_BAD_CERT_DOMAIN | Firefox | The hostname is not in the certificate. | Same as the name error above. |
SEC_ERROR_UNKNOWN_ISSUER | Firefox | Firefox does not trust the issuer, often because an intermediate is missing. | Serve the full chain. |
curl: (60) SSL certificate problem: certificate has expired | curl | The certificate, or one in its chain, is past its end date. | Renew and reload. |
If one visitor reports a date error and your own check shows a valid certificate, ask them what date their device shows. A wrong clock is the cause more often than you would guess.
What should you monitor, and at what thresholds?
Monitor the certificate that each public hostname really serves, checked from outside your network, and alert at 30, 14 and 7 days before the end date. Those three thresholds give you a notice, a warning and an emergency.
What each threshold means
The meaning depends on how the certificate renews. Many ACME clients renew a 90 day certificate when about 30 days are left.
| Days left | Renewed by hand | Renewed automatically (90 day certificate) |
|---|---|---|
| 30 | Start the renewal now | Normal. Renewal should happen in the next day or two. |
| 14 | The renewal is late. Give it an owner today. | Renewal has been failing for about 16 days. Find out why. |
| 7 | Emergency. Stop other work. | Emergency. Renew by hand if you must. |
| 0 | Visitors are blocked | Visitors are blocked |
As lifetimes get shorter, fixed day counts stop fitting. A 45 day certificate renewed at two thirds of its life is renewed with 15 days left, so "14 days left" already means renewal failed. For short certificates, set the first alert a day or two below the point where renewal should have happened.
What to put on the list
- Every hostname, not only the main one. Include
www,api,app,status, staging sites and anything a customer or a partner system connects to. - The served certificate, not the file. A file check passes in the "renewed but not reloaded" case. An outside check does not.
- The public address. If a CDN or load balancer sits in front, that is the certificate visitors get.
- Other TLS ports. Mail on 465, 587 and 993, and any database or message queue that uses TLS.
- The renewal job itself. Add a heartbeat to it, so you hear when it stops running. The cron job monitoring guide shows how.
17 3 * * * certbot renew -q && curl -fsS -m 10 --retry 5 -o /dev/null https://pulsx.net/api/beat/<your-token>
certbot renew exits with status 0 when nothing is due, so this ping means "the renewal job ran and nothing failed".
PULSX tracks SSL certificate expiry on every HTTPS monitor, so adding a site for uptime covers its certificate too. It is free for 20 monitors and you can sign up without a card.
What is the runbook for renewing a certificate?
The runbook is: confirm the problem from outside, find where the certificate lives, renew, reload, then verify from outside again. Do not skip the last step.
Before it expires: a routine check
- List what Certbot manages and when each certificate ends:
certbot certificates - Test renewal without changing anything:
certbot renew --dry-run - Confirm the timer exists:
systemctl list-timers | grep -i certbot - Compare the list of names with the hostnames you really use. Anything missing is a forgotten subdomain.
When it has expired or is about to
- Confirm from outside. Run the
openssl s_clientcommand from the top of this guide. Note thenotAfterdate and the serial number. - Find where TLS ends. Is it a CDN, a load balancer, or the web server itself? That is the place that needs the new certificate.
- Renew. Run
certbot renew --cert-name example.comand read the full error if it fails. - Fix the cause. Use the failure table above: port 80, the DNS challenge, the CAA record, a rate limit.
- Check the config and reload.
nginx -t && systemctl reload nginx - Copy it to the edge if a load balancer or CDN holds its own copy.
- Verify from outside. Run the openssl command again. The
notAfterdate and the serial number must both have changed, and "Verify return code" must be0 (ok). - Check the neighbours. Other hostnames set up at the same time often expire in the same week.
If Certbot says the certificate is not due
If step 3 reports that the certificate is not yet due for renewal, but visitors still see the old one, the renewal already worked. The server was never reloaded, or the edge has an old copy. Go to steps 5 and 6.
Avoid --force-renewal in a loop. Each forced run counts towards the rate limit of 5 certificates for the same names in 7 days.
Afterwards
Write down the cause while it is fresh, add the missing monitor, and add a reload hook if that was the gap. The incident postmortem guide has a format you can copy.