Docs / Monitoring & Logging / How to Monitor SSL Certificate Expiration Automatically

How to Monitor SSL Certificate Expiration Automatically

By Admin · Mar 2, 2026 · Updated Apr 23, 2026 · 21 views · 3 min read

How to Monitor SSL Certificate Expiration Automatically

Expired SSL certificates cause browser warnings, break API integrations, and erode trust. Automated monitoring ensures you are alerted well before any certificate expires on your Breeze server or across your infrastructure.

Method 1: Bash Script with Cron

Create a monitoring script at /usr/local/bin/check-ssl.sh:

#!/bin/bash
# SSL Certificate Expiration Checker
DOMAINS=(
    "yourdomain.com"
    "api.yourdomain.com"
    "app.yourdomain.com"
)
WARNING_DAYS=30
CRITICAL_DAYS=7
LOG="/var/log/ssl-check.log"

echo "$(date): SSL Certificate Check" >> "$LOG"

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 "  ERROR: Cannot connect to $DOMAIN" >> "$LOG"
        continue
    fi

    EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
    NOW_EPOCH=$(date +%s)
    DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

    if [ "$DAYS_LEFT" -lt "$CRITICAL_DAYS" ]; then
        echo "  CRITICAL: $DOMAIN expires in $DAYS_LEFT days ($EXPIRY)" >> "$LOG"
        mail -s "CRITICAL: SSL cert for $DOMAIN expires in $DAYS_LEFT days" \
            admin@yourdomain.com  "$LOG"
        mail -s "WARNING: SSL cert for $DOMAIN expires in $DAYS_LEFT days" \
            admin@yourdomain.com  "$LOG"
    fi
done
chmod +x /usr/local/bin/check-ssl.sh

# Run daily at 8 AM via cron
echo "0 8 * * * /usr/local/bin/check-ssl.sh" | sudo crontab -

Method 2: Prometheus ssl_exporter

Use the Prometheus SSL exporter for metric-based monitoring:

docker run -d --name ssl-exporter \
  -p 9219:9219 \
  ribbybibby/ssl-exporter:latest

Add a scrape config to prometheus.yml:

scrape_configs:
  - job_name: ssl
    metrics_path: /probe
    static_configs:
      - targets:
          - yourdomain.com:443
          - api.yourdomain.com:443
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: ssl-exporter:9219

Create an alert rule for certificates nearing expiry:

groups:
  - name: ssl_alerts
    rules:
      - alert: SSLCertExpiringSoon
        expr: ssl_cert_not_after - time() < 30 * 24 * 3600
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "SSL cert for {{ $labels.instance }} expires in less than 30 days"
      - alert: SSLCertExpiryCritical
        expr: ssl_cert_not_after - time() < 7 * 24 * 3600
        for: 1h
        labels:
          severity: critical
        annotations:
          summary: "SSL cert for {{ $labels.instance }} expires in less than 7 days"

Method 3: Python Script for Multiple Protocols

#!/usr/bin/env python3
import ssl
import socket
import datetime
import json

def check_cert(hostname, port=443):
    context = ssl.create_default_context()
    with socket.create_connection((hostname, port), timeout=10) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            cert = ssock.getpeercert()
            expire_date = datetime.datetime.strptime(
                cert['notAfter'], '%b %d %H:%M:%S %Y %Z'
            )
            days_left = (expire_date - datetime.datetime.utcnow()).days
            return {
                'hostname': hostname,
                'expires': cert['notAfter'],
                'days_left': days_left,
                'issuer': dict(x[0] for x in cert['issuer'])
            }

domains = ['yourdomain.com', 'api.yourdomain.com', 'app.yourdomain.com']
for domain in domains:
    try:
        result = check_cert(domain)
        status = 'OK' if result['days_left'] > 30 else 'WARNING' if result['days_left'] > 7 else 'CRITICAL'
        print(f"[{status}] {domain}: {result['days_left']} days remaining")
    except Exception as e:
        print(f"[ERROR] {domain}: {e}")

Setting Up Grafana Dashboard

If using the Prometheus method, create a Grafana dashboard with these panels:

  • Gauge panel — showing days until expiry for each domain
  • Table panel — listing all certificates with issuer and expiry dates
  • Alert list — showing any triggered SSL alerts

Best Practices

  • Monitor all domains, subdomains, and API endpoints on your Breeze infrastructure
  • Set warning thresholds at 30 days and critical thresholds at 7 days
  • Include internal services that use self-signed or private CA certificates
  • Test your alerting pipeline regularly to confirm notifications are delivered
  • Combine monitoring with automated renewal using Certbot for a hands-free solution

Was this article helpful?