Skip to content

SSL Certificate Expiring

An expiring certificate is one of the few outages you can fix completely before anyone notices. You have days, not minutes. Work through this in order and do not skip step 4 — a renewed certificate that was never loaded is the most common way this alert comes back an hour later.

DownBad reports that the certificate expires in N days, or that it has already expired. Once it does expire, every browser shows a full-page interstitial (NET::ERR_CERT_DATE_INVALID in Chrome), API clients fail their TLS handshake, and anything doing certificate pinning stops dead.

SSL monitors carry two thresholds. The default warning threshold is 30 days and the default critical threshold is 7 days; the check fails inside the critical window, so the alert fires while there is still time to renew rather than at the moment the certificate dies.

  • Auto-renewal broke silently, weeks ago. A disabled systemd timer, a renewal hook that exits non-zero, or an ACME HTTP-01 challenge that a new redirect rule or WAF now intercepts. The renewal has been failing since long before you were alerted.
  • The certificate was renewed but never deployed. The new file is on disk; the running server still holds the old one in memory. Renewal does not reload your server.
  • Renewed on one node out of several. The load balancer still serves the stale certificate from the node you forgot.
  • You are not looking at the certificate you think you are. DownBad sends SNI, so it sees what a browser sees. If your host serves a different certificate without SNI, your own testing may be checking the wrong one.
  • A DNS-01 renewal is failing on an expired API token at your DNS provider. Wildcard certificates fail this way and nothing else on the box notices.
  • The site moved hosts and the new host issued its own certificate for a hostname you are not monitoring.
  1. See what is actually being served, not what is on disk:

    Terminal window
    echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
    | openssl x509 -noout -dates -issuer -subject

    notAfter is the date that matters. Do this for every hostname you serve — apex and www are frequently different certificates.

  2. Renew, using whichever of these matches your stack:

    Terminal window
    sudo certbot renew
    sudo systemctl reload nginx

    If renewal fails, run sudo certbot renew --dry-run and read the error. An HTTP-01 failure almost always means a redirect or a WAF rule is intercepting /.well-known/acme-challenge/.

  3. Reload the server. Renewal writes a file; it does not restart anything. systemctl reload nginx, apachectl graceful, or whatever your stack calls it — and on every node, not just the one you are logged into.

  4. Verify with the same openssl command from step 1. The notAfter date must have moved. If it has not, you renewed a certificate the server is not using.

  5. Re-run the check from the monitor rather than waiting. SSL monitors default to an hourly interval, so the incident will otherwise stay open for up to an hour after you have fixed it.

  • Automate renewal and then verify the automation. certbot renew --dry-run on a schedule catches a broken renewal months before the certificate matters.
  • Put the reload in the renewal hook (--deploy-hook), not in your memory.
  • Keep the warning threshold at 21 days or more. Let’s Encrypt certificates live 90 days and renew at 30; a 7-day warning gives you exactly one weekend to notice.
  • Monitor every hostname your customers actually type, including www, apex, and any vanity or alias domain. They are separate certificates and they fail separately.
  • Alert somewhere a human reads. Certificate expiry is the one outage with a scheduled start time; missing it is a notification problem, not a TLS problem. If the alert never reached you, see Not Receiving Notifications.