SYSTEMS OPERATIONAL
VPS

Certbot Renewal Failing on Your VPS: Causes and the Fix

Getwebup 6 min read

Certbot ran fine for months, then one day certbot renew throws an error and three weeks later your site is showing an expired-certificate warning. If you're on a VPS without cPanel — just Nginx or Apache and a cron job or systemd timer doing the renewal — here's how to actually find out why it's failing and fix it before it takes your site down.

Symptom: What a Failed Renewal Looks Like

You usually don't notice the renewal failed until the certificate is already expired, because the failure itself is silent unless you're checking logs. The visible signs show up later:

  • Browsers show NET::ERR_CERT_DATE_INVALID or "Your connection is not private" on a site that was fine yesterday.
  • certbot certificates shows VALID: 5 days or a date already in the past.
  • Running sudo certbot renew --dry-run manually fails with a timeout, a 403, or a connection-refused error.
  • An email from Let's Encrypt landed in your inbox weeks ago warning about upcoming expiry — most people miss it because it goes to the address used at cert creation, not the domain's admin inbox.

Start by confirming the actual state before guessing at causes:

sudo certbot certificates
sudo certbot renew --dry-run

The dry run is the fastest way to reproduce the failure without touching your live certificate, and its error message almost always points straight at the cause.

Cause: The Six Things That Actually Break Renewal

1. Port 80 is blocked or already in use

HTTP-01 validation (the default for certbot's Nginx and Apache plugins, and for standalone mode) needs port 80 reachable from the public internet at the moment of renewal. Three common ways that breaks:

  • UFW or a cloud firewall was tightened after the original cert was issued, and port 80 got left out of the allow list.
  • You're running certbot renew in standalone mode, but Nginx is already bound to port 80, so certbot can't start its own listener.
  • A CDN or reverse proxy in front of the server (Cloudflare in "Proxied" mode, a load balancer) is intercepting the validation request before it reaches your origin.

Check the firewall and what's listening:

sudo ufw status
sudo ss -tlnp | grep :80

2. The Nginx or Apache config has a syntax error

The --nginx and --apache plugins reload the web server as part of renewal to install the new cert. If someone edited a vhost file and left a broken directive, the reload fails, certbot reports the renewal as failed even though the certificate itself may have issued fine, and the old (soon-to-expire) cert stays live. Test the config directly:

sudo nginx -t
sudo apachectl configtest

Fix whatever it flags, then re-run the dry run.

3. The systemd timer or cron job silently stopped running

Certbot installs a systemd timer (certbot.timer) or a cron entry in /etc/cron.d/certbot at install time. Both can go quiet without any obvious signal:

systemctl list-timers | grep certbot
systemctl status certbot.timer

If the timer is inactive or was disabled by a system update, re-enable it:

sudo systemctl enable --now certbot.timer

If you're on the older cron-based install, check /var/log/syslog or /var/log/cron around the scheduled run time for errors, and confirm the file in /etc/cron.d/ still exists — some server hardening scripts strip cron.d entries they don't recognize.

4. DNS-01 validation is failing (wildcard certs)

If you're issuing a wildcard certificate, you're on DNS-01 validation, which needs a TXT record created and propagated before Let's Encrypt checks it. This fails when:

  • You're using manual DNS-01 without a hook script, so certbot renew just hangs waiting for a TXT record nobody added — this is the most common wildcard renewal failure on a VPS.
  • A DNS API plugin (certbot-dns-cloudflare, certbot-dns-route53, etc.) has an expired or revoked API token.
  • TTL on the DNS zone is high enough that propagation doesn't finish inside Let's Encrypt's validation window.

For unattended renewal, wildcard certs need a DNS plugin with a working API credentials file — manual DNS-01 simply cannot renew itself.

5. Rate limits from a previous burst of attempts

Let's Encrypt limits you to 5 duplicate certificates per registered domain per week, and 50 new certs per registered domain per week. If a script or a misconfigured automation retried renewal repeatedly after earlier failures, you can hit this limit and every subsequent attempt fails with a rate-limit error regardless of what caused the original problem. Check the actual error text — it will explicitly say "too many certificates" or "too many failed authorizations" — and wait out the window (it resets on a rolling 7-day basis) rather than retrying immediately.

6. Renewal succeeds but the web server never reloads

Occasionally the certificate itself renews fine, but the deploy hook that's supposed to reload Nginx or Apache doesn't fire — usually because a custom --deploy-hook was set once via the command line and never got saved into the renewal config. Check what's actually configured:

cat /etc/letsencrypt/renewal/yourdomain.com.conf

Look for a renew_hook line. If it's missing, add one so the server reloads automatically after every renewal:

sudo certbot renew --deploy-hook "systemctl reload nginx"

Run that once manually to also write the hook into the renewal config file, so future automated runs pick it up.

Fix: A Clean, Repeatable Renewal Setup

  1. Confirm the timer or cron job is active and actually points at a working certbot binary: which certbot and systemctl status certbot.timer.
  2. Run sudo certbot renew --dry-run --verbose and read the actual error — don't guess, the message tells you which of the six causes above you're dealing with.
  3. Fix the specific blocker: open port 80, fix the config syntax, refresh the DNS API token, or add the missing deploy hook.
  4. Re-run the dry run until it passes cleanly, then force a real renewal to confirm end to end: sudo certbot renew --force-renewal.
  5. Verify the live site: echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -dates.

Prevention

  • Monitor expiry separately from certbot itself — a simple cron job that emails you if a cert has fewer than 14 days left catches failures the renewal system missed.
  • Never rely on manual DNS-01 for anything that needs to renew unattended; move wildcard domains to a DNS plugin with API credentials.
  • After any firewall or reverse-proxy change, immediately run certbot renew --dry-run to catch validation breakage before it becomes a live outage.
  • Keep certbot itself updated — sudo certbot --version and compare against the latest release, since old plugin versions occasionally break against Let's Encrypt API changes.

If you'd rather not babysit cron timers and DNS plugins at all, Getwebup's managed VPS plans handle certificate issuance and renewal as part of the stack, with alerting before anything gets close to expiry.

Frequently asked questions

Why did my SSL certificate expire even though certbot was installed?

Certbot only renews automatically if its systemd timer or cron job is still active and the renewal command actually succeeds. A silent failure - a firewall change, a broken Nginx config, an expired DNS API token - stops renewal without any obvious alert, so the cert quietly expires on schedule.

How do I test a renewal without risking my live certificate?

Run sudo certbot renew --dry-run. It goes through the full validation and issuance process against Let's Encrypt's staging environment, so you see the real error without replacing your live certificate or touching your rate limits.

Why does certbot renew work manually but not from cron or the timer?

Manual runs are usually done as root in a full shell environment. Cron and systemd timers run with a stripped-down PATH and environment, so a plugin or hook script that depends on something in your interactive shell's PATH can fail silently in the scheduled run even though it works when you type the command yourself.

Can I renew a wildcard certificate without manual DNS steps every time?

Yes - install a DNS plugin for your provider, such as certbot-dns-cloudflare or certbot-dns-route53, and give it an API token scoped to DNS edits. That lets certbot create and remove the validation TXT record automatically, which is required for unattended wildcard renewal.

What do I do if I hit a Let's Encrypt rate limit?

Read the exact error - it specifies which limit you hit, usually 'too many certificates' or 'too many failed authorizations'. Fix the underlying cause first, then wait out the rolling 7-day window rather than retrying immediately, since repeated attempts during a rate-limit period don't reset the clock.

#vps #certbot #lets-encrypt #ssl-renewal #nginx #linux

Keep reading

Chat with Support