← Blog  ·  October 2026

How to Set Up SSL Certificate Expiry Email Alerts

Your certificate registrar did send a renewal reminder. It went to a shared inbox that nobody checks. Three people assumed someone else handled it.

SSL certificate expiry is one of the most preventable outages in web operations — and one of the most common. The fix is simple: get an email alert far enough in advance to act on it. In practice, this turns out to be harder than it sounds, because the places you'd expect the alert to come from tend to fail silently.

This guide explains why SSL expiry email alerts get missed, how to build a working bash script that sends alerts directly from your server using openssl and sendmail, and the limitations you'll hit as the number of certificates grows.


Why SSL expiry emails get missed

There are several common alert paths for SSL expiry, and each has a characteristic failure mode:

Certificate registrar renewal reminders

Most certificate authorities and domain registrars send renewal reminder emails 30 and 7 days before expiry. These emails go to the address on file when the certificate was purchased — which may be a colleague who left the company, a shared inbox that nobody actively monitors, or a personal email that the original purchaser used when setting up a freelance project. When the team rotates or the original owner moves on, the alert path breaks.

Let's Encrypt expiry emails

Let's Encrypt sends an expiry notification to the email address registered when the certificate was created. This has the same problem: the email goes to whoever ran certbot certonly the first time, not to whoever is currently responsible for the infrastructure. If auto-renewal is configured correctly, these emails become background noise and people stop reading them. If auto-renewal breaks, the emails arrive but are ignored because they've always arrived and the cert has always renewed.

Server-local mail

Cron jobs that send alerts via mail or sendmail on the same server route to the local mail spool (/var/mail/root or similar). On most production Linux servers, nobody reads the local mail spool. These alerts are functionally invisible unless you've explicitly configured mail forwarding.


A working bash script for SSL expiry email alerts

The most reliable DIY approach is a script that checks expiry directly and sends an email via SMTP — bypassing the local mail spool and going directly to an address you actually monitor:

#!/bin/bash
# ssl-expiry-alert.sh
# Dependencies: openssl, curl (for SMTP via curl, or configure sendmail/postfix)

DOMAINS=(
  "example.com"
  "api.example.com"
  "staging.example.com"
)
WARN_DAYS=30
ALERT_FROM="monitoring@example.com"
ALERT_TO="ops@example.com"
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_USER="monitoring@example.com"
SMTP_PASS="your-app-password"  # Use an app password, not your main password

send_alert_email() {
  local domain="$1"
  local days="$2"
  local expiry="$3"

  local subject="SSL EXPIRY ALERT: $domain expires in $days days"
  local body="SSL Certificate Expiry Warning

Domain:     $domain
Expires:    $expiry
Days left:  $days

Action required: renew the certificate before expiry to avoid browser security warnings.

-- SSL Monitoring Script"

  curl -s --ssl-reqd \
    --url "smtps://$SMTP_HOST:$SMTP_PORT" \
    --user "$SMTP_USER:$SMTP_PASS" \
    --mail-from "$ALERT_FROM" \
    --mail-rcpt "$ALERT_TO" \
    --upload-file - <
To: Ops Team <$ALERT_TO>
Subject: $subject

$body
EOF
}

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

  if [ -z "$EXPIRY" ]; then
    echo "$(date): WARNING — could not retrieve cert for $DOMAIN"
    continue
  fi

  EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || \
    date -j -f "%b %d %T %Y %Z" "$EXPIRY" +%s)
  NOW_EPOCH=$(date +%s)
  DAYS=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

  echo "$(date): $DOMAIN — $DAYS days until expiry ($EXPIRY)"

  if [ "$DAYS" -le "$WARN_DAYS" ]; then
    echo "$(date): Sending alert for $DOMAIN ($DAYS days)"
    send_alert_email "$DOMAIN" "$DAYS" "$EXPIRY"
  fi
done

Schedule it in crontab to run daily:

# Run daily at 8am, log output
0 8 * * * /usr/local/bin/ssl-expiry-alert.sh >> /var/log/ssl-expiry-alerts.log 2>&1
Use app passwords, not main passwords For Gmail and most modern email providers, SMTP authentication requires an app-specific password (not your login password), and you must enable "less secure app access" or use OAuth. Gmail's app password is found at myaccount.google.com → Security → App passwords. Never hardcode your main account password in a shell script.

Escalating alerts as expiry approaches

A single alert 30 days out is easy to miss or defer. A better approach is escalating alerts at multiple thresholds — with increasing urgency:

WARN_DAYS_CRITICAL=7
WARN_DAYS_HIGH=14
WARN_DAYS_MEDIUM=30

if [ "$DAYS" -le "$WARN_DAYS_CRITICAL" ]; then
  SUBJECT="🚨 CRITICAL: SSL cert for $DOMAIN expires in $DAYS days"
  PRIORITY="high"
elif [ "$DAYS" -le "$WARN_DAYS_HIGH" ]; then
  SUBJECT="⚠️ URGENT: SSL cert for $DOMAIN expires in $DAYS days"
  PRIORITY="normal"
elif [ "$DAYS" -le "$WARN_DAYS_MEDIUM" ]; then
  SUBJECT="SSL cert for $DOMAIN expires in $DAYS days — action needed"
  PRIORITY="low"
else
  # No alert needed
  continue
fi

This structure means you get a low-urgency prompt at 30 days, a higher-urgency prompt at 14, and an actionable critical alert at 7. The critical alert should also go to a channel you're guaranteed to see promptly (Slack, SMS, PagerDuty) rather than only email.


Testing your alert script before relying on it

The most important thing you can do after setting up any alert script is confirm it actually delivers the email before depending on it. Set the warning threshold temporarily to a large number to force the alert to fire:

# Test: force alert for any cert with less than 9999 days remaining
WARN_DAYS=9999 bash /usr/local/bin/ssl-expiry-alert.sh

If you don't receive the email within a few minutes, check:

  1. SMTP credentials — test independently with a minimal curl --mail-from ... --mail-rcpt ... call
  2. Spam folder — transactional email from a new server IP often lands in spam initially
  3. SPF/DKIM setup on your sending domain — without SPF records, email from your server may be rejected by the recipient's spam filter
  4. Firewall rules — outbound port 587 may be blocked on some hosting providers
Test the failure case, not just the success case Run the script with a domain that doesn't exist or has a firewall blocking port 443. Verify that a connection failure produces a meaningful log entry rather than silently passing — an unchecked domain is indistinguishable from a healthy one if the script swallows errors.

Where the bash script approach breaks down

The openssl + curl + cron script works well for a handful of domains on infrastructure you control. It has real limitations:

For personal projects and small deployments, the script is a practical solution. For any domain where SSL expiry has material business impact, external monitoring that sends from a reliable email infrastructure is worth the low cost.

🔒 Get SSL expiry alerts without the script maintenance

Check your SSL certificate right now with the free SSL Certificate Checker — see expiry date, issuer, and chain validity instantly. SSLWatch monitors your certificates continuously and sends escalating email alerts before expiry, from an infrastructure built for reliable delivery.

Check SSL certificate → Get automated alerts with SSLWatch

← All articles  ·  SSL checker tool  ·  SSLWatch