The certbot timer is not running.

certbot.timer inactive, masked, or never installed. No renew runs. The leaf on :443 ages until a browser warns. Prove the timer, enable it, then watch expiry off-box.

Cover Image for The certbot timer is not running.

You issued the cert months ago. certbot certificates still lists it. Nobody has run certbot renew since.

The leaf on :443 is inside the last 30 days. Or it already expired.

There is no failed challenge in today's log. There is no "renewed but nginx ignored it" story. Nothing scheduled the renew. That is this post.

On most Debian/Ubuntu installs, the systemd unit certbot.timer is what calls certbot.service twice a day. If the timer is inactive, masked, or missing, disk and wire both age together until something external notices — which is how letsencrypt auto renew stops without a single error.

Prove it

1. Is the timer even there?

systemctl status certbot.timer
systemctl is-enabled certbot.timer
systemctl list-timers --all | grep -i certbot

What you want:

  • Active: active (waiting) (or a similar "waiting" state)
  • enabled from is-enabled
  • A next trigger time in list-timers

What means renew is not scheduled:

ResultMeaning
Unit certbot.timer could not be foundThe package never installed the unit, or wrong machine
inactive (dead) / disabledThe unit exists; nothing starts it on boot
maskedSomeone blocked it on purpose (or a broken cleanup)
Timer active, but list-timers shows no next runBroken unit or calendar; inspect the unit file
No certbot lines; only a cron you never wroteYou are on cron-based renew (see below)

2. When did renew last run?

# systemd journal for the renew service
sudo journalctl -u certbot.service -n 50 --no-pager

# classic log
sudo tail -n 40 /var/log/letsencrypt/letsencrypt.log

# lineaged cert ages on disk
sudo certbot certificates

If the log's last real renew is weeks or months old, and the timer is dead, you found the gap.

If the log shows recent failed HTTP-01 or DNS-01 attempts, the timer is running. Fix the challenge instead: HTTP-01 died after you forced HTTPS.

If the log shows recent success but browsers still see an old leaf, that is reload, not scheduling: nginx reloaded nothing. Certbot still exited 0.

3. What is on the wire right now?

echo | openssl s_client -connect yourdomain.com:443 \
  -servername yourdomain.com 2>/dev/null \
  | openssl x509 -noout -enddate -subject

Or paste the hostname into the SSL expiry checker. Read days left on the served leaf. That is the clock visitors feel while the timer stays off.

For the full failure map (reload, HTTP-01, rate limit, root mail), use the disk vs wire hub.

Fix now

systemd (Debian / Ubuntu package path)

# if masked, clear that first
sudo systemctl unmask certbot.timer

sudo systemctl enable --now certbot.timer
systemctl status certbot.timer
systemctl list-timers | grep -i certbot

Optional: run one renew immediately so you are not waiting on the next timer tick:

sudo certbot renew --dry-run
# when the dry-run is clean:
sudo certbot renew

Then confirm disk notAfter moved (when the cert was inside the renew window) and reload TLS if your stack needs it — that half is the reload write-up.

Package missing the timer unit

# Debian/Ubuntu
dpkg -L certbot | grep -E 'timer|service' || true
apt-cache policy certbot

If the package is absent or ancient, install or refresh it from your distro docs. Snap-based certbot uses a different scheduler (below). Do not invent a second parallel cron and a timer without knowing which one owns renew. One scheduler is enough.

cron-based installs (older guides, some AMIs)

sudo ls -la /etc/cron.d/ /etc/cron.daily/ 2>/dev/null | grep -i certbot || true
sudo grep -R certbot /etc/cron.* /var/spool/cron 2>/dev/null | head

A healthy cron line usually looks like a twice-daily certbot renew -q (exact flags vary). If the file is missing, commented out, or points at a binary that no longer exists, restore it from your distro's certbot package or from Certbot's current docs for your OS, then:

sudo certbot renew --dry-run

snap certbot

snap list certbot 2>/dev/null || true
systemctl list-timers --all | grep -i snap.certbot || true

snap often installs its own renew timer; the name varies by version. If you migrated from apt to snap (or the reverse), you can end up with no active timer on either side. Pick one install method, remove the orphaned one, enable that method's timer.

Containers / immutable hosts

There is no durable certbot.timer on a container that was only used once to issue. Renew must live on:

  • the host that still has /etc/letsencrypt, or
  • your orchestrator's CronJob / scheduled task, or
  • DNS-01 in CI with a written schedule

If "the box" was replaced and the new image never got a timer, wire will keep the old leaf until someone issues again. Fix scheduling on the long-lived place that holds the lineage, then reload every edge that terminates TLS.

Make it permanent

  1. Alert on timer health, not only on cert expiry: a check that systemctl is-active certbot.timer is active, or that list-timers still shows a next run.
  2. Keep certbot.timer in config management so a "cleanup" playbook cannot leave it disabled.
  3. After OS upgrades, re-run systemctl is-enabled certbot.timer once. Unit renames and package switches do happen.
  4. Prefer off-box expiry on the public hostname, so a dead timer is visible even when root mail never leaves the box.

How you catch it off-box

A dead timer produces silence. No failed challenge. No deploy-hook drama. Local logs look calm if nobody opens them.

  • One-off: SSL expiry checker on the public name. See days left while you fix scheduling.
  • Any port, quick wire read: SSL checker.
  • Ongoing: CertPost watches the served cert (3 free, no card) and emails at 30/14/7/1 days. When the timer dies again after a rebuild, the leaf still ages in public and the alert still fires.

Watch the wire. Put renew back on a clock.