SSL certificate expiry is the most embarrassing and preventable outage in web operations. I've watched it happen to large companies. Here's what to check, when to check it, and how to build a free monitoring setup.
Infrastructure

SSL Certificates Expire Silently. Here's How I Monitor 50 Sites Without Paying for a Service.

Sunny Pal Singh · · 7 min read

SSL certificate expiry is the most embarrassing and preventable outage in web operations. I've watched it happen to large companies. Here's what to check, when to check it, and how to build a free monitoring setup.

SSL Certificates Expire Silently. Here's How I Monitor 50 Sites Without Paying for a Service.

SSL certificate expiry is the most embarrassing and preventable outage in web operations. I've watched it happen to large companies. Here's what to check, when to check it, and how to build a free monitoring setup.

At 2:14 AM on a Tuesday, I got paged for a revenue-critical checkout flow showing a "Your connection is not private" browser error. The cert on our CDN edge layer had expired six hours earlier — at 8 PM, right after the US east coast wound down. The origin cert was fine. Certbot was running. Let's Encrypt had renewed successfully. Nobody had thought to separately monitor the certificate on the Cloudflare custom domain. That outage cost roughly four hours of engineering time and a measurable drop in that night's conversion rate. The fix took 90 seconds once we knew what it was.

That night I built a small monitoring script. Since then I've extended it to cover 50 domains across personal projects, client sites, and internal tooling — all for free. This post documents exactly what I built, why SSL expiry keeps happening even when teams think they've automated it, and how ByteWaveNetwork's SSL Certificate Monitor lets you audit any domain in seconds without installing anything.

Key takeaways

  • Let's Encrypt auto-renewal silently fails more often than teams expect — cron jobs break on server updates, DNS changes, and Docker restarts.
  • CDN and load-balancer certificates are separate from origin certs. Monitoring one does not mean monitoring the other.
  • 90-day certs expire three times faster than annual certs — the renewal window is only 30 days and most alerts fire too late for enterprise change management.
  • A Node.js script + cron + Resend email can monitor 50 domains at zero cost.
  • TLS 1.0 and 1.1 are EOL everywhere that matters. If you're still serving them, you have a compliance problem today.

Why SSL Expiry Keeps Happening Despite "Automation"

Every engineering team I've talked to says the same thing before an expiry incident: "We had that automated." Here are the four reasons automation fails in practice.

a) Certbot timers silently break

Let's Encrypt renewal relies on either a cron job or a systemd timer running certbot renew. When you update the OS, rebuild a container, or migrate to a new server, the timer doesn't follow. There's no email, no alert, no error — the job simply stops running. The cert renews fine right up until the day the old server was last running, then quietly stops.

b) Wildcard certificates don't auto-renew everywhere

Wildcard certs (*.domain.com) require DNS-01 challenge validation. Not every hosting provider or DNS provider supports the Certbot DNS plugin for your registrar. In those cases the renewal is either manual or silently skipped. I've seen a wildcard cert covering 200 subdomains expire because the DNS provider switched APIs and the plugin started returning 403s — with no alerting.

c) CDN/load-balancer certs are a separate surface

This is the scenario I described above. When you serve traffic through Cloudflare, AWS CloudFront, or a load balancer with SSL termination, there are at least two certificate surfaces: the origin cert and the edge cert. Certbot only renews the origin. The CDN cert is managed through its own console, its own renewal mechanism, and its own expiry clock. Teams monitor origin uptime. Nobody monitors the CDN cert until users start reporting browser errors.

d) SaaS custom domains give you no visibility

If you've added a custom domain to a SaaS product — Webflow, Ghost, Shopify, Notion Sites — the cert is issued and managed by the SaaS platform, not you. You can't check it via Certbot. You often can't even see the expiry date in the dashboard. The only reliable way to know is to connect to port 443 and read the certificate directly.

What the ByteWaveNetwork SSL Certificate Monitor Shows You

I built the SSL Certificate Monitor using the same TLS probe approach we use in the Page Speed Inspector — a Node.js tls.connect() call directly to port 443, no third-party libraries, no external APIs. You enter a domain name. The tool returns a complete certificate report within a couple of seconds.

Here's exactly what the output looks like for a typical domain:

Example output — checking example.com

Certificate expiry: 2026-10-14  |  Days remaining: 45 days (amber — renew soon)
Issuer / CA: Let's Encrypt (R11)
TLS version: TLS 1.3
Cipher suite: TLS_AES_128_GCM_SHA256
HSTS: Enabled (max-age=31536000; includeSubDomains)
SANs: example.com, www.example.com
Chain validity: Valid (3 certificates, root trusted)

The colour coding is immediate: green means more than 30 days remaining, amber means 8–30 days (renew now), red means fewer than 7 days or already expired. For most teams, the amber band is the action zone — it's visible enough to act on without triggering a crisis.

The Subject Alternative Names section is particularly useful if you have a wildcard or multi-domain cert — you can see exactly which hostnames are covered and immediately spot if a new subdomain you added is actually missing from the cert.

Comparing SSL Monitoring Options

Tool Cost TLS version check HSTS check SAN visibility Bulk / API No install needed
ByteWaveNetwork SSL Monitor Free ✓ (1.2 / 1.3) Script below
SSL Labs (Qualys) Free ✓ (full grade) Rate-limited API
Uptime Robot (paid tier) $7/mo+
StatusCake (pro) $24/mo+
Screaming Frog SEO Spider £199/yr Partial CLI mode ✗ (desktop app)

SSL Labs gives the most thorough TLS grading — if you need a full cipher suite audit for a compliance report, use Qualys. ByteWaveNetwork's tool is optimised for rapid daily monitoring and at-a-glance expiry status, not a one-time audit. Screaming Frog will crawl HSTS headers but doesn't surface cert expiry dates in a usable way. The paid uptime services alert you to expiry but don't tell you why — they don't show chain validity, TLS version, or SANs.

SSL Certificate Status Reference

Status What it means What to do
Green (>30 days) Certificate is valid and not approaching expiry. No action needed. Keep monitoring weekly.
Amber (8–30 days) Renewal window is open. Cert will expire within a month. Trigger renewal immediately. Don't wait for auto-renew — verify it runs and succeeds.
Red (<7 days) Critical. Browsers may already be showing warnings on some older clients. Escalate to on-call. Renew immediately. Check CDN edge cert separately.
Expired Certificate is past its NotAfter date. All modern browsers block the site. Emergency renewal. Check whether auto-renewal is even configured. Post-mortem required.
Chain invalid Intermediate certificate missing or misconfigured. Chain doesn't build to a trusted root. Reinstall the full certificate chain. Check your server includes the intermediate bundle.
HSTS missing HTTPS is working but the browser is not instructed to enforce it on future requests. Add Strict-Transport-Security header with at least max-age=31536000.

TLS Version Deprecation — Know Where You Stand

TLS version Status Browser EOL PCI DSS Action
TLS 1.0 Deprecated Chrome 84 (Aug 2020), Firefox 78 Non-compliant since Jun 2018 Disable immediately. No exceptions.
TLS 1.1 Deprecated Chrome 84 (Aug 2020), Safari 14 Non-compliant since Jun 2018 Disable immediately. No exceptions.
TLS 1.2 Accepted Still supported Compliant (with approved ciphers) Acceptable short term. Target TLS 1.3 for new deployments.
TLS 1.3 Recommended N/A — current standard Fully compliant Default for all new and upgraded infrastructure.
Non-obvious insight: If your server reports TLS 1.3 in tool output but your CDN terminates TLS before the origin, the TLS version the user experiences is determined by the CDN's configuration — not yours. I've seen teams celebrate moving to TLS 1.3 on origin while their CDN was still negotiating TLS 1.2 because nobody updated the CloudFront security policy. Always probe the CDN hostname, not just the origin IP.

Certificate Authority Comparison

CA Cost Cert lifetime Validation types Best for
Let's Encrypt Free 90 days DV only All web servers with automated renewal. Personal projects, startups, high-volume.
DigiCert $200–$600+/yr 1 year (max 398 days) DV, OV, EV Enterprise, financial services, EV green bar requirements, high-assurance applications.
Sectigo (formerly Comodo) $70–$400/yr 1 year DV, OV, EV Mid-market. Good wildcard pricing. Reseller ecosystem common in managed hosting.
AWS ACM Free (with AWS resources) 13 months (auto-renews) DV Any infrastructure running on AWS. Seamless ELB/CloudFront integration.

For most teams running modern infrastructure, Let's Encrypt plus a solid monitoring setup is the right answer. Where I recommend DigiCert is specifically for OV/EV certificates where the organisation name in the cert itself matters to the business — typically regulated financial or legal services. The cost is the warranty and the assurance process, not the cryptography.

The Free 50-Domain Monitoring Script

Here's exactly what I run. It uses Node.js built-in tls and https modules only — no npm packages. Email goes via Resend's free tier (3,000 emails/month free, which is more than enough for daily cert checks on 50 domains).

// ssl-monitor.js
// Node.js 18+ — no external dependencies except 'resend' for email
// npm install resend

import tls from 'tls';
import { Resend } from 'resend';

const resend = new Resend(process.env.RESEND_API_KEY);

const DOMAINS = [
  'bytewavenetwork.com',
  'yourdomain.com',
  // add up to 50 domains here
];

const ALERT_DAYS = 30;      // amber threshold
const CRITICAL_DAYS = 7;    // red threshold

function checkCert(domain) {
  return new Promise((resolve, reject) => {
    const socket = tls.connect(
      { host: domain, port: 443, servername: domain },
      () => {
        const cert = socket.getPeerCertificate(true);
        const protocol = socket.getProtocol(); // 'TLSv1.3' or 'TLSv1.2'
        const cipher = socket.getCipher();
        socket.destroy();

        if (!cert || !cert.valid_to) {
          return reject(new Error(`No cert returned for ${domain}`));
        }

        const expiry = new Date(cert.valid_to);
        const now = new Date();
        const daysRemaining = Math.floor((expiry - now) / 86400000);

        const sans = cert.subjectaltname
          ? cert.subjectaltname.replace(/DNS:/g, '').split(', ')
          : [];

        resolve({
          domain,
          expiry: expiry.toISOString().split('T')[0],
          daysRemaining,
          issuer: cert.issuer?.O || 'Unknown',
          tlsVersion: protocol,
          cipher: cipher?.name || 'Unknown',
          sans,
          status: daysRemaining < 0
            ? 'EXPIRED'
            : daysRemaining <= CRITICAL_DAYS
              ? 'CRITICAL'
              : daysRemaining <= ALERT_DAYS
                ? 'WARNING'
                : 'OK',
        });
      }
    );

    socket.setTimeout(10000, () => {
      socket.destroy();
      reject(new Error(`Timeout: ${domain}`));
    });

    socket.on('error', reject);
  });
}

async function run() {
  const results = await Promise.allSettled(DOMAINS.map(checkCert));
  const alerts = [];

  for (const result of results) {
    if (result.status === 'fulfilled') {
      const r = result.value;
      console.log(`[${r.status}] ${r.domain} — ${r.daysRemaining}d remaining (${r.tlsVersion})`);
      if (r.status !== 'OK') alerts.push(r);
    } else {
      console.error(`[ERROR] ${result.reason.message}`);
    }
  }

  if (alerts.length > 0) {
    const html = alerts.map(r => `
      <p><strong>${r.domain}</strong> — 
      ${r.daysRemaining < 0 ? 'EXPIRED' : r.daysRemaining + ' days remaining'}<br>
      Expiry: ${r.expiry} | CA: ${r.issuer} | TLS: ${r.tlsVersion}</p>
    `).join('');

    await resend.emails.send({
      from: '[email protected]',
      to: '[email protected]',
      subject: `⚠️ SSL Alert: ${alerts.length} domain(s) need attention`,
      html,
    });

    console.log(`Alert email sent for ${alerts.length} domain(s).`);
  }
}

run();

Schedule it with a daily cron job: 0 8 * * * /usr/bin/node /opt/ssl-monitor/ssl-monitor.js >> /var/log/ssl-monitor.log 2>&1

Cost breakdown for 50 domains: Node.js runtime — free. Resend free tier — 3,000 emails/month, more than sufficient for daily alerts. A small VPS (or even a free-tier AWS t2.micro) runs this comfortably. Total monthly cost: £0.

SSL Certificate Monitoring Checklist

  • Check the origin cert expiry date and days remaining.
  • Check the CDN/edge cert expiry separately — this is a different cert from your origin.
  • Verify TLS version is 1.2 or 1.3. Disable TLS 1.0 and 1.1 if still enabled.
  • Check HSTS header is present with at least max-age=31536000.
  • Verify the certificate chain is complete — intermediate certs must be included in your server config.
  • Check SANs cover all hostnames you're serving — including both apex and www, and any new subdomains.
  • Confirm auto-renewal is actually running — check systemd timer status (systemctl status certbot.timer) or cron log output.
  • Check any SaaS custom domains (Webflow, Shopify, Ghost) — these are not covered by your server's Certbot.
  • Set renewal threshold to 30 days remaining — not 14. Enterprise change management needs the runway.
  • After any DNS change or server migration, re-verify all certs from scratch — assume nothing carried over.
  • Run a quick check right now on your most critical domain at /tools/ssl-checker/.

Frequently Asked Questions

Why does Let's Encrypt use 90-day certificates instead of one year?

Let's Encrypt deliberately chose 90-day lifetimes to encourage automation and to limit the damage window if a private key is compromised. A stolen key from a one-year cert is exploitable for up to a year; a 90-day cert caps that exposure. The trade-off is that teams who aren't fully automating renewal will hit expiry three to four times per year instead of once. The 90-day design assumes your renewal is automated — if it isn't, you'll feel the shorter lifespan acutely.

What's the difference between a certificate expiring and a certificate being revoked?

Expiry is a scheduled, predictable event based on the NotAfter date embedded in the certificate at issuance. Revocation is an out-of-band action — the CA marks the cert as untrusted before its expiry date, usually because the private key was compromised or the domain ownership changed. Revocation is checked via OCSP or CRL. Most monitoring tools, including the script above, check expiry but not revocation status in real time. If you need revocation monitoring, use Qualys SSL Labs or a dedicated PKI management platform.

How far in advance should I renew my SSL certificate?

The technically correct answer is 30 days before expiry, which is also the window at which Let's Encrypt will accept a renewal request. The operationally correct answer for enterprise teams is earlier — at 45 days if you work in an environment with formal change management, ticket queues, or approval processes. Alerting at 14 days feels early until you factor in a two-week change freeze, a public holiday, or an on-call rotation gap. I alert at 30 days and treat anything under 14 days as a P1 incident.

Can I use this SSL checker to test certificates on non-standard ports?

The ByteWaveNetwork SSL Certificate Monitor probes port 443 by default, which covers the vast majority of public HTTPS traffic. The underlying Node.js tls.connect() approach in the monitoring script can be pointed at any port — useful if you're running HTTPS APIs on port 8443, SMTPS on 465, or LDAPS on 636. For public-facing sites, port 443 is always the right starting point, but the self-hosted script can be adapted by changing the port parameter in the tls.connect() call.

Check your SSL certificate right now — free, no sign-up required

Enter any domain and get back expiry date, days remaining, issuer, TLS version, HSTS status, SANs, and chain validity in seconds. Green, amber, and red colour coding makes the status immediately clear.

Open the SSL Certificate Monitor →

No account needed. No rate limits for single-domain checks. Built on the same TLS probe engine as ByteWaveNetwork's Page Speed Inspector.

Transparency disclosure: ByteWaveNetwork may earn a commission from affiliate links in this post, including links to third-party services mentioned for comparison purposes. These do not affect editorial recommendations — tools are evaluated on technical merit. The Node.js monitoring script and all ByteWaveNetwork tools referenced are free to use without any affiliate relationship.

SP

Sunny Pal Singh

Fellow · Technical Director — AI Infrastructure, Cloud Orchestration & Network Automation

Sunny is a Fellow and Technical Director specialising in AI infrastructure, cloud orchestration, and network automation. With hands-on depth across AWS, Azure, GCP, Red Hat OpenStack, and OpenShift, he leads high-performing teams of architects and engineers building transformative solutions at scale. He built ByteWaveNetwork to bring the same engineering rigour to everyday web tooling.

Choose design