How do I get alerted before my SSL certificate expires?

Point an external check at your HTTPS URL and have it read the certificate on every request, then alert you when the days remaining fall below a threshold you choose. Fourteen days is a sane default for automated renewal, thirty if a human still has to do the renewing. The alert has to reach a device you actually look at — a log line, a mailbox nobody reads, or a dashboard you open once a week is the same as no alert at all.

Why do certificates still expire when renewal is automated?

Because automation fails quietly. Certificate renewal is one of the most reliably automated tasks in operations, and that is exactly why expiry still catches people: the renewal ran fine for eighteen months, so nobody watches it any more.

The common failure modes are boring and all look identical from outside:

  • The renewal cron entry was lost in a server migration or an OS upgrade.
  • The renewal succeeded but the new certificate was never loaded — the web server was not reloaded, so it is still serving the old file from memory.
  • The HTTP-01 challenge now fails because a redirect, a firewall rule, or a CDN sits in front of the validation path.
  • The DNS-01 challenge fails because the API credential for the DNS provider expired.
  • Renewal works on the origin server but a load balancer, proxy, or third-party edge holds its own copy of the certificate.

Note what those have in common. In four of the five, the renewal tool itself is not throwing an error at you at the moment things break, and in one of them the renewal genuinely succeeded. Checking that renewal ran is not the same as checking that visitors get a valid certificate. Only the second one is the thing your users experience.

What actually happens when a certificate expires?

Nothing degrades gracefully. Browsers show a full-page interstitial warning that most visitors will not click through, and they are right not to. API clients are worse: curl, most HTTP libraries, and every mobile app with default TLS settings refuse the connection outright. Webhooks from payment processors and other platforms stop being delivered, and some senders will disable your endpoint after enough failures.

An expired certificate is therefore a total outage for a cause that was scheduled, visible, and preventable for the entire ninety days beforehand. That is what makes it worth a dedicated alert rather than folding it into general uptime.

How do I check the expiry date myself?

One command, no account needed anywhere. This prints the notBefore and notAfter dates for a live host:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -dates

The -servername flag matters. It sends SNI, so a host serving several sites from one IP hands you the right certificate instead of the default one. Leave it off and you can end up measuring a certificate that no visitor of yours will ever see.

To get a number you can act on rather than a date you have to read, ask openssl whether the certificate is still valid at a future point in time. -checkend takes seconds and exits non-zero when the certificate will have expired by then:

#!/usr/bin/env bash
# ssl-expiry-check.sh — warn if a cert expires within N days
set -euo pipefail

HOST="${1:?usage: ssl-expiry-check.sh host [days]}"
DAYS="${2:-14}"
PINGWIRE_KEY="pw_live_xxxxxxxxxxxxxxxxxxxxxxxx"

end_date=$(echo | openssl s_client -connect "$HOST:443" -servername "$HOST" 2>/dev/null \
  | openssl x509 -noout -enddate | cut -d= -f2)
left=$(( ( $(date -d "$end_date" +%s) - $(date +%s) ) / 86400 ))

if [ "$left" -le "$DAYS" ]; then
  curl -sS -X POST https://pingwire.dev/api/v1/messages.php \
    -H "Authorization: Bearer $PINGWIRE_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"channel\":\"alerts\",\"text\":\"TLS cert for $HOST expires in ${left}d\"}"
fi

Run it once a day from cron and you have a working warning system for the cost of ten minutes. The same one-curl-call pattern works for any script-shaped alert — there is a fuller walkthrough in sending a push notification from a bash script.

The catch with checking it yourself

This script has the same blind spot as the renewal cron it is meant to protect. It lives on a box, and if that box, its cron daemon, or its network path goes away, the check goes silent — and silence looks exactly like success. A check that stops running never tells you it stopped running. If you keep this approach, wrap it in a heartbeat monitor so that the checker not reporting in is itself an alert.

How do I get the alert without maintaining the checker?

Let something outside your infrastructure do the reading. Every HTTPS check an uptime monitor in Pingwire performs already records the days remaining on the certificate alongside the status code and the response time — the certificate is presented during the handshake, so there is no extra request and nothing to configure per host beyond the URL.

Setup is three fields:

  • URL — any https:// address, including an API endpoint rather than a page.
  • Check interval — every 5 minutes on Free, as often as every 1 minute on Pro.
  • SSL warning days — the threshold, anywhere from 1 to 90 days. It defaults to 14.

When the remaining days drop to or below your threshold, Pingwire opens an incident and pushes your phone. When the certificate is renewed and the number goes back above the threshold, the incident auto-resolves with a certificate renewed event — so a successful renewal closes the loop by itself and you get confirmation without doing anything.

Expiry warnings are separate from up/down

This is deliberate and it matters more than it sounds. A site with a certificate that expires in nine days is up. It returns 200, it is fast, it is fine. If the expiry warning were folded into the down/up state, you would either get a false outage alert or — far more likely — the warning would be suppressed because the monitor is green.

So an expiry incident rides on its own track with its own cause, its own incident record, and its own recovery. Your monitor stays up, and you get told about the thing that will take it down. Ordinary downtime alerting is covered separately in how to get notified when your website goes down.

How many days of warning should I set?

Work backwards from how long a fix actually takes you, then add slack for the fix failing on the first attempt.

SituationSuggested thresholdWhy
Automated renewal, you control the server14 daysRoughly the point where a 90-day certificate should already have renewed; a warning here means the automation is broken, and two weeks is plenty of room.
Manual renewal, or a certificate you buy30 daysPurchasing, validation, and installation involve other people and can take a week on their own.
Certificate managed by a third party or a client30 to 45 daysYou are not the person who can fix it, so the lead time has to cover chasing someone who is.
Short-lived certificates, renewed every few days1 to 3 daysA 14-day warning on a 6-day certificate fires constantly and trains you to ignore it.

The default of 14 days is aimed at the first row, which is where most people are. Let's Encrypt certificates are valid for 90 days and their tooling aims to renew at 60 days, so a warning at 14 days means renewal has already had roughly a month of chances to succeed. If it has not, something is genuinely wrong and you want to know.

One rule regardless of the number: pick a threshold that will be quiet in normal operation. An alert that fires every renewal cycle is not an alert, it is a habit, and the day it means something you will swipe it away with the rest.

What about certificates that are not on a web page?

Mail servers, database connections, internal services behind a VPN, and client certificates all expire too, and an external HTTP check cannot reach any of them. For those, the openssl script above is the right tool — it works against any TLS port, and for protocols that upgrade mid-session openssl has a -starttls option:

echo | openssl s_client -connect mail.example.com:587 -starttls smtp 2>/dev/null \
  | openssl x509 -noout -enddate

Run that from somewhere that can reach the service, push the result out with one API call, and put a heartbeat on the job that runs it. The full API surface, including the raw-text variant that turns any command output into a message, is documented on the developers page.

The short version

Certificate expiry is the rare outage that announces itself weeks ahead of time in a machine-readable field. All you have to do is read that field from outside your own infrastructure, on a schedule, and route the number to a device you will actually see. Check with openssl if you want to own the plumbing; use an external monitor if you would rather not maintain the thing that maintains the thing.

Frequently asked questions

How do I check when an SSL certificate expires from the command line?

Run: echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates. Include -servername so SNI sends the right hostname, otherwise a shared IP may hand you a different site's certificate than the one your visitors see.

How many days before expiry should I be warned?

Fourteen days is a good default when renewal is automated, because a 90-day certificate should have renewed around day 60 and a warning at day 14 means the automation is already broken. Use 30 days when renewal is manual or depends on another party, and 1 to 3 days for short-lived certificates that renew every few days.

Does a certificate expiry warning mean my site is down?

No, and that is why Pingwire tracks it separately. A site whose certificate expires in nine days still returns 200 and is perfectly healthy right now. The expiry warning opens its own incident with its own cause, so the monitor's up/down state is untouched and the warning is never suppressed by a green check.

What happens after I renew the certificate?

The next check reads the new expiry date, sees that the days remaining are back above your threshold, and auto-resolves the incident with a 'certificate renewed' event. You do not have to close anything by hand, and the resolution doubles as confirmation that the renewal actually reached the server visitors hit.

Can I monitor expiry for a mail server or an internal service?

Not with an external HTTP check, since it cannot reach a non-HTTP port or anything behind a VPN. Run openssl s_client from a host that can reach the service — adding -starttls smtp for mail — and push the result with a single API call. Put a heartbeat monitor on that job so the checker going silent is itself an alert.

Why did my certificate expire even though auto-renewal was set up?

The usual causes are a renewal cron entry lost during a server migration, a renewal that succeeded but was never followed by a web server reload, a validation challenge broken by a new redirect or firewall rule, an expired DNS provider credential, or an edge proxy holding its own copy of the old certificate. Checking that renewal ran is not the same as checking what visitors are served.

Try Pingwire

Send your first alert in under 30 seconds — one HTTP call, straight to a chat and your phone.

Create a free account Read the API docs

More from the blog