certbot Renewals Failing Silently Until Your Cert Expires

Let's Encrypt certs last 90 days and certbot renews via a cron/systemd timer — until something small breaks it. Test renewals before you need them.

What you'll see

Root causes

Port 80 blocked or redirected wrong

HTTP-01 needs /.well-known/acme-challenge reachable on port 80. A redirect loop or firewall breaks it.

Renewal timer disabled or hooks failing

systemd timer masked, or the deploy hook that reloads nginx errors and the new cert never activates.

DNS changed / challenge mismatch

You moved servers or proxied the domain; the challenge now hits the wrong box.

Fix it

  1. Dry-run the renewal to see the real error
    certbot renew --dry-run
  2. Check the timer is enabled and firing
    systemctl list-timers | grep certbot
  3. Switch to DNS-01 if port 80 is awkward
    certbot certonly --preferred-challenges dns -d yourdomain.com
  4. Verify the deploy hook reloads your server
    --deploy-hook 'systemctl reload nginx'

Field note

Behind Cloudflare, use a DNS-01 challenge with an API token: no port-80 exposure, works for wildcards, and survives origin moves.

Common questions

How can I test renewal without waiting for the cron window?

certbot renew --dry-run uses the staging endpoint: it exercises the whole auth path without touching your real quota or certificates. It's the definitive pre-flight test.

Will a failed renewal take my site down immediately?

No — the current certificate keeps serving until it actually expires. Renewal failures warn for days (certbot tries twice daily) before the real deadline, which is why alerting on renewal failures matters more than the failure itself.

Ship it right the first time

Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.

Browse the template store →

One-time. Yours to modify. Instant download from the NinjaOps template store.