The Cost of Expired Certificates
An expired SSL certificate is one of the most preventable causes of production downtime, yet it remains remarkably common. When a certificate expires, browsers show full-page security warnings that most users cannot bypass. API clients refuse connections with hard TLS errors. Load balancer health checks fail. Revenue stops. All because a date passed unnoticed.
The problem is not that certificate expiry is hard to detect. Every certificate carries its expiration date in plain text. The problem is that expiry dates are easy to forget. Certificates typically last 90 days to one year, and when one expires, the team that installed it has often moved on to other priorities or the original administrator has left the organization entirely.
Automated monitoring eliminates the human memory problem entirely. A script that checks certificate expiry dates and alerts you weeks or months in advance costs almost nothing to set up and runs indefinitely with minimal maintenance. This article covers practical approaches to building that monitoring, from simple command-line checks to scheduled alerting systems.
Checking Certificate Expiry from the Command Line
OpenSSL can retrieve and parse certificate expiry dates directly from any server, making it the foundation of any monitoring solution. The s_client command connects to the server, and x509 extracts the expiry date from the presented certificate.
Retrieving the Expiry Date
Connect to a server with s_client, pipe the certificate to x509, and use the -enddate flag to display only the expiration timestamp.
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate
The output shows a line like "notAfter=Dec 15 12:00:00 2026 GMT." The -servername flag enables SNI, which is essential for servers hosting multiple certificates on the same IP address. Without SNI, you might receive a default certificate that does not match the domain you intend to check.
For a more machine-readable format, use -dates to get both the start and end dates, or combine -enddate with -checkend to test whether the certificate expires within a given number of seconds.
# Check if certificate expires within 30 days (2,592,000 seconds)
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -checkend 2592000
If the certificate expires within 30 days, the command prints "Certificate will expire." If it has more than 30 days remaining, there is no output. This exit-code-free approach is clunky for scripting, so the next section shows a better method.
Parsing the Expiry Date for Scripting
To build reliable monitoring, convert the expiry date to a Unix timestamp and compare it against the current time. This approach gives you an exact number of days remaining and lets you define precise alerting thresholds.
#!/bin/bash
HOST="example.com"
PORT="443"
ENDDATE=$(echo | openssl s_client -servername "$HOST" -connect "$HOST:$PORT" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$ENDDATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "$HOST certificate expires in $DAYS_LEFT days (${ENDDATE})"
if [ $DAYS_LEFT -lt 30 ]; then
echo "WARNING: Certificate expires in less than 30 days"
exit 1
fi
This script works on any Linux system with openssl and GNU date installed. Save it as check-cert.sh, make it executable, and run it manually or from cron. The exit code makes it suitable for integration with monitoring systems like Nagios, Zabbix, or any alerting pipeline that reacts to non-zero exit codes.
Building an Automated Monitoring System
A single manual check is useful for troubleshooting, but automated monitoring is what prevents disasters. The goal is a system that checks all your certificates on a schedule and alerts you with enough lead time to renew before anything expires.
Setting Up a Cron Job
A daily cron job provides frequent enough checks for most organizations. Certificates do not expire overnight without warning — the expiry date is fixed from the moment of issuance. Checking once per day gives you ample warning when a certificate crosses your alert threshold.
# Run certificate check every day at 8 AM
0 8 * * * /usr/local/bin/check-cert.sh example.com 443 | mail -s "Cert Check" admin@example.com
The cron entry runs the check script each morning and emails the output. If the script exits with a non-zero status, the email body contains the warning message, alerting you that a certificate needs attention.
Monitoring Multiple Domains
Most organizations manage certificates for more than one domain. Instead of creating a separate cron entry for each domain, write a wrapper script that iterates over a list of hosts and checks each one.
#!/bin/bash
HOSTS=(
"example.com:443"
"api.example.com:443"
"mail.example.com:993"
"admin.internal:8443"
)
THRESHOLD_DAYS=30
ALERTS=()
for entry in "${HOSTS[@]}"; do
HOST="${entry%:*}"
PORT="${entry#*:}"
ENDDATE=$(echo | openssl s_client -servername "$HOST" -connect "$HOST:$PORT" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
if [ -z "$ENDDATE" ]; then
ALERTS+=("FAILED: Could not connect to $HOST:$PORT")
continue
fi
EXPIRY_EPOCH=$(date -d "$ENDDATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt $THRESHOLD_DAYS ]; then
ALERTS+=("WARNING: $HOST:$PORT expires in $DAYS_LEFT days ($ENDDATE)")
else
echo "OK: $HOST:$PORT — $DAYS_LEFT days remaining"
fi
done
if [ ${#ALERTS[@]} -gt 0 ]; then
printf '%s\n' "${ALERTS[@]}"
exit 1
fi
echo "All certificates have more than $THRESHOLD_DAYS days remaining."
Point a single cron job at this script and it checks every host in the list. The output is silent on success except for the summary line, and only produces alerts when certificates are approaching expiry or when a connection fails entirely.
Adding External Verification
Local openssl checks are reliable, but they test from your own network. A certificate that appears valid from your server might show chain problems or trust store issues when accessed from different geographic regions or network environments.
The OpsCheck SSL Checker tests your certificate from an external vantage point and reports the full chain, expiry date, signature algorithm, and key strength. It catches problems that local openssl checks miss, such as network path issues where a CDN or load balancer serves a different certificate than the one you configured on the origin server.
For a broader security assessment, the OpsCheck TLS Scanner enumerates all TLS versions and cipher suites your server supports, identifies deprecated protocols like TLS 1.0 and 1.1, and flags weak ciphers that should be disabled. Combining certificate expiry monitoring with TLS configuration scanning ensures your endpoints remain both available and secure.
Best Practices for Certificate Lifecycle Management
Monitoring expiry dates is the last line of defense. The first line is a disciplined renewal process. Automate renewal wherever possible. Let's Encrypt and other ACME-based CAs support fully automated issuance and installation through tools like certbot, acme.sh, and lego. If your CA supports ACME, set up automated renewal and treat manual certificate management as a fallback, not the primary workflow.
Document every certificate your organization uses. Maintain a registry that lists each certificate's domain, issuer, expiry date, renewal method, and the person or team responsible for it. This registry doubles as the input for your monitoring script, ensuring that new certificates are added to monitoring at the same time they are deployed.
Set multiple alert thresholds. A single 30-day warning can get buried in email. Configure warnings at 60 days, 30 days, 14 days, and 7 days, escalating through different channels — email at 60 days, Slack or Teams notification at 30 days, and pager alerts at 7 days. Escalating alerts reduce the chance that a critical expiry gets missed among routine notifications.
Test your renewal process. A monitoring system that tells you a certificate is expiring is worthless if the renewal process fails when you need it. Run a renewal dry run at least once per quarter to confirm that your ACME client still has valid credentials, that DNS challenges resolve correctly, and that your deployment pipeline actually installs the renewed certificate.
# Dry-run renewal with certbot
certbot renew --dry-run
Certificate expiry monitoring is not complex, but it requires consistent attention to process. Five minutes spent setting up automated checks and alerting prevents hours of outage response and the reputational damage of serving expired certificates to users and API consumers.