Skip to main content
All fixes

SSL, TLS, security headers

Renew SSL certificates before they expire and break HTTPS

Your certificate expires in under 30 days. Automate renewal with certbot, ACM, or Caddy and add monitoring so it never reaches the wire.

What's happening

A certificate expiring within 30 days is a renewal pipeline that has not yet fired or has silently failed. The certificate still works today, but every day brings the expiry closer and the cost of a missed renewal is total — full HTTPS outage, browser interstitials, search-ranking damage, and PCI compliance failure.

Let's Encrypt issues 90-day certificates and recommends renewing at the 60-day mark; certbot's default systemd timer fires twice daily and renews when fewer than 30 days remain. ACM auto-renews managed certificates 60 days before expiry. Manual certificates from commercial CAs are usually 12-month and are the most-likely culprit for missed renewals because nobody owns the calendar entry.

The fix is to automate renewal end-to-end and to add external monitoring that alerts at 21 days before expiry — far enough out to debug a broken ACME challenge or DNS-validation flow without panic.

Why it matters

Certificates that reach expiry cause immediate, total HTTPS outages. Even with the best monitoring, a Friday-night expiry on a small team without paged on-call leaves the site down until Monday.

Browsers, search engines, and client SDKs all reject expired certificates instantly. Recovery takes minutes once the renewal succeeds, but downtime cost can be much higher in revenue and trust.

Compliance frameworks count an expired certificate as a control failure. Re-certification audits ask for evidence of continuous coverage; a one-day outage shows up in change logs and uptime monitors.

Common causes

  • Manual commercial certificate with no calendar reminder for renewal.
  • Certbot installed but the systemd timer never enabled (systemctl enable certbot.timer was skipped).
  • DNS-01 challenge plugin upgraded with a breaking change and silently fails on each renewal attempt.
  • Cloud load balancer pinned to a specific certificate ARN that does not auto-rotate.
  • Renewal runs but the deploy hook (reload nginx) is missing, so the new certificate is on disk but not served.

Detect this on your site

Run a quick scan with the SSL Checker. The tool surfaces this exact issue with the records and context needed to apply the fix below.

Open SSL Checker

How to fix it

  1. 1

    Read the current expiry

    Run openssl s_client -servername example.com -connect example.com:443 /dev/null | openssl x509 -noout -dates. Note the notAfter date. Anything within 30 days is urgent; under 7 days is an emergency.

  2. 2

    Renew via certbot

    On the origin host: sudo certbot renew --force-renewal --deploy-hook "systemctl reload nginx". The deploy-hook ensures the new certificate is loaded automatically. Watch the output for ACME challenge failures and resolve them.

  3. 3

    Renew via cloud provider

    AWS ACM: re-validate any DNS records that need attention, and confirm the certificate enters 'Issued' status with a fresh notAfter. Cloudflare: Edge Certificates panel — toggle off and on to force reissuance. Caddy renews automatically on file change.

  4. 4

    Confirm the new certificate is on the wire

    Re-run the openssl s_client command and read the new notAfter — it should be 60–90 days in the future for Let's Encrypt or 12–13 months for commercial certs. If the wire still shows the old expiry, the web server was not reloaded.

  5. 5

    Enable expiry monitoring

    Add an external probe (UptimeRobot, Better Uptime, Pingdom, CheckFast monitors at /monitors) that alerts at 21 days before expiry. Internal cron logs are not enough; the renewal can silently fail and the certificate still serve until the day it dies.

  6. 6

    Document the renewal pipeline

    Write down which CA, which method (certbot/ACM/Caddy/manual), which deploy hook, and where the alert goes. The next person who touches this should not have to reverse-engineer it.

Example

# Check expiry from the command line
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates

# Force renewal with deploy hook
sudo certbot renew --force-renewal --deploy-hook "systemctl reload nginx"

Inspect the expiry and force a fresh issuance

Frequently asked

Let's Encrypt recommends the 30-day mark; certbot defaults are aligned. For 12-month commercial certificates the 30-day window is also fine. Earlier renewal does not extend the validity window — the new certificate's notAfter is set by the CA from issuance time.

You can — openssl s_client | openssl x509 -checkend $((30*86400)) returns nonzero if the cert expires within 30 days. But external monitoring is essential because the box itself might be the thing that breaks. Use both.

Related fixes