SSL certificate expiry: check, monitor and renew

By the PULSX team. Updated . 14 minute read.

TL;DR

Every SSL certificate has an end date, and browsers refuse the site from the second after it. Read the date with openssl, renew automatically, and monitor the certificate your server really sends, because automated renewal can fail without an error anyone sees.

Key takeaways

  • An expired certificate blocks every visitor with a full page warning. The site itself can be healthy.
  • One openssl command shows the start and end date of the certificate a server sends.
  • Maximum lifetimes are falling: 200 days now, 100 days from 15 March 2027, 47 days from 15 March 2029.
  • Renewal most often fails at the edges: a closed port 80, a broken DNS challenge, or a server that was not reloaded.
  • Monitor from outside, on every hostname, and alert at 30, 14 and 7 days.
  • After any renewal, check the served certificate, not the file on disk.

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: notBefore and notAfter. 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:443 is the server and port to open.
  • -servername example.com tells the server which site you want. One IP address often serves many sites, each with its own certificate.
  • </dev/null closes the connection straight after the handshake, so the command does not wait for input.
  • 2>/dev/null hides the connection messages.
  • -noout -dates prints 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 issuedMaximum lifetime
Before 15 March 2026398 days
From 15 March 2026200 days
From 15 March 2027100 days
From 15 March 202947 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.

FailureWhat went wrongHow to check
Port 80 closedA firewall rule, a new proxy or a "HTTPS only" setting blocks the HTTP-01 check, which always starts on port 80curl -I http://example.com/.well-known/acme-challenge/test from outside your network. A 404 is fine. A timeout is not.
DNS challenge brokeThe API token for your DNS provider expired, or DNS was moved to another providerdig _acme-challenge.example.com TXT +short during a test renewal
Renewed, not reloadedThe new file is on disk but the web server still holds the old certificate in memoryCompare -serial of the file with -serial of the served certificate
Forgotten subdomainA hostname such as api or status was set up by hand on another server and is in no renewal jobCheck every hostname you own, one by one
Rate limitA script asked for the same certificate again and againRead the error text from your ACME client
Expired certificate higher in the chainYour own certificate is fine, but one above it has expired or is no longer trustedopenssl s_client -showcerts and look at "Verify return code"
Old copy on a load balancer or CDNThe 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 CAA CAA record in DNS names a different CA from the one you now usedig example.com CAA +short
The job is goneThe server was rebuilt and the renewal timer was not set up againsystemctl 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:

  1. Leaf: your certificate, with your hostname in it.
  2. Intermediate: a certificate owned by the CA, which signed yours. There can be more than one.
  3. 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 0 is your leaf. You should see at least one more entry after it.
  • "Verify return code" near the bottom. You want 0 (ok).
Verify return codeMeaning
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 certificateexample.comwww.example.comapi.example.coma.b.example.com
example.comYesNoNoNo
*.example.comNoYesYesNo
example.com and *.example.comYesYesYesNo

Common causes

  • The certificate has www.example.com but not example.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.10 does 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.

CodeWhere you see itWhat it meansUsual fix
NET::ERR_CERT_DATE_INVALIDChrome, EdgeThe 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_INVALIDChrome, EdgeThe 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_INVALIDChrome, EdgeThe 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_REVOKEDChrome, EdgeThe CA has cancelled the certificate before its end date.Issue a new certificate with a new key.
NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHMChrome, EdgeThe certificate was signed with a method that is no longer accepted, such as SHA-1.Issue a new certificate.
NET::ERR_CERT_VALIDITY_TOO_LONGChrome, EdgeThe gap between the two dates is longer than the browser allows.Issue a certificate with a shorter lifetime.
ERR_SSL_VERSION_OR_CIPHER_MISMATCHChrome, EdgeThe 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_CERTIFICATEFirefoxThe certificate is past its end date.Renew and reload.
SEC_ERROR_EXPIRED_ISSUER_CERTIFICATEFirefoxA certificate above yours in the chain has expired.Serve the CA's current intermediate.
SSL_ERROR_BAD_CERT_DOMAINFirefoxThe hostname is not in the certificate.Same as the name error above.
SEC_ERROR_UNKNOWN_ISSUERFirefoxFirefox does not trust the issuer, often because an intermediate is missing.Serve the full chain.
curl: (60) SSL certificate problem: certificate has expiredcurlThe 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 leftRenewed by handRenewed automatically (90 day certificate)
30Start the renewal nowNormal. Renewal should happen in the next day or two.
14The renewal is late. Give it an owner today.Renewal has been failing for about 16 days. Find out why.
7Emergency. Stop other work.Emergency. Renew by hand if you must.
0Visitors are blockedVisitors 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

  1. List what Certbot manages and when each certificate ends: certbot certificates
  2. Test renewal without changing anything: certbot renew --dry-run
  3. Confirm the timer exists: systemctl list-timers | grep -i certbot
  4. 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

  1. Confirm from outside. Run the openssl s_client command from the top of this guide. Note the notAfter date and the serial number.
  2. 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.
  3. Renew. Run certbot renew --cert-name example.com and read the full error if it fails.
  4. Fix the cause. Use the failure table above: port 80, the DNS challenge, the CAA record, a rate limit.
  5. Check the config and reload. nginx -t && systemctl reload nginx
  6. Copy it to the edge if a load balancer or CDN holds its own copy.
  7. Verify from outside. Run the openssl command again. The notAfter date and the serial number must both have changed, and "Verify return code" must be 0 (ok).
  8. 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.

Common questions

How do I check when an SSL certificate expires?+
Run: openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates. The notAfter line is the end date, in UTC. You can also click the padlock or site information icon in a browser and open the certificate details.
What happens when an SSL certificate expires?+
Browsers stop loading the site and show a full page warning such as NET::ERR_CERT_DATE_INVALID. Apps and API clients refuse the connection. If the site uses HSTS, visitors get no option to continue. The server itself keeps running, so an uptime check that ignores certificates can still show it as up.
How long is an SSL certificate valid for?+
A public certificate issued today can last at most 200 days. The limit falls to 100 days on 15 March 2027 and to 47 days on 15 March 2029. Certificates issued before 15 March 2026 could last up to 398 days. Let's Encrypt certificates last 90 days by default.
How many days before expiry should I renew?+
Renew automatically when about one third of the lifetime is left, which is 30 days for a 90 day certificate. If you renew by hand, start at 30 days. Set alerts at 30, 14 and 7 days so that a failed renewal is noticed with time to fix it.
Why did my certificate expire when I have auto-renewal?+
The usual causes are: port 80 was closed so the HTTP challenge failed, the DNS API token expired, the certificate renewed but the web server was not reloaded, a load balancer or CDN still holds an old copy, or the hostname was never in the renewal job. Compare the serial number of the file on disk with the one the server sends to tell these apart.
Does a wildcard certificate cover the main domain?+
No. A certificate for *.example.com covers www.example.com and api.example.com, but not example.com and not a.b.example.com. Request both example.com and *.example.com in the same certificate.
Why does my site work in the browser but fail in curl?+
The server is probably not sending the intermediate certificate. Desktop browsers can often fill that gap, but curl and most API clients do not. Configure the server with the full chain file (fullchain.pem with Certbot), reload, and check that openssl s_client reports Verify return code: 0 (ok).
Does PULSX monitor SSL certificate expiry?+
Yes. SSL certificate expiry is tracked on every HTTPS monitor. PULSX is free with 20 monitors per account and no card needed. Alerts go out by email, Slack, Discord, webhook or push to the Android app.

More guides

Free DNS, SSL and cron tools

Know the minute your site goes down.

PULSX checks your site, API, cron jobs and SSL certificate every 60 seconds. Free for 20 monitors, with no card.