← Blog  ·  October 2026

SSL Certificate Expiry Notifications: Never Miss a Renewal Again

Let's Encrypt was supposed to end SSL expiry incidents. It didn't. Here's what still goes wrong — and how to fix it.

SSL certificate expiry is one of the most avoidable production outages in web infrastructure. Let's Encrypt made it dramatically better by automating renewal — yet certificates still expire and take down real services every day. The gap isn't the technology; it's the assumption that "auto-renewal is configured" equals "auto-renewal is working." SSL certificate expiry notifications exist to catch the difference.


Why certs still expire despite Let's Encrypt

Let's Encrypt's 90-day certificates with automated renewal via Certbot or similar clients work reliably — when everything is configured correctly and stays that way. In practice, several common patterns cause silent renewal failures:

Certbot's renewal timer breaks

On most systems, Certbot renewal runs via a systemd timer or a cron job. Either can fail without notice. A systemd timer can be masked, fail to start on a new OS installation, or be silently disabled by a package update. A cron job gets clobbered by a configuration management deploy or a server reimaging. Certbot's renewal mechanism is a scheduled job; scheduled jobs fail silently.

Check your Certbot timer right now:

systemctl status certbot.timer
# or
crontab -l | grep certbot

If either returns an error or nothing, your auto-renewal is not running.

The HTTP-01 or DNS-01 challenge fails

Let's Encrypt validates domain ownership during renewal. The HTTP-01 challenge requires that /.well-known/acme-challenge/ on your domain returns a specific token. If a CDN, reverse proxy, or redirect rule blocks that path — and your infrastructure changes frequently — renewal can fail silently for months. Certbot logs the failure but doesn't send you an email by default.

Let's Encrypt rate limits

Let's Encrypt enforces rate limits: 50 certificates per registered domain per week, and 5 duplicate certificates per week. A misconfigured deployment pipeline that requests a new certificate on every deploy can exhaust the rate limit, causing the next renewal attempt to fail with a 429 error. Again: Certbot logs this but doesn't email you.

Non-Let's Encrypt certificates

Not every cert comes from Let's Encrypt. Commercial CAs (DigiCert, Sectigo, GlobalSign), internal PKI, client certificates, API authentication certificates, and certificates for services behind firewalls that can't complete ACME challenges are all outside Let's Encrypt's scope. These renew manually — or not at all, until someone remembers. A 1-year or 2-year commercial certificate doesn't trigger any alert when it expires unless you've set one up explicitly.

Multi-server deployments

In a load-balanced setup, the certificate may be renewed on one node and not deployed to others. Or the certificate is stored in a central secret manager, renewed by a pipeline job, but the pipeline job silently fails for one environment. You end up with a renewed cert in staging and an expiring cert serving production traffic on two of your four nodes.


How to set up SSL certificate expiry email alerts

There are three main approaches to getting reliable expiry notifications.

Option 1: Certbot deploy hook + local mail

Certbot supports pre/post/deploy hooks that run when a certificate is issued or renewed. You can use a post-expiry check hook to send an alert when a cert is approaching expiry. Add a deploy hook at /etc/letsencrypt/renewal-hooks/deploy/notify.sh:

#!/bin/bash
DOMAIN="$RENEWED_DOMAINS"
EXPIRY=$(certbot certificates 2>/dev/null \
  | grep -A3 "$DOMAIN" \
  | grep "Expiry Date" \
  | awk '{print $3}')
echo "Certificate renewed for $DOMAIN. New expiry: $EXPIRY" \
  | mail -s "SSL Renewed: $DOMAIN" ops@yourcompany.com
chmod +x /etc/letsencrypt/renewal-hooks/deploy/notify.sh

This tells you when a renewal succeeds, not when it fails. To catch failures, add a separate cron-based check:

#!/bin/bash
# Run daily — alerts when cert is within 21 days of expiry
DOMAIN="yourdomain.com"
EXPIRY=$(echo | openssl s_client -servername "$DOMAIN" \
  -connect "$DOMAIN:443" 2>/dev/null \
  | openssl x509 -noout -enddate \
  | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null \
  || date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s)
DAYS=$(( (EXPIRY_EPOCH - $(date +%s)) / 86400 ))
if [ "$DAYS" -lt 21 ]; then
  echo "$DOMAIN expires in $DAYS days ($EXPIRY)" \
    | mail -s "SSL EXPIRY WARNING: $DOMAIN" ops@yourcompany.com
fi

Limitations: requires a working MTA; only checks one domain; the script itself can fail silently.

Option 2: OpenSSL cron script for multiple domains

For environments with multiple domains and no Let's Encrypt, a cron-based script that checks each domain against a threshold gives you a lightweight self-hosted notification system:

#!/bin/bash
THRESHOLD_DAYS=30
ALERT_EMAIL="ops@yourcompany.com"
DOMAINS=("api.yourapp.com" "app.yourapp.com" "admin.yourapp.com")

for DOMAIN in "${DOMAINS[@]}"; do
  EXPIRY=$(echo | openssl s_client \
    -servername "$DOMAIN" \
    -connect "$DOMAIN:443" 2>/dev/null \
    | openssl x509 -noout -enddate 2>/dev/null \
    | cut -d= -f2)

  if [ -z "$EXPIRY" ]; then
    echo "Could not fetch cert for $DOMAIN" \
      | mail -s "SSL CHECK FAILED: $DOMAIN" "$ALERT_EMAIL"
    continue
  fi

  EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null)
  DAYS=$(( (EXPIRY_EPOCH - $(date +%s)) / 86400 ))

  if [ "$DAYS" -lt "$THRESHOLD_DAYS" ]; then
    echo "$DOMAIN SSL cert expires in $DAYS days" \
      | mail -s "SSL EXPIRY: $DOMAIN ($DAYS days)" "$ALERT_EMAIL"
  fi
done

Add to crontab to run daily:

0 7 * * * /opt/scripts/ssl-check-all.sh >> /var/log/ssl-check.log 2>&1
Script-based monitoring has a blind spot Any script running on the same server as the certificate it's monitoring can fail when the server has problems. An expired cert often correlates with a server that's having other issues. External monitoring — running from outside your infrastructure — catches what on-server scripts miss.

Option 3: External SSL certificate monitoring service

The most reliable approach is an external service that checks your certificate from outside your infrastructure on a schedule and sends email alerts at multiple thresholds: 30 days, 14 days, 7 days, and 1 day before expiry. This works for any certificate — Let's Encrypt, commercial CAs, wildcard certs — and doesn't depend on your server's health, MTA configuration, or cron daemon.

SSLWatch takes this approach: enter a domain, get alerts. No agents, no scripts, no server-side changes required.


What to check when an expiry alert fires

When you receive an SSL expiry notification, work through this checklist before panicking:

  1. Check the current expiry date. Run echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates to confirm the live cert's expiry. Sometimes an alert fires for a cert that has already been renewed but the monitoring service hasn't picked up the new expiry yet.
  2. Check Certbot's renewal log. Run certbot certificates to see the expiry as Certbot sees it, and check /var/log/letsencrypt/letsencrypt.log for recent renewal attempts and any errors.
  3. Test renewal manually. Run certbot renew --dry-run to see if renewal would succeed without actually issuing a certificate.
  4. Check the renewal hook and challenge path. If dry-run fails, check whether /.well-known/acme-challenge/ is accessible on port 80 for HTTP-01 challenges.
  5. For commercial certs: identify the CA, log into the CA dashboard, check whether auto-renewal was configured, and initiate a manual renewal if necessary.

Check your cert's current status

Before setting up notifications, verify what your current certificate looks like from the outside — the same view a browser sees:

🔍 Instant SSL certificate check

See your certificate's expiry date, issuer, and chain validity in seconds. No signup, no install.

Check your SSL cert →

Set up automated SSL expiry alerts with SSLWatch

SSLWatch monitors your SSL certificates externally and sends email alerts at 30, 14, 7, and 1 day before expiry. It works for any certificate on any server — Let's Encrypt, commercial, wildcard — without agents or server-side changes.

🔒 SSL certificate expiry notifications that actually work

SSLWatch checks your certs from outside your infrastructure and emails you at 30, 14, 7, and 1 day before expiry — whether you're using Let's Encrypt, a commercial CA, or a wildcard cert.

Join the SSLWatch waitlist → Check your cert for free

← All articles  ·  SSLWatch  ·  Free SSL checker