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.
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
# 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
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
# 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)
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.