Let's Encrypt Rate Limits: What to Do When You Hit the Wall

Why your certificate issuance suddenly fails, and how to dig out

Let’s Encrypt is one of the genuinely great things to happen to the open web. Free, automated, universally trusted TLS certificates — the whole reason your homelab services have padlocks now instead of a wall of browser warnings. It is also a free service handling staggering volume, and it protects itself with rate limits. The trouble is you only ever learn those limits exist at the precise moment you slam into one, usually at 2am, usually while debugging something else, usually with a renewal cron job hammering away making everything worse.

Let me walk through what the walls actually are, how to tell which one you’ve hit, and how to get yourself back to issuing certificates.

Advertisement

There are several rate limits, but for self-hosters one dominates: Certificates per Registered Domain, currently 50 per week, counted against the registered domain (the example.com part, not each subdomain). Every distinct certificate for *.example.com counts. Spin up Traefik with a typo in your config and let it retry, or run a half-dozen services each requesting their own cert in a loop, and you can burn through fifty in an afternoon.

The error, when it comes, is unmistakable:

acme: error: 429 :: POST :: https://acme-v02.api.letsencrypt.org/acme/new-order
:: urn:ietf:params:acme:error:rateLimited ::
too many certificates (50) already issued for "example.com" in the last 168 hours,
retry after 2024-10-12T08:14:22Z

That retry after timestamp is not advisory. Nothing you do — restarting the proxy, deleting cert files, recreating the account — will move it. The window is a rolling 168 hours, so the wall starts dissolving exactly one week after each issuance. There is no support queue, no “please reset my limit” button. You wait.

The second limit worth knowing: Duplicate Certificate, 5 per week for the exact same set of domain names. This one is sneakier because it bites people whose containers don’t persist their certificates. Every redeploy fetches a fresh cert for the identical hostname, and five redeploys later you’re locked out of that specific cert — even though you’re nowhere near the 50-per-domain ceiling.

Read the 429 body. “too many certificates (50) … for example.com” is the per-domain limit — you’ve issued too many different certs. “too many certificates already issued for exact set of domains” is the duplicate limit — you’ve issued the same cert too many times. The two have completely different fixes, and confusing them wastes a week.

To check your standing before you get burned, query crt.sh, the public Certificate Transparency log search. Every cert Let’s Encrypt issues is logged there within minutes:

$ curl -s 'https://crt.sh/?q=example.com&output=json' \
    | jq -r '.[] | "\(.entry_timestamp)  \(.name_value)"' \
    | sort -r | head
2024-10-09T07:58:01  service.example.com
2024-10-09T07:52:14  service.example.com
2024-10-09T07:41:09  service.example.com

Count entries inside the last seven days and you know exactly how close to the wall you are. Three identical lines minutes apart is the smoking gun of a redeploy loop chewing through the duplicate limit.

Almost every rate-limit disaster traces back to testing against production. Let’s Encrypt runs a staging endpoint with limits roughly 10x higher and certificates signed by an untrusted root. You point your client at it while you debug, accept that browsers will complain, and switch to production only once issuance demonstrably works.

With certbot:

$ certbot certonly --staging -d service.example.com

With Traefik, it’s one line in your static config:

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /etc/traefik/acme.json
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory
      tlsChallenge: {}

Get a green issuance in staging, then comment out caServer (production is the default) and delete the now-untrusted acme.json so it re-issues real certs once. Burn your fifty in staging, not production.

Three habits keep you off the wall for good. Persist your certs — mount acme.json, /etc/letsencrypt, or whatever your client stores on a real volume so a redeploy reuses the existing certificate instead of fetching another. This alone kills the duplicate-cert problem entirely.

Consolidate with SANs. One certificate can carry up to 100 names. Instead of ten certs for ten subdomains, request one cert with ten Subject Alternative Names. That’s one issuance against your weekly count, not ten:

$ certbot certonly -d a.example.com -d b.example.com -d c.example.com

Back off on failure. A renewal cron that retries every minute after a transient failure is how a small problem becomes a 429. Certbot already renews sane (twice daily, only acting on certs within 30 days of expiry); don’t replace it with something more eager. If you’re hand-rolling, exponential backoff is not optional.

Let’s Encrypt’s rate limits are the price of a free, abuse-resistant CA, and they’re entirely livable once you respect them. The whole game is: test in staging, persist your certificates, and consolidate subdomains into SAN certs. Do those three things and you will likely never see a 429 in normal operation. If you’ve already hit the wall, there’s no shortcut — read the error to learn which wall, check crt.sh to confirm, and use the staging endpoint to fix your config while the production window heals itself. Then go to bed; the wall comes down in a week whether you watch it or not.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.