Certificate Expiration: Monitoring, Alerts, and Auto-Renewal

How to monitor SSL certificate expiration, set up alerts before certs expire, and automate renewal. Covers openssl checks, Prometheus, cron monitoring, and Let's Encrypt auto-renewal.


Why Certificates Expire

Certificates have expiration dates for security reasons. If a private key is compromised, the damage is limited to the certificate's validity period. Shorter lifetimes also force regular key rotation.

Common validity periods: - Let's Encrypt: 90 days - Commercial DV/OV: 1 year (397 days max since 2020) - Self-signed / internal: Whatever you set (common: 1-10 years)

An expired certificate doesn't just show a browser warning -- it breaks API calls, webhook deliveries, mobile apps, and automated systems that validate certificates strictly.

Check Expiration from the Command Line

Check a remote server

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

Output:

notBefore=Jan 15 00:00:00 2026 GMT
notAfter=Apr 15 23:59:59 2026 GMT

Check a local certificate file

openssl x509 -in cert.pem -noout -enddate

Check days until expiry

expiry=$(openssl x509 -in cert.pem -noout -enddate | cut -d= -f2)
days=$(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 ))
echo "$days days until expiry"

Check via our SSL checker

Paste your domain into getaCert.com/check to see the full certificate details including expiration date.

Simple Monitoring with Cron

A bash script that checks your domains and emails you when certificates are about to expire:

#!/bin/bash
# check-certs.sh — Run from cron daily

DOMAINS="example.com api.example.com mail.example.com"
WARN_DAYS=30
ALERT_EMAIL="admin@example.com"

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 "WARNING: Cannot connect to $domain" | \
            mail -s "SSL Check Failed: $domain" "$ALERT_EMAIL"
        continue
    fi

    days=$(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 ))

    if [ "$days" -lt "$WARN_DAYS" ]; then
        echo "Certificate for $domain expires in $days days ($expiry)" | \
            mail -s "SSL Expiring: $domain ($days days)" "$ALERT_EMAIL"
    fi
done

Add to cron:

0 8 * * * /path/to/check-certs.sh

Monitoring with Prometheus

Use the ssl_exporter or blackbox_exporter to track certificate expiry as a metric.

Blackbox exporter config

# blackbox.yml
modules:
  https_check:
    prober: http
    http:
      preferred_ip_protocol: "ip4"
      tls_config:
        insecure_skip_verify: false

Prometheus scrape config

scrape_configs:
  - job_name: 'ssl'
    metrics_path: /probe
    params:
      module: [https_check]
    static_configs:
      - targets:
        - https://example.com
        - https://api.example.com
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115

Grafana alert

Create an alert on probe_ssl_earliest_cert_expiry:

probe_ssl_earliest_cert_expiry - time() < 86400 * 30

This fires when any certificate is within 30 days of expiry.

Auto-Renewal Strategies

Let's Encrypt (certbot)

Certbot handles renewal automatically. Verify it's working:

# Check the renewal timer
systemctl status certbot.timer

# Test renewal
sudo certbot renew --dry-run

ACME for internal certificates

Use an ACME client with your internal CA (like step-ca) for automatic certificate renewal of internal services.

Script-based renewal

For certificates that can't use ACME:

#!/bin/bash
# renew-cert.sh

CERT="/etc/ssl/certs/server.pem"
DAYS_LEFT=$(( ($(date -d "$(openssl x509 -in $CERT -noout -enddate | cut -d= -f2)" +%s) - $(date +%s)) / 86400 ))

if [ "$DAYS_LEFT" -lt 30 ]; then
    # Generate new cert (example using getaCert.com API)
    curl -X POST https://getacert.com/api/v1/self-signed \
        -H "X-API-Key: your-api-key" \
        -d '{"cn": "example.com", "days": 365}' \
        -o /tmp/new-cert.json

    # Extract and install new cert
    # ... (parse JSON, write files, reload service)

    systemctl reload nginx
    echo "Certificate renewed ($DAYS_LEFT days remaining)"
fi

Certificate Lifecycle Best Practices

  1. Monitor early -- Alert at 30 days before expiry, not 7
  2. Automate renewal -- Manual renewal fails when people are on vacation
  3. Test renewal -- Run certbot renew --dry-run monthly
  4. Track all certificates -- Maintain an inventory of every certificate, its expiry date, and who owns it
  5. Use short-lived certificates -- 90-day certs with auto-renewal are more secure than 1-year certs that get forgotten
  6. Have a runbook -- Document the exact steps to renew each certificate for when automation fails

Next Steps


More in Gotchas