Certbot Auto Renewal: Why Small Teams Need a Dry Run
By Nick Phillips, Founder
Certbot Auto Renewal: Why Small Teams Need a Dry Run

Certbot auto-renews through certbot renew, and most installs already have a timer or cron job doing this twice a day. If yours doesn’t, schedule certbot renew with a randomized sleep offset or set up a systemd timer, then confirm it actually works with certbot renew --dry-run. Success means the schedule runs on its own, the dry run passes, and you have an alert in place if either one ever stops being true.
TL;DR:
- Before creating a job, check systemd timers and root crontabs; snap installations often already include a timer, and duplicates create confusing log noise.
- Renewal becomes eligible with less than one third of a certificate’s lifetime left, about the final 30 days for a standard 90 day certificate.
- Deploy hooks run only after renewal, while standalone validation needs pre and post hooks to stop and restart the server around port 80 checks.
- Run Certbot’s dry run against staging to avoid changing production certificates, then inspect logs and confirm the site serves HTTPS after any reload.
- Set independent daily expiry alerts at 30, 14, 7, and 3 days, and verify HTTPS after deployment because Certbot logs cannot detect every failed reload.
Table of Contents
- How to check whether automatic renewal is already configured
- How to set up scheduled renewal: cron and systemd examples
- Using hooks to reload servers and automate deployment steps
- How to test renewal without touching production certificates
- Common automated renewal failure modes and quick fixes
- Adding expiry alerts so a failed renewal doesn’t go unnoticed
- An operations view from running renewal on small teams
- Otterwatch: a calm backstop for certificate expiry and uptime
- FAQ
- Sources
How to check whether automatic renewal is already configured
Before you add a new job, find out if one is already running. Duplicate renewal tasks waste resources and can cause confusing log noise when two jobs touch the same certificate within minutes of each other.
Start with systemd, since most modern Linux distributions package Certbot with a timer rather than a cron entry:
- Run
systemctl list-timers | grep certbotto see whether a Certbot timer is active and when it last fired. - Run
systemctl status certbot.timerfor details on the unit, including whether it’s enabled on boot. - Check
crontab -las root and look inside/etc/cron.d/,/etc/cron.daily/, and/etc/crontabfor any line mentioningcertbot renew.
If you installed Certbot through snap, which the Certbot instructions recommend for most platforms, a systemd timer is typically installed automatically the moment you install the snap package. You usually don’t need to add anything yourself in that case: just confirm the timer exists and move on.
Watch for leftovers from certbot-auto, the older shell-script installer. According to the Certbot manual pages, certbot-auto is deprecated and no longer supported, so any cron job or systemd unit that still calls it should be removed or rewritten to call the current certbot binary instead. Grep your crontabs and /etc/cron.d/ for certbot-auto specifically, since it’s easy to forget a job set up years ago on a server that’s since been handed off or forgotten about.
If you find nothing anywhere, that’s your answer: you need to build the schedule yourself, which is what the next section walks through.
How to set up scheduled renewal: cron and systemd examples
Certbot’s own guidance recommends running certbot renew twice a day with a randomized offset, a pattern baked into the Certbot instructions for a reason: if every Certbot install on the internet tried to renew at exactly midnight, Let’s Encrypt’s infrastructure would take a predictable, synchronized hit twice a day. A random sleep spreads that load out, and it costs you nothing since certbot renew only acts on certificates that are actually close to expiring.
That “close to expiring” threshold matters for scheduling. Under Certbot’s current renewal logic, a certificate becomes eligible for renewal once less than one third of its total lifetime remains, not a fixed 30-day window as in earlier versions. For a standard 90-day Let’s Encrypt certificate, that works out to roughly the last 30 days anyway, but the logic now scales automatically if you ever request a shorter or longer lived certificate. Because renew skips anything that isn’t due, running it twice daily is both safe and the documented recommendation.

Cron approach. Add a line like this to /etc/crontab:
0 0,12 * * * root sleep $((RANDOM \% 3600)) && certbot renew -q
- The
0 0,12 * * *schedule fires at midnight and noon. sleep $((RANDOM % 3600))adds up to an hour of random delay so your server isn’t hitting Let’s Encrypt at the exact top of the hour alongside everyone else.- The
-qflag keeps routine runs quiet in your mail logs, while still writing full detail to/var/log/letsencrypt.
Systemd approach. If your distribution favors timers, create /etc/systemd/system/certbot-renew.service:
[Unit]
Description=Certbot renewal
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
Then /etc/systemd/system/certbot-renew.timer:
[Unit]
Description=Twice daily renewal check with randomized delay
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true
[Timer]
WantedBy=timers.target
Enable and start it with systemctl enable --now certbot-renew.timer. The RandomizedDelaySec setting does the same job as the cron sleep, and Persistent=true means a missed run (say, the server was off at noon) catches up as soon as the machine is back online.
If you installed via snap or your distribution’s package manager, check first: a timer unit matching this pattern is frequently already there, and adding a second one just duplicates the work.
Pro Tip: Keep the cron or timer job calling plain certbot renew with no extra flags beyond -q. Certbot reuses each certificate’s original plugin options automatically, so there’s rarely a reason to repeat them in the scheduled command.
Using hooks to reload servers and automate deployment steps
A renewed certificate does nothing for your visitors until the service using it reloads. Certbot gives you three hook types for exactly this, and which one fires when matters:
--pre-hookruns before Certbot attempts renewal, useful for stopping a service that’s bound to port 80.--post-hookruns after the renewal attempt completes, regardless of whether it succeeded, so it’s the right place to restart something you stopped in a pre-hook.--deploy-hookruns only after a certificate is actually renewed and installed, never on a no-op run, making it the correct choice for reload commands you don’t want firing twice a day for nothing.
For most setups, directory-based hooks beat command-line flags. Drop executable scripts into /etc/letsencrypt/renewal-hooks/pre/, /etc/letsencrypt/renewal-hooks/deploy/, or /etc/letsencrypt/renewal-hooks/post/, and according to the Certbot user guide, they’ll run automatically for any relevant Certbot subcommand, in alphabetical order, with no fragile shell quoting to maintain in a crontab line.
A typical nginx reload script in renewal-hooks/deploy/reload-nginx.sh:
#!/bin/bash
systemctl reload nginx
For the standalone plugin, which needs port 80 free to complete its own validation, stop the webserver in a pre-hook and bring it back in a post-hook rather than a deploy-hook, since a post-hook fires even when nothing actually renewed.
Two things that trip people up: the script needs chmod +x or Certbot will silently skip it, and since Certbot 2.7.0 your post hooks receive RENEWED_DOMAINS and FAILED_DOMAINS as environment variables, letting a script branch on exactly which domains succeeded or failed instead of guessing.
Pro Tip: Log hook output to its own file (>> /var/log/certbot-hooks.log 2>&1 at the end of the script) so a failed reload doesn’t get buried in the middle of Certbot’s own renewal log.
How to test renewal without touching production certificates
Testing before you trust a schedule saves you from discovering a broken hook three weeks before an expiry, not three days after one.
- Run
certbot renew --dry-run. This simulates the entire renewal flow, including hooks, against Let’s Encrypt’s staging environment, so nothing in production changes and you don’t risk tripping a rate limit. - Check
/var/log/letsencrypt/letsencrypt.logright after for a clean exit and confirmation that any hooks ran. On systemd, pair this withjournalctl -u certbot-renew.serviceto see the timer’s own run history. - If you need to confirm the full production path including a real certificate swap,
certbot renew --force-renewaldoes that for one domain. Use it sparingly: forcing renewal on every certificate every day burns into the same rate limits that twice daily scheduled checks are designed to respect.
A test counts as passing when you can confirm three things: the log shows a successful renewal (or a clean “not yet due” skip), any deploy or post hook exited without an error, and the site answers correctly over HTTPS afterward. Skipping that last check is how people discover, days later, that nginx never actually reloaded.
Common automated renewal failure modes and quick fixes
Most renewal failures trace back to a small set of causes, and almost all of them show up clearly in the logs if you know where to look.
- Port 80 blocked. HTTP-01 validation needs port 80 reachable from the internet; a firewall rule or another service already bound to that port is the most common single cause of a failed renewal.
- Manual plugin certificates. A certificate issued with
--manualand no authenticator hook won’t renew unattended at all. Convert it to the webroot or nginx plugin, or add a manual auth hook, if you want it to auto-renew. - Hook script failures. A non-executable script, a permissions error, or SELinux and snap confinement blocking a script from reaching outside its sandbox will all cause a hook to fail silently unless you’re checking exit codes.
- Rate limit worries. Running
certbot renewtwice daily is explicitly safe because it skips anything not due; the real risk is forcing renewal on every certificate on a daily basis, which eats into Let’s Encrypt’s rate limits for no benefit.
A certificate becomes renewal-eligible once less than one third of its total lifetime remains, which for a 90-day certificate works out to about the last 30 days, the window where most of these failures actually surface since that’s when renewal is first attempted.
When something does fail, certbot renew writes a clear line to /var/log/letsencrypt/letsencrypt.log naming the domain and the reason, and the command’s own exit code (nonzero on failure) is what you want your monitoring or cron error handling to watch for rather than parsing log text.
Adding expiry alerts so a failed renewal doesn’t go unnoticed
Automation fails quietly more often than it fails loudly. A full disk, a changed firewall rule, a renamed webroot path: any of these can break renewal weeks before anyone notices, and the first sign is often a browser warning a visitor sees before you do.
- Run an independent expiry check daily, separate from whatever Certbot itself logs, so a broken cron job doesn’t also break your only source of truth.
- Stage alerts at multiple thresholds (30, 14, 7, and 3 days out) rather than one single warning, so a missed first alert still leaves room to fix things.
- After a deploy hook fires, add a quick reachability check confirming the site actually answers over HTTPS with the new certificate, not just that Certbot logged success.
- Wire a simple check into your deploy hook itself, hitting your own site or an external checker right after reload, so renewal and verification happen in the same closed loop.
We’ve written more on this pattern, including specific checks that catch failures small teams miss before they become a weekend incident.
Pro Tip: A monitor that checks expiry daily and alerts at several thresholds costs you nothing in setup time compared to the hours lost explaining a lapsed certificate to a client.
An operations view from running renewal on small teams
Small teams don’t need to elaborate renewal pipelines, they need a schedule that runs without supervision and a test that proves it works. The twice daily cron or timer pattern above, paired with a --dry-run check after any server change, covers the vast majority of setups we’ve seen break.
What tends to go wrong isn’t the renewal command itself, it’s the silent failure nobody notices until a visitor complains. We’ve covered the specific failure patterns and how to catch them in more detail, along with the upcoming 200 day certificate lifetime change and what it means for renewal scheduling going forward.
— Nick Phillips
Otterwatch: a calm backstop for certificate expiry and uptime
Certbot handles the renewal itself well once it’s scheduled and tested, but it has no way to tell you if its own automation quietly breaks. That’s the gap we built Otterwatch to close: an independent check that watches your certificates and your site’s reachability from the outside, so a failed renewal shows up as a plain email instead of a surprise.

- We check certificate expiry and site reachability on a schedule separate from anything running on your own server.
- Alerts arrive as a plain, friendly heads up, not a dashboard full of graphs or alarm colors.
- The Free plan covers five sites with no credit card required, and Pro offers expanded monitoring and additional features for teams.
If you just want to see where a certificate currently stands, our Free SSL Certificate Checker gives you an instant read with no signup. For anything you want watched continuously after your renewal job runs, the Otterwatch pricing page has the details on getting monitoring set up today.
FAQ
Can Certbot automatically renew a specific certificate?
Yes. Running certbot renew --cert-name yourdomain.com renews only that one certificate using its saved configuration, which is useful for testing a single domain without touching the rest of your fleet. For scheduled automation, though, plain certbot renew with no name filter is the recommended approach since it automatically skips anything not yet due.
How can I automatically renew my SSL certificate?
Schedule certbot renew to run twice daily with a randomized delay, using either a cron entry or a systemd timer, since the command only acts on certificates nearing expiry. Confirm it’s working with certbot renew --dry-run and check /var/log/letsencrypt afterward for a clean result.
How do I force Certbot to renew?
Use certbot renew --force-renewal to renew regardless of how much lifetime remains on the certificate. Reserve this for one-off testing or an urgent fix, since forcing renewal on a regular schedule works against the rate limit exemptions that come from renewing only when actually due.
How often does Let’s Encrypt renew certificates?
Let’s Encrypt doesn’t renew certificates itself; your client renews when a certificate nears the end of its lifetime. Under Certbot’s current logic, that point is less than one third of the certificate’s total lifetime remaining, which for a standard 90-day certificate lands around the 30-day mark.
What should I check if renewal fails silently?
Look at /var/log/letsencrypt/letsencrypt.log for the exact domain and reason first, since Certbot records specific errors there rather than failing without explanation. Port 80 availability, hook script permissions, and whether the certificate was originally issued with the --manual plugin are the three most common culprits worth checking, and our guide to catching renewal failures early walks through the commands for each.
Sources
For the full command reference and hook behavior, the Certbot user guide and Certbot manual pages cover edge cases beyond this guide. Let’s Encrypt’s own ARI integration guide explains renewal windows and rate limit exemptions in more depth, and for firewall or port issues blocking HTTP-01 validation, AceRDP’s VPS hardening checklist is a useful companion reference.
- Certbot user guide (readthedocs)
- Guide to integrating ARI into ACME clients (Let’s Encrypt)
- certbot(7) — Arch manual pages
Recommended
- 2 Checks to Prevent Let’s Encrypt Expiry for Small Teams
- Catch Renewal Failure Alerts Before Expiry: Commands for Small Teams
- Why your Let’s Encrypt auto-renewal silently failed (and how to catch it)
- Confirm Nginx Certificate Reloads for Small Teams, Free for 5 Sites
Catch the next cert expiry before your users do.
Otterwatch checks your SSL certificates daily and emails you 30 days before they expire. Five sites free.
Start watching →