crontoolModern Cron Expression Builder

SSL Certificate Check Cron Job — Weekly Expiry Alert Every Monday

Check all your domains for expiring SSL certificates every Monday and get alerted before the 30-day threshold.

0 0 * * 1
At 12:00 AM, only on Monday
Open in builder →

What this schedule does

The expression 0 0 * * 1 fires at midnight every Monday. The five fields mean: minute=0, hour=0, any day of month, any month, day=1 (Monday only). It gives you a reliable weekly window to audit every domain before your work week begins.

SSL certificates that expire unnoticed cause outages, browser warnings, and eroded user trust. A 30-day alert window gives your team enough runway to renew through any CA, including orgs that require manual procurement approval. Running the check weekly — rather than daily — keeps alert noise low while still catching problems early enough to act.

How to check SSL certificates in a script

The standard approach uses openssl s_client to connect to each domain and parse the certificate's notAfter field. The script below checks a list of domains, calculates days remaining, and fires a Slack webhook alert for any cert expiring within 30 days.

#!/usr/bin/env bash
# ssl-check.sh — check SSL expiry for a list of domains
# Alert via Slack if any cert expires within WARN_DAYS days

WARN_DAYS=30
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL}"
DOMAINS=(
  "example.com"
  "api.example.com"
  "app.example.com"
  "status.example.com"
)

alert() {
  local domain="$1"
  local days="$2"
  local msg="*SSL expiry warning*: \`${domain}\` expires in *${days} days*. Renew now."
  curl -s -X POST -H 'Content-type: application/json' \
    --data "{\"text\":\"${msg}\"}" \
    "${SLACK_WEBHOOK}"
}

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 "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_left=$(( (expiry_epoch - now_epoch) / 86400 ))

  echo "${domain}: ${days_left} days remaining"

  if [[ "${days_left}" -le "${WARN_DAYS}" ]]; then
    alert "${domain}" "${days_left}"
  fi
done

Save this as ssl-check.sh, make it executable with chmod +x ssl-check.sh, and add your domains to the DOMAINS array. Set SLACK_WEBHOOK_URL in your environment or a .env file that the script sources. To send email instead, replace the alert() function body with a mail or sendmail call.

Platform snippets

Standard crontab
# crontab -e
# Run SSL check every Monday at midnight, log output
0 0 * * 1    SLACK_WEBHOOK_URL=https://hooks.slack.com/... /opt/scripts/ssl-check.sh >> /var/log/ssl-check.log 2>&1
GitHub Actions
on:
  schedule:
    - cron: '0 0 * * 1'   # UTC midnight every Monday

jobs:
  ssl-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check SSL certificate expiry
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        run: |
          chmod +x scripts/ssl-check.sh
          ./scripts/ssl-check.sh

      - name: Upload check log
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: ssl-check-log
          path: /tmp/ssl-check.log
          retention-days: 30
AWS EventBridge
# EventBridge cron (6 fields — adds year, UTC)
# Targets a Lambda function that runs the ssl-check logic
cron(0 0 ? * MON *)

# Lambda handler (Python)
import ssl, socket, datetime, urllib.request, json, os

DOMAINS = ["example.com", "api.example.com"]
WARN_DAYS = 30

def handler(event, context):
    alerts = []
    for domain in DOMAINS:
        ctx = ssl.create_default_context()
        with socket.create_connection((domain, 443), timeout=10) as sock:
            with ctx.wrap_socket(sock, server_hostname=domain) as ssock:
                cert = ssock.getpeercert()
        expiry = datetime.datetime.strptime(
            cert["notAfter"], "%b %d %H:%M:%S %Y %Z")
        days_left = (expiry - datetime.datetime.utcnow()).days
        if days_left <= WARN_DAYS:
            alerts.append(f"{domain}: {days_left} days remaining")
    if alerts:
        payload = json.dumps({"text": "\n".join(alerts)}).encode()
        urllib.request.urlopen(os.environ["SLACK_WEBHOOK_URL"], payload)
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
  name: ssl-expiry-check
spec:
  schedule: "0 0 * * 1"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: ssl-check
            image: alpine/openssl:latest
            command: ["/bin/sh", "/scripts/ssl-check.sh"]
            env:
            - name: SLACK_WEBHOOK_URL
              valueFrom:
                secretKeyRef:
                  name: ssl-check-secrets
                  key: slack-webhook-url
            volumeMounts:
            - name: scripts
              mountPath: /scripts
          volumes:
          - name: scripts
            configMap:
              name: ssl-check-script
              defaultMode: 0755
          restartPolicy: OnFailure

Timezone note

Standard crontab uses the system timezone of the machine running the job. GitHub Actions and AWS EventBridge schedule in UTC. Midnight UTC on Monday is Sunday evening in US time zones — that is usually fine for a weekly maintenance check. If you want Monday morning in your local timezone, offset accordingly: 0 5 * * 1 fires at midnight US Eastern (UTC-5). Kubernetes 1.27+ supports spec.timeZone: "America/New_York" for human-readable timezone scheduling.

More guides

Database backup Dependency audit Morning brief Weekly metrics report