
September 12, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
You wake up to a Slack ping, then a client email: the site shows a red browser warning. The root cause is almost always the same — an expired TLS certificate nobody tracked. To monitor certificate expiry before it bites, you need automated checks, threshold-based alerts, and a renewal path that does not depend on someone's calendar. On production stacks I maintain — Laravel apps on Ubuntu with Apache and PHP-FPM, plus SSL/TLS certificate fundamentals baked into every deploy — expiry monitoring is as basic as disk-space checks. This guide covers the checks, scripts, and alert thresholds that actually work in 2026.
Why do TLS certificates expire and take production sites offline?
Public CAs issue certificates with fixed lifetimes. Let's Encrypt currently caps at 90 days. Commercial CAs often offer 397 days or less under browser policy. Expiry is intentional — it limits compromise windows and forces key rotation.
Outages happen when renewal automation breaks and nobody notices. Common failure modes include stale cron paths after a Deployer symlink swap, Certbot hitting rate limits, DNS validation records removed early, or a wildcard cert renewed on one server but not another behind a load balancer.
I've seen this on sister legal-tech sites sharing the same GitLab CI pipeline. One server renewed fine. A second node still served the old cert. Users on that node got hard browser blocks. No revenue, no lead forms, no trust.
Expired certificates break more than browsers. Payment gateway callbacks over HTTPS fail silently. Mobile apps with certificate pinning reject connections outright. Webhooks from Stripe, Khalti, or eSewa never arrive. Your certificate lifecycle management plan must cover every hostname — apex, www, API subdomain, staging, and admin panels.
How do you check certificate expiry from the command line on Linux?
Before you automate anything, confirm what the world actually sees. Local file dates lie. Always check the live endpoint.
OpenSSL one-liner for any hostname
This returns the notAfter date from the certificate the server presents on port 443:
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer Expected output includes notAfter=.... Parse it in a script to compute days remaining. The OpenSSL s_client documentation covers SNI flags you need when multiple vhosts share one IP.
Certbot inventory on Ubuntu
If you use Let's Encrypt via Certbot, list every managed cert on the box:
sudo certbot certificates Compare Certbot's expiry column against the live OpenSSL check. They should match. When they do not, Apache or Nginx may still point at an old fullchain path. I've hit this after manual edits to vhost files on Ubuntu SSL installs.
Batch check from a host list
Keep a plain-text file of hostnames — one per line — and loop:
#!/usr/bin/env bash
THRESHOLD=30
while read -r host; do
[[ -z "$host" || "$host" =~ ^# ]] && continue
enddate=$(echo | openssl s_client -servername "$host" -connect "${host}:443" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$enddate" +%s)
now_epoch=$(date +%s)
days=$(( (expiry_epoch - now_epoch) / 86400 ))
if (( days < THRESHOLD )); then
echo "WARN: $host expires in $days days ($enddate)"
fi
done < /etc/ssl-monitored-hosts.txt Run it daily from cron. Pipe warnings to email, Slack, or your incident channel. Store the host list in version control alongside your Linux server administration runbooks.
What tools automatically monitor TLS certificate expiration?
Shell scripts work for five to twenty hostnames. Beyond that, dedicated monitoring pays for itself. Pick based on what you already run.
| Tool | Best for | How it checks | Alert channels |
|---|---|---|---|
| Cron + OpenSSL script | Small VPS fleets, solo devs | Live TLS handshake per host | Email, webhook, exit code |
| Certbot + systemd timer | Single-server Let's Encrypt | Local cert files + renew hook | Certbot mail plugin, logs |
| Prometheus Blackbox Exporter | Kubernetes, multi-node stacks | Probe module over HTTPS | Alertmanager → PagerDuty, Slack |
| Uptime Kuma / Healthchecks.io | Agencies, client sites | External HTTPS monitor | Telegram, Discord, email |
| Cloudflare / CDN dashboard | Proxied domains | Edge cert + origin cert | Built-in email alerts |
External monitors matter even when you have internal checks. They catch DNS mispointing, partial load-balancer drift, and CDN origin mismatches that local Certbot never sees.
For Prometheus, the blackbox exporter's probe_ssl_earliest_cert_expiry metric gives you seconds-until-expiry per target. Alertmanager rules at 30, 14, and 7 days mirror the timeline above. Pair this with application-level monitoring — similar to how Laravel Horizon monitors queues — so infrastructure alerts live in one place.
How do you set up alerts before certificates expire?
Alerts fail when they fire too late or too often. Use tiered thresholds and separate warning from critical.
- 30 days — informational. Create a ticket. Verify auto-renewal is enabled. Check DNS validation records for wildcard certs.
- 14 days — action required. Run a manual dry-run renewal. Inspect Certbot logs at
/var/log/letsencrypt/. - 7 days — critical. Page whoever can run
certbot renewand reload PHP-FPM plus Apache. Block other deploys until HTTPS is green. - 1 day — all hands. Every hour, re-probe from an external monitor until notAfter moves forward.
Certbot renew dry-run
Test renewal without changing live certs:
sudo certbot renew --dry-run If dry-run fails, fix it immediately. Common causes include port 80 blocked by UFW, missing webroot path after a Deployer release swap, or expired API credentials for DNS plugins. The Certbot renewal documentation lists plugin-specific requirements.
Cron versus systemd timer
Certbot on Ubuntu installs a systemd timer by default. Verify it is active:
systemctl list-timers | grep certbot After migrating deploy paths, confirm cron and timers still reference the current symlinked current/ release — not a retired directory. Stale paths are the top reason auto-renewal silently stops on zero-downtime setups I maintain with Deployer 7.
Webhook alert example
Send a Slack-compatible webhook when days remaining drop below threshold:
curl -s -X POST "$SLACK_WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"TLS WARN: ${host} expires in ${days} days\"}" Include the hostname, days left, issuer, and a link to your renewal runbook. Vague alerts get ignored. Precise ones get fixed.
Sites like Notary Nepal and Court Marriage In Nepal depend on HTTPS for lead capture and document uploads. A cert outage there is not a minor ops ticket — it stops client intake entirely.
What certificates beyond public HTTPS do you need to track?
Teams obsess over the main www cert and forget the rest. A complete inventory includes every trust anchor your stack presents or validates.
- Apex and www — browser-facing site cert, often Let's Encrypt or commercial CA.
- API subdomains —
api.example.comfor mobile apps and SPA frontends. - Staging and preview — easy to skip; still indexed if robots.txt fails.
- Origin certs behind CDNs — Cloudflare origin certificates expire too.
- Internal mTLS certs — microservice meshes, database TLS, Redis TLS.
- Client certificate auth — B2B APIs where partners present their own certs.
- Code-signing and document signing — see Nepal digital signature certificates for web apps for local PKI context.
Parse the full chain when probing. An expired intermediate in the certificate chain of trust breaks older Android clients even when the leaf cert is valid. Use openssl s_client -showcerts and inspect each link.
How do you automate renewal without hiding failures?
Auto-renewal is necessary but not sufficient. Renewal can succeed on disk while the running web server still serves the old PEM files. Always reload after renew and verify from outside.
Deploy hook pattern for Certbot
#!/bin/bash
certbot renew --quiet --deploy-hook "systemctl reload apache2 && systemctl reload php8.4-fpm" Adjust service names for your PHP version. On PHP 8.3 or 8.4 stacks — common on Laravel 12 and 13 hosts — FPM reload clears opcache tied to stale HTTPS upstream configs.
For deeper automation, read automate certificate rotation and anatomy of an X.509 certificate so your hooks rotate fullchain, privkey, and any intermediate bundles together.
Multi-server and zero-downtime deploys
When two or more nodes sit behind a load balancer, renew on each node or terminate TLS at the edge with a shared cert store. Never assume rsync happened. After Deployer 7 symlink swaps on shared EC2 infrastructure, I verify cert paths inside the active current release and run an external probe per node IP.
WordPress and WooCommerce shops — like florist sites in our Petals Qatar portfolio — often mix CDN SSL with origin certs. Monitor both layers. A valid edge cert does not fix a broken origin handshake during cache miss.
What should your certificate expiry runbook include?
A runbook turns a 2 a.m. alert into a fifteen-minute fix instead of a multi-hour outage.
- Identify the hostname and which server terminates TLS.
- Run the OpenSSL probe and
certbot certificateson that host. - If expired, run
certbot certonlyorcertbot renew --force-renewalafter fixing the root cause. - Reload Apache or Nginx plus PHP-FPM.
- Re-probe from an external monitor and confirm notAfter moved forward at least 60 days for Let's Encrypt.
- Document the failure — stale cron, DNS, firewall — in your post-incident log.
Store CA account emails and API tokens in your secrets manager. Losing access to the Let's Encrypt account email slows recovery during rate-limit windows. For internal CA setups, see run an internal certificate authority with step-ca and track those certs on the same schedule.
Revocation checks belong in security audits, not daily expiry cron. Understand CRL vs OCSP revocation separately from expiration monitoring. Pinning adds another layer — review certificate pinning pros and cons before mobile apps hard-code SPKI hashes.
Fold cert checks into broader testing and optimization and support and maintenance contracts. Clients paying Rs 8,000–15,000/month (~USD 60–110) for maintenance expect HTTPS to stay invisible. Expiry monitoring is part of that promise.
When planning new sites, bake monitoring into launch checklists alongside essential pre-launch website steps. Generate strong staging credentials with a password generator — but never confuse secret rotation with cert rotation. They share a schedule cadence, not the same tooling.
Key Takeaways
- Probe live HTTPS endpoints daily — file mtimes and Certbot rows are not enough on their own.
- Alert at 30, 14, and 7 days; page on-call at seven days for production domains.
- Run
certbot renew --dry-runmonthly and fix failures before they block real renewal. - Reload web server and PHP-FPM after every renew, then verify with an external check.
- Track every hostname — API, staging, origin, internal mTLS — in a version-controlled inventory.
- On multi-node fleets, probe each node individually; load balancers hide partial expiry.
People Also Ask
How many days before expiry should I alert on SSL certificates?
Start at 30 days for visibility, escalate at 14 days, and treat seven days as critical for production. Let's Encrypt's 90-day lifetime means 30 days is roughly one-third through the cert — enough time to fix DNS, firewall, or plugin issues without panic.
Does Certbot automatically renew Let's Encrypt certificates?
Certbot installs a scheduled renew job on most Linux systems, but it only works when port 80 or DNS validation remains reachable and vhost paths stay correct. Monitor the outcome, not the assumption — verify with certbot renew --dry-run and external HTTPS probes.
Can an expired certificate affect SEO and Google indexing?
Yes. Crawlers that hit HTTPS URLs receive connection errors or certificate warnings. Indexing stalls, Search Console reports spike, and users bounce. Technical SEO work — covered in our search engine optimization service — includes cert uptime as a baseline requirement, not an optional extra.
What is the fastest way to check all domains on a server?
Run certbot certificates for the Let's Encrypt inventory, then loop your full hostname list with the OpenSSL s_client one-liner. Compare both outputs. Any mismatch means the active vhost is not serving the cert you think it is.
Build certificate monitoring into your ops stack before the outage
TLS expiry is predictable. Every outage is a process failure, not a surprise. Monitor certificate expiry before it bites by combining daily probes, tiered alerts, tested renewal, and post-reload verification across every node and subdomain. Whether you run a Laravel booking platform, a WooCommerce shop, or a legal-tech portal, the pattern is identical — know the date, automate the renew, prove it live.
Need help wiring this into your Deployer pipeline, Ubuntu fleet, or client maintenance plan? Review our domain registration and hosting and Mijar Law Associates portfolio for examples of production HTTPS done properly, then contact us to audit your certificate inventory before the next expiry window closes.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

