← All posts
Operations· 7 min read

SSL certificate expiry: a practical guide for small teams

How to check SSL certificate expiry, set useful alert thresholds, and avoid surprise outages — with real commands and no special tools required.

By , Director of Softuvo Solutions (LinkedIn) · Updated

An expired SSL certificate takes your site offline for everyone, instantly, with no warning to users beyond a browser error screen. For small teams running production without a dedicated SRE, it is one of the most avoidable outages there is — and yet it still catches people out every week. This guide explains how SSL certificates expire, how to check them from the command line, what thresholds to alert on, and how to keep a watch across more than a handful of domains without it becoming a manual job.

How SSL certificate expiry actually works

Every certificate is issued with a Not After date baked into it. When a browser or API client connects to your server, it checks that date before anything else. If today is past that date, the handshake fails and the connection is refused. There is no grace period. The certificate does not degrade; it simply stops working.

Let's Encrypt certificates last 90 days. Certificates from commercial CAs typically last one year (since September 2020, browsers cap the maximum at 398 days). Both renew automatically in theory — Let's Encrypt via certbot or acme.sh, commercial certs via your CA's renewal tooling — but automation fails silently more often than teams expect: a cron job that stopped running, a firewall rule that blocks the ACME challenge, a certificate stored outside the renewal path, or a domain that moved to a new server without its cert following.

How to check SSL certificate expiry from the command line

You do not need any special tool. OpenSSL is installed on almost every Linux server, and a single command gives you the full expiry date for any domain reachable from that machine.

# Check expiry date for a domain
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -dates

# Expected output:
# notBefore=Apr  1 00:00:00 2025 GMT
# notAfter=Jun 30 23:59:59 2025 GMT

# Check how many days remain (Linux)
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate \
  | cut -d= -f2 \
  | xargs -I{} date -d {} +%s \
  | xargs -I{} bash -c 'echo $(( ({} - $(date +%s)) / 86400 )) days remaining'

# Check the cert served by nginx or Apache locally (no network needed)
openssl x509 -noout -dates -in /etc/letsencrypt/live/example.com/fullchain.pem

# Quick check: does the cert chain include intermediates?
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -issuer -subject

The -servername flag matters. It sends the SNI header so the server returns the correct certificate when multiple domains share one IP address. Without it, you may get the default certificate rather than the one you are checking.

If you want to check a certificate file on disk rather than a live connection — useful after a renewal before you reload the web server — read it directly with openssl x509 -in as shown above. This confirms the file renewed correctly before nginx or Apache picks it up.

What to alert on: concrete thresholds

Knowing your cert expires in 14 days is useful. Getting that information while you are still asleep is what saves you. These are the thresholds most teams settle on after one incident:

  • 30 days: warning alert. Renewal has not happened yet. Investigate why. For Let's Encrypt this should never trigger unless automation is broken.
  • 14 days: urgent alert. Treat this as a production issue. Manual renewal may be needed.
  • 7 days: critical alert. Page someone. At this point a renewal failure is very likely to cause an outage.
  • 0 days (expired): incident. The cert is already failing for end users.
Rule of thumb: if you are not getting a 30-day alert and your renewal automation is working, the system is healthy. If you do get a 30-day alert, do not dismiss it — that is the automation telling you it failed.

For teams using Let's Encrypt, certbot renews at 30 days by default, so a 30-day alert firing means the renewal run failed or never ran. Check systemctl status certbot.timer and the renewal logs at /var/log/letsencrypt/letsencrypt.log before anything else.

Common reasons a certificate expires even with automation

Automation does not eliminate expiry incidents; it just changes what goes wrong. The failures teams run into most often are:

  • The ACME HTTP-01 challenge is blocked. A firewall rule, CDN setting, or redirect loop prevents Let's Encrypt from reaching /.well-known/acme-challenge/. The renewal runs, fails silently, and no one notices until the cert is two weeks from expiry.
  • The domain moved. A site migrated to a new server or a different web root, but the cert (and the renewal hook) stayed on the old one.
  • Post-renewal reload never ran. certbot renewed the files but the nginx or Apache reload in the deploy-hook failed, so the server is still serving the old expired cert from memory.
  • Wildcard certs and DNS-01 challenges. These require DNS API access. Credentials rotate, IAM policies change, and the renewal quietly fails.
  • Commercial certs managed manually. If someone bought a one-year cert and put a reminder in a calendar, that reminder gets missed or lands on a day when the right person is on leave.

For the reload problem specifically: after any renewal, verify the live cert matches the file. Run the openssl s_client command against the live domain and compare the notAfter date to the file on disk. If they differ, the server has not picked up the new cert yet.

SSL certificate expiry monitoring across multiple domains

Checking one domain with OpenSSL takes seconds. Checking twenty domains reliably, on a schedule, with alerts going to the right person — that is where manual checks break down. If you are evaluating how to handle this across a fleet, SSL certificate expiry monitoring: get an alert before a cert expires covers the monitoring options in detail, including what a good alerting setup looks like for teams without a dedicated ops person.

For teams deciding how to structure their overall monitoring setup, what is the best server monitoring for small teams? puts SSL monitoring in context alongside uptime checks, disk alerts, and service health — which is usually the right way to think about it, since an SSL expiry is one of several things that can silently break production.

If you are running a cron job to check certs, the basic pattern is: for each domain, run the openssl s_client command, extract the days remaining, and send an alert if the number is below your threshold. Store the results somewhere so you have a history. The fragile part is not the check itself but making sure the checker itself keeps running and that alert delivery does not silently break.

How Opservo handles SSL expiry monitoring

Opservo auto-discovers nginx and Apache vhosts on your server and monitors their SSL expiry as part of the standard install. You get alerts at configurable thresholds without writing a cron job or maintaining a separate checker. The same agent also monitors uptime, service health, disk fill forecasts, and open ports, so SSL expiry sits alongside the other things that break production rather than in a separate dashboard. The install is one command and takes about 30 seconds:

curl -sSL https://api.getopservo.com/install.sh | sudo bash

The free plan covers two servers with no card required, which is enough to see whether the auto-discovery picks up your vhosts and what the first SSL findings look like before you commit to anything.

Invite the reader to try Opservo's free two-server plan to see SSL expiry auto-discovered alongside uptime and service health in a single agent install. — one command installs a read-only agent and your first findings arrive in five minutes: start free. Related: blog monitor ssl certificate expiry.