TLS troubleshooting
Let's Encrypt Rate Limit: Too Many Certificates
cert-manager stops issuing, and the Order shows a rateLimited error from Let's Encrypt. The cause is almost always the same: the cluster keeps requesting a certificate for a name set that has already been issued several times this week. This guide shows how to read the error, which limit it is, how to stop the retry loop, and how to reduce issuance so it stays under the limit.
By Muhammad Soliman, Founder at SeaGit
·
Key takeaways
- Read the retry time in the error. It is the earliest moment the next order for that limit can succeed.
- The exact set limit is 5 certificates per 7 days for one set of names, global across all accounts, with no override.
- The registered domain limit is 50 per 7 days, and it can be raised by an override request.
- Stop the loop first. Each new Certificate, Order or delete-and-recreate counts toward the limit.
- One Certificate per name set, a wildcard where it fits, and copied Secrets keep issuance low.
Read the error
Let's Encrypt enforces its limits when an order is created, so the refusal appears in the cert-manager Order, not in the Ingress or the application. The text has three parts: the error type, the count that is full, and the retry time. The count and the phrase after it name the limit.
# Illustrative error text, shape only (not from a real account)
urn:ietf:params:acme:error:rateLimited: too many certificates (5) already issued
for this exact set of identifiers in the last 168h0m0s, retry after 2026-10-17 09:00:00 UTCThe phrase "for this exact set of identifiers" points to the exact set limit. A message about a registered domain points to the domain limit. A message about too many failed authorizations points to a different limit, which comes from validation failures, not from issuance volume. Fix the validation error before you retry, or the failures will fill that limit too.
Do not treat the retry time as a schedule for your automation. Retrying earlier will be refused again. Retrying after the retry time works only if the loop that caused the first refusal has stopped.
Too many certificates already issued for exact set of domains
This is the most common rate limit error from cert-manager. The Let's Encrypt documentation names it New Certificates per Exact Set of Identifiers. It allows up to 5 certificates for the same exact set of identifiers every 7 days. The set is exact: the same names, and no extra or missing names. Two certificates for example.com and www.example.comare one set. A certificate for example.com alone is a different set.
The limit is global. All new orders for the set count, whichever ACME account submits them. Two clusters that each request the same names share one budget. The documentation says the ability to issue for the same set refills at one certificate every 34 hours, so five quick issuances take about a week to clear.
There is no override for this limit. The only real fixes are to issue the same name set less often, or to change the set so it is new. The next sections show how.
Which limit you hit
Let's Encrypt groups its limits into account, domain and identifier scopes. The three that cause most cert-manager failures are below. The figures are from the Let's Encrypt rate limit page, checked October 2026, and they can change, so confirm them on that page before you plan around them.
| Limit | Documented value | Override |
|---|---|---|
| New Certificates per Exact Set of Identifiers | 5 per 7 days, global. Refill 1 per 34 hours. | None |
| New Certificates per Registered Domain | 50 per 7 days, global. Refill 1 per 202 minutes. | Request form, for the domain or an account |
| New Orders per Account | 300 per 3 hours. Refill 1 per 36 seconds. | See the rate limit page |
The renewal rule matters. Let's Encrypt exempts renewals sent through ACME Renewal Info (ARI) from all rate limits. Older renewal detection counts an order for the same exact set as a renewal, and such orders can still hit limits. Whether your cert-manager sends ARI depends on its version and configuration, so read its release notes before you assume renewals are free.
Stop the retry loop
Before you change anything else, find out what keeps asking for certificates. cert-manager retries a failed Order with backoff, and a Certificate that is deleted and recreated starts over with a new Order. Either one adds to the count. Start with the Certificate, then its request, order and challenge objects:
kubectl describe certificate example-com -n web
kubectl get certificaterequests,orders,challenges -n web# The Order records the refusal from the ACME server
kubectl describe order -n web example-com-tls-1234567890-1# Summary of the Certificate, its request and its last failure (cmctl)
cmctl status certificate example-com -n webThen list every Certificate, to find duplicates. Two Certificates for the same DNS list in different namespaces are two separate issuance paths for one set:
# Every Certificate in the cluster, with its secret and names
kubectl get certificates -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,SECRET:.spec.secretName,DNS:.spec.dnsNamesPause the source of the loop before you retry. Remove a GitOps application that re-creates the Certificate, or scale down the controller that keeps deleting the Secret. Do not delete the Secret to force a fresh issuance, because that starts a new order under the same limit.
cert-manager settings that matter
Use the staging issuer for every new Certificate until it reaches Ready. The staging server is a separate Let's Encrypt environment with its own limits. Its certificates are not trusted by browsers, which is fine for checking the chain, the DNS or HTTP challenge, and the Ingress wiring:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
# Staging: separate rate limits, certificates not trusted by browsers
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: platform@example.com
privateKeySecretRef:
name: letsencrypt-staging-account
solvers:
- http01:
ingress:
ingressClassName: nginxWhen the staging test passes, point the Certificate at a production issuer. The production directory is the same URL without -staging:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: platform@example.com
privateKeySecretRef:
name: letsencrypt-prod-account
solvers:
- http01:
ingress:
ingressClassName: nginxOn the Certificate, set the lifetime and the renewal window explicitly. duration is the requested certificate lifetime and renewBefore sets how long before expiry the renewal starts. A short window with a retry loop means many attempts close together, so keep the default or a clear value and do not set it to the expiry date:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com
namespace: web
spec:
secretName: example-com-tls
dnsNames:
- example.com
- www.example.com
issuerRef:
name: letsencrypt-staging
kind: ClusterIssuer
duration: 2160h # 90 days
renewBefore: 360h # renew 15 days before expiryKeep one Certificate per name set in the whole cluster. If two Ingresses request the same names, reference the same Secret from both, rather than letting each create its own Certificate. A duplicate Certificate is a second order for the same set.
Reduce how often you issue
The lasting fix is fewer orders for the same set. There are three ways to get there, and they combine well.
Use one wildcard where the names share a parent
A wildcard certificate such as *.example.com covers every single-label subdomain with one exact set. Let's Encrypt requires the DNS-01 challenge for wildcard names, so the issuer needs a DNS solver. The example below uses Route 53, with a placeholder hosted zone ID:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-dns01
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: platform@example.com
privateKeySecretRef:
name: letsencrypt-prod-dns-account
solvers:
- dns01:
route53:
region: us-east-1
hostedZoneID: <your-hosted-zone-id>
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: wildcard-example-com
namespace: platform-tls
spec:
secretName: wildcard-example-com-tls
dnsNames:
- "*.example.com"
issuerRef:
name: letsencrypt-prod-dns01
kind: ClusterIssuerA wildcard does not escape the registered domain limit. Every certificate for the domain still counts there, so a wildcard saves budget only on the exact set limit.
Issue once, copy the Secret
Secrets are namespaced, so a Certificate in one namespace cannot populate another. A common mistake is to create the same Certificate in every namespace. The cleaner pattern is one Certificate in one trusted namespace, then a Secret-copying controller that places the resulting Secret where it is needed. Only copy within a trust boundary, because the copy holds the private key.
Fix the validation failures first
A challenge that fails repeatedly fills the authorization failure limit and wastes time. Check the Challenge objects and the ingress path before you retry. An HTTP-01 challenge needs the ingress class and the DNS record to point at the controller. A DNS-01 challenge needs the solver credentials and the zone.
While the limit is full
Once the exact set limit refuses an order, nothing you change in the cluster lets that order through before the retry time. Use the wait for work that does not need a certificate:
- Serve a temporary certificate with a different name set. A new name set has its own budget. A short-lived test hostname, issued on staging, lets you check the routes while production waits.
- Keep the existing Secret in service. A Certificate that still has a valid certificate in its Secret keeps serving until it expires. A failed renewal does not remove the old Secret, so check the expiry date before you act.
- Fix the cause before the retry time. Stop the duplicate Certificates and the GitOps loop now, so the first order after the retry time is the only one.
Avoid the common shortcut of creating a second Certificate with a slightly different name, such as an extra www hostname. It looks like a workaround, but it is a new exact set that consumes its own budget and leaves the original set still blocked.
A worked example
The example is illustrative. The namespace names and counts are invented to show the order of the checks. Suppose a platform team sees an exact set refusal for the same two hostnames in three namespaces. The list command returns three Certificates with identical dnsNames. Each one was created by a copy of the same Helm chart, and each one has its own cert-manager Order. Over one week, the chart re-creates them after each release, so the set is requested five times and the sixth order is refused.
The fix has three parts. First, the team keeps one Certificate in a platform namespace and copies its Secret to the two namespaces that need it. Second, the chart stops creating a Certificate in each release namespace and references the shared Secret instead. Third, the team adds an alert on Certificate Ready=False, so the next refusal is seen the day it happens. None of these changes needs an override, and the retry time is respected.
The lesson is that the limit counts orders, not the number of Secrets. A cluster that looks quiet can still issue the same set many times, because a chart or a controller does it on every deploy. Count orders per name set before you assume a problem is elsewhere.
Request an override
The registered domain limit can be raised. The override request form is linked from the rate limit page, and it asks for the registered domain or the account, the volume you need, and the reason. The form is at the Let's Encrypt override form. Requests take time, so plan for a delay.
An override is not a fix for a loop. It only moves the ceiling. Ask for one after you have stopped the source of the extra orders and can show the steady volume you expect. The exact set limit has no override, so an override request does nothing for a refused exact set.
Prevention checklist
- New Certificates are tested on the staging issuer until Ready.
- Each name set has one Certificate in the cluster. Duplicates are found with the list command above.
- Wildcards are used where the names share a parent domain, with DNS-01 solved by a managed solver.
- Secrets are copied from one trusted namespace, not re-issued in each namespace.
- GitOps does not re-create a Certificate in a loop, and no script deletes Secrets to force renewal.
- Alerts fire on Certificate Ready=False, with the Order error text in the alert.
How SeaGit handles this
SeaGit clusters run cert-manager inside your AWS account, and the platform issues Let's Encrypt certificates for the Ingress routes it creates. Two behaviours in the code bear on the rules above:
- ALB-terminated routes skip Let's Encrypt. When an Ingress uses the ALB class or a certificate ARN annotation, TLS is served by an ACM certificate on the load balancer. The platform does not ask Let's Encrypt for a certificate for that route, which avoids an issuance the cluster would never use.
- The app templates ship a staging issuer. The app template examples include a letsencrypt-staging ClusterIssuer that points at the staging directory, which is the safe place to test a new route first.
Whatever platform you run, the same limits apply to the certificates you request. For the cluster model and where certificates are issued, see the certificates documentation, and for wildcard DNS setup see the wildcard TLS guide.