Skip to content

Certbot Renewal Fails With "Account Does Not Exist": A Stale ACMEv1 Account ID After Migration

🛑 Problem

certbot.service starts failing on a schedule (typically the ~12-hourly systemd timer), with an error like:

Failed to renew certificate <domain> with error:
Account at /etc/letsencrypt/accounts/acme-v01.api.letsencrypt.org/directory/<account-id> does not exist

The site is still serving fine — the existing certificate hasn't expired yet — but renewal keeps failing on every attempt, and left alone this becomes a real outage once the certificate actually runs out.

🔬 Root Cause

This shows up specifically after migrating a server (new VPS, new host, restored from an old snapshot/image) where the Let's Encrypt renewal config and certificate archive were carried over, but the account registration was not.

Two things get conflated:

  • /etc/letsencrypt/renewal/<domain>.conf — the renewal config, which names an account = <id> and a server = <endpoint>. This file typically gets restored/copied cleanly during a migration.
  • /etc/letsencrypt/accounts/<endpoint>/directory/<id>/ — the actual account registration on disk (private key + registration metadata) for that specific ACME endpoint. This can easily not survive a migration if only the renewal config and cert archive were copied, not the full /etc/letsencrypt/accounts/ tree.

Diagnose by checking both pieces:

cat /etc/letsencrypt/renewal/<domain>.conf | grep -E 'account|server'
ls -la /etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/

Two failure shapes to distinguish:

  1. The renewal conf still points at a v1-era endpoint or account ID that was never migrated to v2 (Let's Encrypt's original ACMEv1 API is long deprecated) — the account directory for that ID genuinely doesn't exist under the v2 endpoint's directory tree.
  2. The account ID looks right, but /etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory/ is empty — the account was simply never (re-)created on this host, even though the renewal conf references one as if it were.

Either way, the fix is the same: register a fresh account and repoint the renewal config at it.

🩹 Fix

1. Register a fresh ACMEv2 account:

certbot register --agree-tos --no-eff-email -m <your-email>
# note the new account ID it prints

2. Point the existing renewal config at the new account (don't recreate the whole cert config — just fix the account reference):

sed -i.bak 's/^account = .*/account = <new-account-id>/' \
    /etc/letsencrypt/renewal/<domain>.conf

3. Verify before touching anything live:

certbot renew --dry-run
# expect: "Congratulations, all simulated renewals succeeded"

4. Run the real renewal (don't wait for the next scheduled window if the cert is within its renewal threshold — typically <30 days to expiry):

systemctl start certbot.service

5. Confirm the result — check the certificate's actual serial number and expiry changed, and that the web server picked up the new cert (most setups have a renewal hook that reloads automatically; confirm rather than assume):

openssl x509 -in /etc/letsencrypt/live/<domain>/cert.pem -noout -serial -enddate
systemctl status httpd   # or nginx/apache2 — confirm active, and that it auto-reloaded via the renewal hook

🧭 Why this doesn't show up immediately after migration

The existing certificate keeps serving from /etc/letsencrypt/live/<domain>/ regardless of whether the account registration is valid — TLS termination reads the certificate files directly, it doesn't check ACME account state. The problem only surfaces when the renewal timer actually fires and tries to talk to the ACME server using an account that isn't there. That can be weeks after the migration itself, with no connection back to "we moved this box" in the failure message — worth checking migration history specifically if you hit this on a server that's changed hosts recently.

  • 30-Areas/MajorInfrastructure/Servers/dcaprod.md § Incident Log, 2026-05-27 — the original incident this was diagnosed from (a Hetzner migration)
  • 30-Areas/MajorInfrastructure/Servers/teelia.md — a second occurrence of certbot drift on the same fleet, different root cause (certbot package removed by an unattended-upgrade run rather than a stale account) but worth checking both articles when certbot starts failing after any host change