Custom Domains with Automated SSL for a Multi-Tenant SaaS (Cloudflare for SaaS)
The feature request sounded small: "let creators use their own domain." It isn't.
On Krey, a creator-commerce platform I built, a storefront can live at a platform
subdomain or at shop.theirbrand.com — a domain the platform has never seen
before, owned by someone else, that needs a valid TLS certificate the moment they
point DNS at us. Multiply that by every creator on the platform and you can't be
issuing certs by hand.
I solved it with Cloudflare for SaaS, and the interesting part is how little of the problem ends up being mine.
What actually has to happen
When a creator adds a custom domain, three things need to be true before their storefront works:
- They prove they control the domain (so nobody claims
google.com). - A TLS certificate gets issued for it and renewed forever after.
- Incoming requests for that hostname get routed to the right tenant.
The trap is thinking you'll do 1 and 2 yourself — build ACME challenges, store private keys, run a renewal cron, handle rate limits from the CA. That's a whole product on its own, and it's a security surface you don't want to own if you don't have to.
Hand the hard part to Cloudflare
Cloudflare for SaaS turns this into an API call plus a DNS instruction. I register the creator's hostname as a custom hostname on the platform's Cloudflare zone; Cloudflare handles domain-control validation and issues + renews the certificate. The creator just adds one CNAME.
// When a creator saves a custom domain, register it as a custom hostname.
const res = await fetch(
`https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/custom_hostnames`,
{
method: "POST",
headers: { Authorization: `Bearer ${CF_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({
hostname: creatorDomain,
ssl: { method: "http", type: "dv", settings: { min_tls_version: "1.2" } },
}),
},
);
const { result } = await res.json();
await saveDomain({ storefrontId, hostname: creatorDomain, cfId: result.id, status: "pending" });
The ssl.method: "http" is the choice that makes onboarding painless: Cloudflare
validates control over HTTP once the CNAME resolves, so the creator does one
thing — add the CNAME — and both verification and the certificate follow
automatically. No TXT record dance, no waiting on me.
The tenant lookup is now the whole backend job
Once TLS is off my plate, my job shrinks to one question: a request arrived for
shop.theirbrand.com — whose storefront is this? Every custom hostname maps to a
tenant, so the incoming Host header becomes the tenant key.
active domains serve; pending ones get a clean verifying page.// Resolve the tenant from the host on every storefront request.
export async function resolveStorefront(host: string) {
const domain = await findVerifiedDomainByHostname(host); // cached, then Postgres
if (!domain) throw new NotFoundError("No storefront for this domain");
return getStorefront(domain.storefrontId);
}
I keep the domain-to-tenant map cached because it's read on every single request
and changes rarely. The Cloudflare webhook (or a poll of the custom-hostname
status) flips a domain from pending to active when the cert is live, and only
active domains resolve — so a half-set-up domain shows a clean "verifying your
domain" state instead of a broken TLS error.
Why I don't build this myself anymore
I've seen teams spend weeks on a homegrown ACME pipeline for exactly this feature. My honest take: unless certificate issuance is your product, don't own it. The failure modes — rate limits, renewal at 2am, a leaked private key — are all downside with no differentiation. Cloudflare for SaaS (or a comparable managed layer) turns "give every tenant their own HTTPS domain" from a subsystem into a form field plus a status column.
What stays mine is the part that's actually about my product: mapping a hostname to a tenant, gating on verification status, and rendering the right storefront. That's where the value is, and it's a page of code.
Takeaways
- Custom-domain SSL is three problems — control validation, cert lifecycle, routing. Only routing is really yours.
- Use a managed layer (Cloudflare for SaaS) for issuance and renewal; make the creator's job a single CNAME with HTTP validation.
- Model a domain with an explicit
pending→activestatus and only resolve verified ones, so onboarding never surfaces a scary cert error. - Treat the
Hostheader as the tenant key and cache the lookup — it runs on every request.
The best infrastructure code is the code you talked yourself out of writing.
I build multi-tenant SaaS platforms end-to-end. If you're working on something similar, let's talk.