</>CodeWithKarani

Your DDNS subdomain keeps hitting Let's Encrypt rate limits, and it is not a bug

Karani GeoffreyKarani Geoffrey8 min read

You set up a home lab or a small self-hosted box on a free dynamic DNS name, something like myserver.dyndns-example.org. Certbot worked the first time. Then a week later a renewal failed, or you rebuilt the container and could not get a certificate at all. The error talks about too many certificates for a domain you have barely used. You have issued maybe three certs in your life. This makes no sense.

It makes complete sense once you know how the limit is counted. You are not being rate limited for what you did. You are being rate limited for what a few thousand strangers on the same base domain did this week. This is not a bug, it is not a Certbot problem, and no retry, no --force-renewal, and no second account will get you out of it. The limit is attached to a domain you do not own.

Let's Encrypt counts its main certificate limit per registered domain, worked out from the Public Suffix List, not per hostname. If your DDNS provider's base domain is not on that list, every customer under it shares a single bucket of 50 certificates per 7 days.

  • Check whether the provider's base domain is on the Public Suffix List. If it is, you get your own bucket and something else is wrong. If it is not, you are sharing.
  • The durable fix is a domain you register yourself. It costs a few dollars a year and gives you a rate-limit bucket nobody else can touch.
  • Ask the DDNS provider to add their domain to the Public Suffix List. That fixes it for all their users, but it is their action, not yours, and it takes time.
  • A rate-limit increase request to Let's Encrypt is slow and does not help on a shared suffix, because others still spend your raised allowance.

The exact error: "too many certificates already issued for"

Certbot surfaces it from the ACME server, and the key part names a domain that is a level or two up from the one you asked for:

too many certificates already issued for: domain.org while -d sub.domain.org was requested

Read that carefully, because the wording is the entire clue. You requested sub.domain.org. The limit was counted against domain.org. Newer ACME error text phrases it as a limit on new certificates per registered domain with a retry-after timestamp, but the meaning is identical: the ceiling is on the parent, and the parent is shared. Your subdomain is just a tenant in a building whose lift is already full.

If you have hit the related duplicate-certificate limit instead, that one is about issuing the same exact set of names too many times, which I covered in the general too-many-certificates fix. This article is specifically about the case where the counted domain is not the one you typed.

Why this happens: the Public Suffix List decides what "your domain" means

Let's Encrypt's most relevant limit here is Certificates per Registered Domain: currently up to 50 certificates every 7 days for a single registered domain. The word doing all the work is registered. Let's Encrypt does not guess where the registrable boundary is. It looks it up in the Public Suffix List, a community-maintained file that records exactly which domain suffixes are shared boundaries.

For an ordinary domain, the list contains public suffixes like .com and .org. So the registered domain of www.example.com is example.com, and the whole 50-per-week bucket is yours because you own example.com. Nobody else can put a name under it.

A DDNS provider is different. Thousands of unrelated people get a name directly under one base domain. Whether that base domain acts as a shared boundary depends on one thing: is it on the Public Suffix List?

Base domain NOT on the list Registered domain = ddns-example.org you.ddns-example.org stranger1.ddns-example.org stranger2.ddns-example.org ONE bucket: 50 certs / 7 days shared by everyone above Base domain ON the list ddns-example.org is a public suffix you.ddns-example.org own bucket: 50 / 7 days stranger1.ddns-example.org separate bucket, cannot touch yours stranger2.ddns-example.org: separate again
The only difference between a working setup and a permanently rate-limited one is a single line in a text file you do not control.

When the base domain is on the list, Let's Encrypt treats it as a public suffix, so you.ddns-example.org becomes its own registered domain and gets its own 50-per-week bucket. This is exactly why DuckDNS and similar well-run providers appear on the list. When the base domain is not on the list, the registered domain is the base domain itself, and you are sharing one bucket with every other customer. Fifty certificates across thousands of active users is nothing, so the bucket is empty most of the time and your issuance fails through no fault of your own.

The fix, in order of durability

Step 1: Find out which situation you are in

Take your host's base domain, the part just below the public suffix, and check the list.

# Your host: myserver.dyndns-example.org  ->  base domain: dyndns-example.org
# Search the Public Suffix List for it:
curl -s https://publicsuffix.org/list/public_suffix_list.dat | grep -i "dyndns-example.org"

If grep prints a matching line, the base domain is a public suffix and you already have your own bucket. In that case a rate-limit error means you genuinely issued too many yourself, usually from a crash loop re-requesting on every restart, and the fix is to stop re-issuing and reuse the cert on disk. If grep prints nothing, you are on a shared suffix and the rest of this applies.

Step 2: Register a cheap domain and point it at your box

This is the real fix, and it is smaller than it sounds. A .com or a country domain costs a few dollars a year. Once you own yourname.com, it sits directly under the .com public suffix, so you are the registered domain and the entire 50-per-week bucket is yours alone. No stranger's issuance can spend it.

  1. Register the domain at any registrar.
  2. Point an A record (or a CNAME to your existing DDNS name if your IP changes) at your server.
  3. Re-run Certbot against the new name: certbot --nginx -d home.yourname.com.
  4. Expect success on the first attempt, because the bucket is empty and yours.

If your IP is dynamic, keep the DDNS name purely for IP tracking and put a CNAME from your owned domain to it. You get stable naming and a private rate-limit bucket at once. When you first do this, test against staging first so a config mistake does not burn even your own fresh allowance; I explained why in why cert automation should default to staging.

Step 3: If you must stay on the DDNS name, ask the provider to list their domain

Providers can submit their base domain to the Public Suffix List. It is the correct fix for a DDNS service, it is what the responsible ones have already done, and it gives every one of their users an independent bucket. But you cannot do it for them, submissions are reviewed by volunteers, and acceptance plus propagation into ACME servers takes weeks to months. Open the request, then do Step 2 anyway so you are not blocked in the meantime.

Verification

Prove the counted domain changed, not just that one issuance happened to work.

  1. Issue against the new owned name and confirm success: certbot certificates should list it with a fresh expiry.
  2. Inspect what the certificate actually covers: openssl x509 -in /etc/letsencrypt/live/home.yourname.com/cert.pem -noout -subject -ext subjectAltName. The SAN should be your owned name.
  3. Force a renewal test against staging to prove the bucket is yours: certbot renew --dry-run. On a private bucket this passes every time; on the old shared suffix it was intermittently failing, which is the whole difference.

What people get wrong

Filing it as a Certbot bug. This gets reported over and over on the Certbot tracker, and it is closed every time, because Certbot is only relaying the server's answer. Certbot never decided the limit and cannot change it. The counting happens on Let's Encrypt's side, against the Public Suffix List.

Making a second ACME account. The Certificates per Registered Domain limit is not scoped to your account. It is scoped to the domain. A fresh account sees the same full bucket, because the bucket belongs to ddns-example.org, not to you. This wastes an afternoon and changes nothing.

Spamming retries or --force-renewal. Every attempt that would have issued a certificate counts toward the limit even when it fails at the ceiling, and hammering the endpoint can land you in a separate failed-validation limit as well. Retrying a rate limit makes it worse. Stop, and either wait for the window or move domains.

Assuming the retry-after time is your fix. On a busy shared suffix the bucket refills at roughly one certificate every few hours and is immediately spent again by other users. You can wait for the retry-after and still fail, because the slot you waited for went to someone else. Only a bucket you own is reliable.

When it is still broken

You moved to an owned domain and still get rate limited. Then you are hitting the duplicate-certificate limit, which counts repeated issuance of the exact same set of names. This is almost always a service in a restart loop re-requesting on every boot. Fix the loop and reuse the certificate already on disk instead of asking for a new one; a wrapper that requests a cert on container start is the usual culprit.

The base domain is on the list but issuance still fails. The Public Suffix List is cached. A recently added suffix may not yet be reflected on the ACME server you are hitting. Give it time, and in the meantime use an owned domain.

You need many real subdomains under your own domain. Fifty certificates per 7 days is generous but finite. If you are issuing per-subdomain at scale, switch to a single wildcard certificate via the DNS-01 challenge so one cert covers *.yourname.com and you stop consuming the bucket one hostname at a time.

The uncomfortable truth is that a free shared DDNS name was never yours in the way TLS assumes a name is yours. The certificate limit simply makes that visible. Owning the name is not a workaround here, it is the thing the whole system is built around.

Frequently asked questions

Why is Let's Encrypt rate limiting my subdomain when nobody else uses it?
The limit is not counted per subdomain. It is counted per registered domain, worked out from the Public Suffix List. If your host is sub.dyndns-example.org and that provider's base domain is not on the Public Suffix List, then you share one 50-certificates-per-week bucket with every other customer under that base domain. Their issuance uses up your allowance, and yours uses up theirs.
How do I know if my DDNS provider's domain is on the Public Suffix List?
Search for the base domain in the list at publicsuffix.org, or check whether well-known providers like DuckDNS already appear there. If the base domain is listed, each user's subdomain is treated as its own registered domain and you get your own full rate-limit bucket. If it is absent, everyone under that domain shares one bucket, which is the whole problem.
Can I ask Let's Encrypt to raise my rate limit?
You can file a rate-limit adjustment request, but it is a slow, manual, human-reviewed process and it does not change the architecture. On a shared suffix your raised limit still gets consumed by other customers. It is a mitigation for a domain you control, not a fix for a bucket you share.
Does moving to my own domain actually fix this permanently?
Yes. A domain you register yourself is its own entry under the Public Suffix List's public suffixes (.com, .org and so on), so its registered domain is yours alone and the 50-per-week bucket belongs entirely to you. No other customer's issuance can touch it. A cheap domain removes the shared-bucket problem completely and for good.
#Let's Encrypt#Certbot#TLS#DDNS#Public Suffix List
Keep reading

Related articles