We serve HTTPS for more than 5,000 customer-owned domains at Lindo.ai, a white-label website builder. Every customer can bring www.their-business.com and it just works — valid cert, fast, isolated. We do it without Cloudflare for SaaS. Here is the actual architecture, the numbers, and the things that broke.
This is the system we productized into cnames.dev. Nothing below is theoretical.
The shape of the system
Three moving parts, deliberately boring:
- An anycast edge terminating TLS and proxying to origins.
- An on-demand TLS path that issues a certificate the first time an allowed hostname is seen.
- A control plane that owns the truth: which hostname belongs to which tenant, and whether it is verified.
Browser
│ TLS ClientHello (SNI: www.acme.com)
▼
Edge node (anycast)
│ 1. SNI -> "is this host allowed?" (control-plane lookup, cached)
│ 2. cert in store? serve it : issue via ACME (HTTP-01), then serve
│ 3. host -> tenant -> origin (+ per-tenant headers, real client IP)
▼
Origin (multi-tenant app)On-demand TLS, and the allow-list that guards it
We pre-issue nothing. When a TLS handshake arrives for a hostname we have never seen, the edge asks the control plane a single question: is this hostname registered and verified by a tenant? If yes, we issue via HTTP-01 inline and cache the result. If no, we drop the handshake.
That allow-list check is the entire security boundary of on-demand TLS. Without it, anyone who points a domain at your edge can trigger unbounded certificate issuance and burn your CA rate limits in minutes. The lookup has to be fast and it has to fail closed. We keep it in an in-memory cache at the edge with a short TTL, backed by the control plane.
Let's Encrypt rate limits: what the docs do not emphasize
The limit everyone worries about — ~50 certificates per registered domain per week — barely matters here, because each customer domain (acme.com, globex.io) is a different registered domain. You would need 50 certs for one customer's single domain in a week to hit it.
What actually bit us was the account-level pressure: new-order limits and the duplicate-certificate limit, both triggered by retries. A customer sets the CNAME, DNS has not propagated, our issuance fails, something retries aggressively, and now one broken domain is generating dozens of failed orders. Multiply by a few hundred customers configuring domains on the same afternoon and you have a self-inflicted outage.
The fixes were unglamorous:
- Do not attempt issuance until DNS resolves to us. Check first; issue second.
- Exponential backoff with a ceiling on failed issuance, per hostname.
- Cache negatives. A host that just failed should not be retried on the next request.
Always re-read the current Let's Encrypt rate-limit page before tuning any of this; the numbers change. We also keep a second ACME provider configured as a fallback so a single CA incident is not a single point of failure.
Keeping the edge stateless
Edge nodes hold no durable truth. The certificate store and the hostname→tenant map are shared; a node can be recycled at any time and warm itself from the shared store. This is what lets us add capacity or replace a node without a certificate re-issuance storm. The first request to a cold node for a given host is a store lookup, not a fresh ACME order.
The real client IP problem
The moment you proxy, your origin sees the edge's IP, not the visitor's. For a multi-tenant app that does rate limiting, fraud checks, or geo logic, that is a correctness bug. We forward the client address explicitly and make the origin trust it only from our edge ranges. It sounds trivial; it is the kind of thing that silently corrupts analytics for months if you skip it. We wrote up the details in the broader custom domains guide.
What we would tell our past selves
- The proxy is the easy part. DNS ergonomics and issuance lifecycle are where the time goes.
- Fail closed on unknown hosts, always. A permissive default is a cross-tenant incident waiting to happen.
- Verify ownership before routing or issuing. See the security section of the guide.
- Instrument issuance as its own funnel: attempted → DNS-ready → issued → served. Most “SSL is broken” tickets are actually “customer's DNS is not set yet.”
Build it or buy it
If you want to run this yourself, the honest trade-offs are in cnames.dev vs self-hosting Caddy on-demand TLS. If you would rather make one API call per domain and never think about ACME rate limits again, that is what cnames.dev is — the same infrastructure described here, offered as a service. It is a particularly good fit for website builders running the exact playbook we did.