← Blog  ·  October 2026

Check SSL Certificate Expiry with Bash: Scripts and One-Liners

The openssl command you already have, plus scripts for multiple domains, cron setup, and the gaps you'll need to fill.

Checking SSL certificate expiry from the command line is a core sysadmin and DevOps task. The tools are already installed on virtually every Linux and macOS system — you just need the right invocations. This guide covers the essential bash one-liners, multi-domain scripts, and cron job patterns for SSL expiry checking, along with an honest assessment of where shell-script-based monitoring falls short.


Check SSL expiry with openssl s_client

The canonical way to check a live certificate's expiry date from bash:

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

Output:

notAfter=Dec 14 23:59:59 2026 GMT

The -servername flag enables SNI (Server Name Indication), which is required for virtual-hosted servers. Without it, servers with multiple certs on the same IP may return the wrong certificate.

To get the number of days remaining:

expiry=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate | cut -d= -f2)
echo $(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 )) days remaining

On macOS, date -d is not available. Use this instead:

expiry=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate | cut -d= -f2)
echo $(( ($(date -j -f "%b %e %T %Y %Z" "$expiry" +%s) - $(date +%s)) / 86400 )) days remaining

Check SSL expiry with curl

If you prefer curl or need to avoid openssl directly, curl exposes certificate information via the --head and verbose flags:

# Most portable curl approach — prints expiry to stderr
curl -v --head https://example.com 2>&1 | grep "expire date"

Output:

*  expire date: Dec 14 23:59:59 2026 GMT

For scripting, the cleaner curl approach uses -w format strings if your curl version supports it, or pipes through grep:

curl -vI https://example.com 2>&1 \
  | grep -i "expire date\|start date\|subject\|issuer" \
  | head -10
curl vs. openssl for SSL checking curl follows redirects by default (use -L or -I) and respects system CA bundles, making it convenient for quick interactive checks. openssl s_client gives you raw TLS handshake detail and is more reliable in scripts because it doesn't depend on curl version or platform-specific behavior. For scripting multi-domain checks, openssl s_client is the safer choice.

Check SSL expiry for a cert file on disk

If you have the certificate file itself (e.g., from a Kubernetes secret, Certbot renewal, or cert store):

# From a PEM file
openssl x509 -noout -enddate -in /etc/ssl/certs/example.com.crt

# From a DER-encoded file
openssl x509 -inform DER -noout -enddate -in example.com.der

# Full details: subject, issuer, validity period
openssl x509 -noout -text -in /etc/ssl/certs/example.com.crt | grep -A2 "Validity"

Bash script to check multiple domains

A straightforward script for checking a list of domains and printing days remaining for each:

#!/bin/bash
# check-ssl-expiry.sh — check SSL expiry for a list of domains

DOMAINS=(
  "example.com"
  "api.example.com"
  "staging.example.com"
  "mail.example.com"
)

WARN_DAYS=30

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 "FAIL  $domain  (could not retrieve certificate)"
    continue
  fi

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

  if [ "$days" -le 0 ]; then
    echo "EXPIRED  $domain  (expired $((days * -1)) days ago)"
  elif [ "$days" -le "$WARN_DAYS" ]; then
    echo "WARN  $days days  $domain"
  else
    echo "OK    $days days  $domain"
  fi
done

Make it executable and run it:

chmod +x check-ssl-expiry.sh
./check-ssl-expiry.sh

Sample output:

OK    73 days  example.com
WARN  18 days  api.example.com
OK    61 days  staging.example.com
FAIL  mail.example.com  (could not retrieve certificate)

Add email alerts to the SSL check script

Extend the script to send an email when a cert is approaching expiry:

#!/bin/bash
DOMAINS=("example.com" "api.example.com" "staging.example.com")
WARN_DAYS=30
ALERT_EMAIL="ops@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)

  [ -z "$expiry" ] && {
    echo "SSL check failed for $domain" \
      | mail -s "SSL Check Failed: $domain" "$ALERT_EMAIL"
    continue
  }

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

  if [ "$days" -le "$WARN_DAYS" ]; then
    echo "$domain SSL certificate expires in $days days ($expiry)" \
      | mail -s "SSL Expiry Warning: $domain ($days days)" "$ALERT_EMAIL"
  fi
done

Set up a cron job for SSL expiry checking

Run the check daily. Add to crontab with crontab -e:

# Run SSL expiry check daily at 8:00 AM
0 8 * * * /usr/local/bin/check-ssl-expiry.sh >> /var/log/ssl-check.log 2>&1

Or using a more verbose format that logs timestamps:

0 8 * * * /usr/bin/bash -c 'echo "=== $(date) ===" >> /var/log/ssl-check.log && /usr/local/bin/check-ssl-expiry.sh >> /var/log/ssl-check.log 2>&1'

Verify the cron job runs by checking the log after the next scheduled time:

tail -f /var/log/ssl-check.log

The fundamental limitations of bash SSL checking

The scripts above work. They also have failure modes that are difficult to close with more bash:

The script can fail silently

If the cron daemon stops, the server migrates, the disk fills up, or the mail server is unavailable, no alerts fire. More importantly, you get no notification that monitoring stopped. This is the classic monitoring gap: you only find out something was broken when you notice the expired cert, not when the monitoring broke.

The script doesn't monitor itself

A cron-based SSL check has no watchdog. You'd need a second cron job to verify the first one ran, and a third to monitor the second. External monitoring services sidestep this by running from infrastructure you don't control — if your server goes down, they still check your endpoint.

You have to maintain the domain list

When a new domain goes live, someone has to remember to add it to the script. Teams regularly miss domains this way — especially subdomains, staging environments, and third-party services that use your TLS certs.

It checks what you have access to

The script connects from your server. It doesn't check from multiple geographic locations, it doesn't test what users in different regions see, and it can't detect CDN-terminated TLS issues where the CDN is serving an expired cert from its own cache.

Cron job monitoring is also SSL monitoring If the cron job running your SSL check silently stops, you won't know your SSL monitoring is broken until a cert expires. External SSL monitoring avoids this dependency entirely.

Check your domains now

Use the free SSLWatch checker to immediately see how many days remain on any domain's certificate — no bash, no openssl, no local setup required:

Free SSL certificate expiry checker

Enter any domain and see days remaining, expiry date, issuer, and chain status in seconds. No signup required.

Check SSL expiry → Get expiry alerts →

Beyond bash: automated SSL certificate monitoring

SSLWatch automates the SSL monitoring loop that bash scripts implement manually. It checks your HTTPS endpoints on a regular schedule, sends email alerts at 30, 14, 7, and 1 day before expiry, and runs from external infrastructure that isn't affected by your server's health.

No script to maintain. No cron job to watch. No domain list to update manually. The monitoring runs whether your server is healthy or not.

Automated SSL certificate monitoring

SSLWatch monitors your domains with scheduled external checks and sends expiry alerts before the cert becomes a problem.

Join the SSLWatch waitlist → Free expiry checker →