The renewal you actually needed is the one that got refused.
Something had been re-issuing certificates for weeks — a container that rebuilt nightly, a deploy script with --force-renewal in it, a pipeline that ran certonly on every push. None of it was visible, because every one of those runs succeeded. Then the certificate came up for real renewal, and Let's Encrypt returned an error instead of a certificate:
Error creating new order :: too many certificates (5) already issued for this exact set of domains in the last 168 hours: yourdomain.com, www.yourdomain.com, retry after 2026-08-09T04:11:22Z
The quota was spent by runs that did not need certificates, and now the run that does cannot have one. The leaf on :443 keeps counting down while you sort it out.
Prove it
Which limit did you hit?
The error names the limit and the retry time. Pull the recent ones out of the log:
sudo grep -iE "too many|rate ?limit|retry after|429" /var/log/letsencrypt/letsencrypt.log | tail -20
Let's Encrypt runs these as refilling buckets rather than hard weekly resets, which matters: you usually do not wait the full window, you wait for one slot to come back.
| Error names | The limit | How fast it comes back |
|---|---|---|
| too many certificates already issued for this exact set of domains | 5 certificates per identical name set per 7 days | one slot every 34 hours |
| too many certificates already issued for registered domain | 50 certificates per registered domain per 7 days | one slot every 202 minutes |
| too many failed authorizations | 5 authorization failures per name per account per hour | one slot every 12 minutes |
| too many new orders | 300 new orders per account per 3 hours | one slot every 36 seconds |
Two of those mean different things. The exact-set limit is a re-issue loop: the same names over and over. The registered-domain limit is breadth, and it bites multi-tenant setups issuing per-customer subdomains. The failed-authorization limit is not an issuance problem at all — it means your challenges are failing, and the retries are digging the hole deeper.
If the log shows failed authorizations rather than issued certificates, the rate limit is a symptom. Fix the challenge first: HTTP-01 died after you forced HTTPS covers the usual reason a challenge stops resolving.
What is on the wire right now?
Before changing anything, find out how much time you actually have:
echo | openssl s_client -connect yourdomain.com:443 \ -servername yourdomain.com 2>/dev/null \ | openssl x509 -noout -enddate -subject
Or read it from outside with the SSL checker, which reports the leaf your visitors are being served along with chain and hostname state.
This one number decides your next move. If the served certificate is still valid for several days, you do not have an emergency — stop the loop, wait for a slot, renew normally. If it has already expired, skip to the last fix.
Fix: find what spent the quota
certbot renew is not the culprit, and this is worth being clear about because the internet says otherwise. renew checks each certificate's expiry and does nothing unless it is inside the renewal window, so running it hourly costs nothing. Quota gets spent by commands that ask for a new certificate every time they run.
Look for those:
# certificates issued far more often than they expire sudo ls -la --time-style=long-iso /etc/letsencrypt/archive/yourdomain.com/ # force-renewal or certonly hiding in cron, units, or deploy scripts sudo grep -rniE "force.renewal|certonly" /etc/cron* /etc/systemd/system /usr/local/bin 2>/dev/null
If archive/ holds ten cert*.pem files from the last week, you have found the loop. The usual causes:
- A container that does not persist
/etc/letsencrypt. Every restart looks like a first run, so it issues again. Mount that path on a volume and the loop stops. --force-renewalleft in a script after someone used it once to fix something. It does exactly what it says, every run.- A CI job running
certonlyon every deploy instead of letting the timer handle renewals.
Fix the cause before asking for another certificate. Otherwise the fresh one gets spent the same way.
Fix: test against staging, not production
While you are debugging hooks, webroot paths, or DNS plugins, point at the staging environment. Staging issues untrusted certificates against separate, far larger quotas.
# exercises the full renewal path against staging sudo certbot renew --dry-run # a fresh issuance test against staging sudo certbot certonly --webroot -w /var/www/html -d yourdomain.com --test-cert
Nothing you do with these touches your production limits. Any debugging loop belongs here.
Fix: getting a certificate back when you are already expired
If the served certificate has expired and you cannot wait for a slot, a certificate for a different set of names is a different bucket, so it is not blocked by the exact-set limit:
sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com -d assets.yourdomain.com
Two conditions before you try this. The extra name must genuinely validate — it needs a DNS record resolving to the same server, or the challenge fails and you start consuming the failed-authorization limit instead. And the new certificate still counts against the 50-per-registered-domain bucket, so this is a way out of one limit, not out of all of them.
The name you add is a real name on your production certificate, so pick one you are happy to serve rather than something disposable.
There is also a quieter route worth knowing. Renewals driven by ACME Renewal Information, where the client asks Let's Encrypt when to renew and follows that answer, are exempt from rate limits. Recent certbot versions use ARI automatically, so keeping the client current is itself protection against ever landing here.
Stop finding out this way
Every version of this failure was invisible until the certificate was already refused. The re-issue loop ran quietly for weeks. The quota drained quietly. The refusal only surfaced when the renewal mattered.
An outside check does not care why the certificate on :443 is old, which is what makes it catch all of these at once. CertPost reads the served certificate on a schedule and warns at 30, 14, 7, and 1 days, so a blocked renewal reaches you while there is still a week of runway instead of on the morning it expires. Three certificates free, no card.
For a one-off look right now, the SSL expiry checker reads the live expiry off port 443 or any TLS port.
Related failures
- Certbot says it renewed. Your server is still serving the old cert. — the full disk-vs-wire failure map
- HTTP-01 died after you forced HTTPS. — failing challenges, which is what burns the authorization limit
- nginx reloaded nothing. Certbot still exited 0. — when renew succeeds but the reload is missing
- The certbot timer is not running. — when nothing schedules the renew at all
- Renewal failed. Root mail never left the box. — when the errors were reported to nobody
