Skip to content

Cloudflare for SaaS Custom Hostnames at Scale

“Bring your own domain” sounds like a checkbox. In practice it’s the part of a hosting product most likely to page you at 2am: certificate issuance, renewal, routing, and the long tail of DNS misconfigurations your customers will invent. Here’s how Micropage does it, and why we made the choices we did.

The shape of the problem

Every published Micropage site is a static build. Each one needs to answer on its own {slug}.micropage.sh subdomain and, on paid plans, on any number of customer domains, each with its own certificate. The hosting layer has to scale with the number of sites without a per-site resource that eventually hits an account limit.

We started with one Cloudflare Pages project per site. It worked, but Pages caps projects per account and custom domains per project, so it was never going to reach thousands of sites. Today every site is served from R2 by a single Worker.

Custom Hostnames + one Worker

Domains terminate at the zone with Cloudflare for SaaS:

  1. A customer points a CNAME at cname.micropage.sh. (Not at their {slug}.micropage.sh subdomain, which is ours to route.)
  2. We register their hostname as a Custom Hostname on the micropage.sh zone and issue a certificate per hostname, validated over HTTP DCV rather than TXT. No wildcard cert for customer domains.
  3. A Worker, bound to */* on the zone, reads the incoming Host header, maps it to a site via KV, looks up that site’s current deploy, and serves the file from R2.

Custom Hostnames is the TLS layer; the Worker is the router and the web server; R2 is the storage. Each does one job.

visitor → https://go.customer.com
        → CF edge (Custom Hostname cert, HTTP-DCV issued)
        → Worker on */*  (Host → site via KV, site → current deploy via KV)
        → R2  (that deploy's files)

Platform subdomains take the same path: a wildcard DNS record gives every {slug}.micropage.sh an answer, so a new site needs no DNS change at all. Our own subdomains (the app, docs, and so on) are protected with dashboard-managed exclusion routes, so the catch-all never swallows traffic it shouldn’t.

Deploys and rollback

Each publish uploads the build’s files to R2 under content hashes, writes a manifest for that deploy, and then flips one KV pointer to it. Files that didn’t change are reused, so a typical publish uploads a handful of files. Old deploys stay in place, so rolling back is pointing the site at an earlier manifest; nothing is rebuilt.

The tradeoffs we accepted

  • Apex domains aren’t supported. CF for SaaS + the CNAME model means customers point a subdomain (www., go., hello.). We reject apex hostnames at the edge rather than fake it.
  • HTTP DCV over TXT. It’s a smoother first-run for non-technical customers — no waiting on TXT propagation — at the cost of the domain needing to serve the validation path.
  • One shared Worker. A bad Worker deploy would affect every site, so it ships with gradual rollouts and a one-step rollback to the previous version.

Why not a project per site?

Per-site projects give a neat blast radius, which is why we started there. But site content is static: the risky part of a deploy is the build, and that is already isolated per site. With immutable, content-addressed deploys and a pointer per site, one site’s publish can’t touch another site’s files, and rollback is still per site. The shared piece, the Worker, changes rarely and deploys gradually.

If you’re building something similar, the one-liner is: let Cloudflare for SaaS own TLS and hostname identity, let a Worker own routing, and keep each site’s deploys immutable.

Custom domains are included on paid plans — try it.