How to monitor SSL certificate expiration across multiple domains

What you're building

You need three things:

1. One list of every hostname you're responsible for.

2. A scheduled check that reads each certificate's expiry date and works out the days left.

3. An alert that reaches a person while there's still time to fix the problem.

You can set this up in under an hour with openssl, cron and a Slack webhook or mail command.

Step 1: Build the domain list

Create domains.txt with one hostname per line:

`

client-a.com

www.client-a.com

shop.client-b.co.uk

mail.client-c.com:465

`

Put every hostname that serves HTTPS on the list, not just the main domain:

To find hostnames you've forgotten, search Certificate Transparency logs at crt.sh (for example %.client-a.com). Every publicly trusted certificate gets logged there.

Step 2: The check script

`bash

#!/usr/bin/env bash

while read -r line; do

[ -z "$line" ] && continue

host=${line%%:*}; port=${line##*:}; [ "$port" = "$line" ] && port=443

end=$(echo | timeout 10 openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null \

| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)

if [ -z "$end" ]; then echo "$host ERROR no-certificate-or-unreachable"; continue; fi

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

echo "$host $days $end"

done < "${1:-domains.txt}" | sort -k2 -n

`

Run it with bash check-ssl.sh domains.txt. Example output:

`

old-client.org ERROR no-certificate-or-unreachable

shop.client-b.co.uk 9 Oct 25 08:14:02 2026 GMT

client-a.com 61 Dec 16 10:00:00 2026 GMT

`

Notes:

Step 3: Alert only when something needs action

Keep the lines that are in trouble (errors, or fewer than 21 days left) and send them only if there are any:

`bash

#!/usr/bin/env bash

out=$(bash /opt/ssl/check-ssl.sh /opt/ssl/domains.txt | awk '$2=="ERROR" || $2+0 < 21')

[ -z "$out" ] && exit 0

curl -s -X POST -H 'Content-type: application/json' \

--data "{\"text\": \"SSL attention needed:\n$out\"}" "$SLACK_WEBHOOK_URL"

`

Schedule it every day:

`

0 8 * * * SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX /opt/ssl/alert.sh

`

Choosing the warning window

Auto-renewing certificates usually renew well before they expire. Certbot, for example, renews when about 30 days are left, which is one third of a 90-day certificate's life. So a certificate showing fewer than about 21 days usually means auto-renewal has already failed. That leaves you three weeks to fix it. For certificates you renew by hand, alert earlier, at 30 and 14 days.

Mistakes that cause missed renewals anyway

Why this matters more each year

Under CA/Browser Forum ballot SC-081, the maximum lifetime of public certificates drops to 200 days (from March 15, 2026), then 100 days (2027), then 47 days (2029) (DNSimple). More renewals mean more chances for one to fail without anyone noticing. Let's Encrypt also ended its expiry reminder emails in 2025, so the warning many people relied on no longer arrives.

If you'd rather not run a script

The DIY setup works, but someone has to own the server, the cron job and the list. We're building Lapsewatch for agency ops teams. It will keep one list of client domains with days-to-expiry and send email or Slack alerts before anything lapses, with no server to maintain. It isn't available yet; the page below has the details.

Try it