How to set up custom domains for your SaaS with Cloudflare SSL for SaaS (and a simpler way)

Cloudflare for SaaS (also called SSL for SaaS, or custom hostnames) lets your customers point their own domain at your app. Every plan includes 100 custom hostnames; after that it's $0.10 per hostname per month, up to a 50,000-hostname pay-as-you-go cap. Setup is four zone-level steps, then one API call plus a polling loop per customer.
That's the short version. The rest of this post is the long one: a full tutorial for Cloudflare, the limits that don't appear on the pricing page, and the same tutorial for Domainee, which does the job in one API call and a webhook.
The numbers, in one table
| Cloudflare for SaaS | |
|---|---|
| Included hostnames | 100 on Free, Pro, and Business |
| Price per extra hostname | $0.10 / month |
| PAYG hostname cap | 50,000 (raised from 5,000 in May 2025) |
| Above the cap | Enterprise contract, pricing not published |
| Certificate issuance rate limit | 15 certs / minute, then a 30-second lockout |
| SSL validation methods | 3 (HTTP DCV, TXT DCV, Delegated DCV) |
| Hostname state webhooks | Enterprise only |
| Zone setup before first customer | Enable custom hostnames, fallback origin, CNAME target, API token |
I build Domainee, which competes with this, so treat the opinions accordingly. The Cloudflare numbers are all from their published pricing and docs, and the "when Cloudflare is the right pick" section near the end is sincere.
Where Cloudflare SSL for SaaS gets complicated
The good news first: the hostname price is genuinely competitive, and 100 included hostnames covers a real pilot. The complications are operational, not financial.
Three SSL validation flows, and you pick
- HTTP DCV — easiest to set up but doesn't work for wildcard certificates and has a race condition where DNS can cut over before the cert is issued.
- TXT DCV — bulletproof. Customer adds a TXT record at
_acme-challenge.shop.acme.com(the ACME DNS-01 challenge). For wildcards you need two TXT tokens (apex + wildcard). - Delegated DCV — one-time CNAME delegation, lets Cloudflare handle renewals forever without bothering the customer again. Conflicts with multi-CDN setups (only one party can hold the delegation).
You'll spend support cycles explaining the trade-offs to customers.
Rate limit you can hit during onboarding spikes
- 15 certificates per minute by default. Exceed it and you get a 30-second lockout. Higher limits require an account-manager request.
- Hostnames over 64 characters require setting
cloudflare_branding: truein the API because of certificate Common Name restrictions.
Two-step setup before you can issue anything
Before the per-customer API calls work, you have to:
- Enable Custom Hostnames on your zone via the Cloudflare dashboard (SSL/TLS → Custom Hostnames).
- Configure a fallback origin — a proxied A or CNAME record on your zone, designated as the fallback. Status must read Active.
- (Optional) Pick a CNAME target like
customers.yoursaas.comso customers point their CNAME at a friendly name instead of your apex. - (Optional) Add a Worker as the origin if you want per-tenant routing logic. Required for Workers-for-Platforms dispatch patterns.
Webhooks are Enterprise-only
On Free, Pro, and Business plans you have to poll the custom-hostname status. Both result.status AND result.ssl.status need to read "active" before traffic works. Hostname webhooks ship on Enterprise.
Cloudflare Enterprise is required for
- Custom metadata per hostname (useful for tenant-mapping at the edge)
- BYOIP (your own anycast IPs)
- Apex proxying
- Webhooks on hostname state changes
Enterprise pricing isn't published. Onboarding involves a sales call.
Error 1016 will be your most-debugged ticket
"Origin DNS error" hits when:
- Fallback origin DNS record missing or not designated
- Ownership validation hasn't completed
- Customer's CAA records at their DNS block your CA (Let's Encrypt / Google Trust Services / SSL.com)
- Customer's zone has a hold that the previous owner hasn't released
None of these are obvious to the customer. You write the runbook, and ours is at /blog/debug-customer-custom-domain-runbook if you'd rather start from someone else's.
The Domainee alternative, in one paragraph
50 custom domains and 100 GB of bandwidth a month, free forever, no card. After that, $0.20/domain/month graduated to $0.10 at 10,000+ domains. One CNAME for each customer. SSL provisions on first request, renewals run forever. Webhooks fire on every plan. The integration is one API call per customer plus one webhook handler. No fallback origin to configure, no validation flow to choose between, no 1016 debugging.
If you don't already run Cloudflare's WAF and DDoS shield, the math and the developer experience both favor Domainee in the 1 to ~5,000 hostnames band.
OK. Tutorials.
Tutorial 1: Cloudflare SSL for SaaS
Prerequisites:
- A Cloudflare account with your SaaS root domain on at least the Free plan.
- An API token with the Custom Hostnames Edit permission. Mint it at My Profile → API Tokens.
- Your origin server reachable on a public hostname.
Step 1. Enable Custom Hostnames on your zone
In the dashboard: select your zone, go to SSL/TLS → Custom Hostnames, toggle on.
Step 2. Configure the fallback origin
Add a proxied A or CNAME record on your zone that points to your origin server (e.g. origin.yoursaas.com → 203.0.113.50). Save.
Back in Custom Hostnames, click Add Fallback Origin, pick the record. Wait for status to flip to Active.
Step 3. (Optional) Create a friendly CNAME target
Customers don't want to CNAME to yoursaas.com. Create a wildcard:
*.customers.yoursaas.com CNAME yoursaas.com
This is the CNAME target you'll give your customers.
Step 4. Provision a custom hostname per customer (API)
Every time a customer adds a domain in your app's dashboard:
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/custom_hostnames" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"hostname": "shop.acme.com",
"ssl": {
"method": "http",
"type": "dv"
}
}'
The response gives you the hostname id, the SSL validation tokens, and the current status.
Step 5. Tell the customer the CNAME
Type Name Value
CNAME shop.acme.com customers.yoursaas.com
Note: if the customer's DNS provider has restrictive CAA records (limiting which CAs can issue), they may need to add a CAA exception for letsencrypt.org or google.com or sectigo.com (Cloudflare picks the CA).
Step 6. Poll until cert is live
Both fields must read "active":
curl "https://api.cloudflare.com/client/v4/zones/{zone_id}/custom_hostnames/{hostname_id}" \
-H "Authorization: Bearer $CF_TOKEN"
# Expected response when ready:
# {
# "result": {
# "id": "...",
# "hostname": "shop.acme.com",
# "status": "active",
# "ssl": { "status": "active" }
# }
# }
Loop every 30 seconds for the first 5 minutes. Most domains land in under 90 seconds, some take longer due to DNS propagation.
Step 7. (Optional) Delegated DCV for hands-off renewals
If you want renewals to never bother the customer again, ask them to set:
Type Name Value
CNAME _acme-challenge.shop.acme.com shop.acme.com.YOURZONE.dcv.cloudflare.com
Cloudflare will handle all future renewal validations via DNS-01 without re-prompting.
Step 8. Handle errors as they come up
Common failure modes you'll see in support:
- Error 1016 — fallback origin not set, or hostname not pointing at the right zone
- TLS handshake error — DNS cut over before cert issued (HTTP DCV race)
- CAA blocks issuance — customer's DNS has a strict CAA record
- CNAME chain too long — customer pointed www.shop.acme.com to shop.acme.com to your CNAME target. Two-hop chains can fail; recommend a direct CNAME.
Plan a few hours of support reading for your first 10-20 onboardings.
Tutorial 2: Domainee
Step 1. Mint an API key
Sign up at /sign-up. From /developers click Create API key. Copy the sk_live_… string. It's shown once.
Step 2. One API call per customer
Run this from your app's backend when a customer adds a domain:
curl https://api.domainee.dev/v1/domains \
-H "Authorization: Bearer $DOMAINEE_KEY" \
-H "Content-Type: application/json" \
-d '{
"hostname": "shop.acme.com",
"originUrl": "https://acme-prod.fly.dev",
"metadata": { "tenantId": "tnt_77721" }
}'
Response includes the CNAME for the customer:
{
"domain": {
"id": "8f09b47c-…",
"status": "pending",
"dnsRecords": [
{ "type": "CNAME",
"name": "shop.acme.com",
"value": "edge.domainee.dev" }
]
}
}
Step 3. Tell the customer the CNAME
Type Name Value
CNAME shop.acme.com edge.domainee.dev
That's it on the customer side.
Step 4. Receive the webhook when DNS lands
Domainee fires domain.verified the moment DNS resolves correctly and the cert provisions. HMAC-signed, retried for 7 days if your endpoint is down.
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post(
"/webhooks/domainee",
express.raw({ type: "application/json" }),
async (req, res) => {
const sig = req.header("x-domainee-signature");
const expected = "sha256=" +
crypto.createHmac("sha256", process.env.WHSEC)
.update(req.body)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.status(401).end();
}
const event = JSON.parse(req.body.toString());
if (event.type === "domain.verified") {
await db.tenants.update(event.data.metadata.tenantId, {
customDomainStatus: "live",
});
}
res.status(200).end();
},
);
Step 5. There isn't one
No fallback origin to configure. No validation flow to pick. No polling loop. SSL renewals run forever. DNS monitoring is built in and fires domain.monitor_updated if anything drifts.
If your AI tooling (Cursor, Claude Code, Claude Desktop) should drive Domainee too, drop the MCP server config into your client and the same Bearer key works for natural-language operations.
Side-by-side, after both tutorials
| Cloudflare SSL for SaaS | Domainee | |
|---|---|---|
| Free quota | 100 hostnames included | 50 hostnames + 100 GB bw, no card |
| Per-hostname price | $0.10/mo | $0.20/mo → $0.10 at 10k+ |
| Setup steps before first customer | 4 (zone enable, fallback origin, CNAME target, API token) | 1 (mint API key) |
| Setup steps per customer | 2 (provision API call + poll) | 1 (one API call) |
| Validation methods you choose between | 3 (HTTP, TXT, Delegated DCV) | 0 (we pick) |
| Webhooks | Enterprise only | All plans |
| MCP / AI agent | No | Yes |
| Per-customer origin URL | One fallback (Worker for per-tenant routing) | Per hostname in the API call |
| Hostname cap | 50,000 (PAYG) | Unlimited (price graduates) |
| Enterprise pricing | Not published | Published, graduates to $0.10 at 10k+ |
| Time from signup to first verified domain | ~30 minutes | ~5 minutes |
When Cloudflare is actually the right pick
Be honest about this. Cloudflare wins when:
- You already run Cloudflare for WAF, DDoS, Workers, or KV
- You need Cloudflare-platform features (Workers for Platforms, custom metadata per hostname, BYOIP)
- You have the engineering bandwidth to maintain validation logic, fallback origins, and 1016 debugging
- You're at 10,000+ hostnames where the per-hostname math wins on $0.10 flat versus Domainee's graduated curve
- You're already shopping for an Enterprise contract for the rest of Cloudflare's stack
Want to check the arithmetic for your own hostname count? The custom domain cost calculator models both. The wider field, including Approximated and Entri, is covered at /blog/cloudflare-for-saas-alternatives-2026 and the direct side-by-side lives at /alternatives/cloudflare-ssl-for-saas.
When Domainee is the right pick
- You want to ship custom domains today, not after a 2-week setup
- 50 free hostnames + no card lets you validate the feature before paying anyone
- You want webhooks on every plan
- You want AI tooling to drive it via MCP
- Your scale is somewhere in the 1 to 10,000 hostnames band
- You don't want to choose between three SSL validation flows
FAQ
How much does Cloudflare for SaaS cost in 2026? 100 custom hostnames are included on the Free, Pro, and Business plans. Beyond that it's $0.10 per hostname per month, billed pay-as-you-go up to 50,000 hostnames. Past 50,000 you need an Enterprise contract, and that pricing isn't published.
What is a custom hostname in Cloudflare?
A custom hostname is one of your customers' domains (shop.acme.com) that Cloudflare terminates TLS for and routes to your fallback origin. You create one per customer via the /custom_hostnames API, and Cloudflare issues and renews the certificate for it.
Do I need a fallback origin for Cloudflare for SaaS? Yes. Custom hostnames won't serve traffic until your zone has a proxied A or CNAME record designated as the fallback origin and its status reads Active. A missing or undesignated fallback origin is the most common cause of error 1016.
Does Cloudflare for SaaS support webhooks for hostname status?
Only on Enterprise. On Free, Pro, and Business you poll the custom-hostname endpoint and wait for both result.status and result.ssl.status to read "active". Domainee fires a signed domain.verified webhook on every plan, including the free one.
Which SSL validation method should I use? TXT DCV if you want reliability, Delegated DCV if you want renewals to never bother the customer again, HTTP DCV only for quick tests. Delegated DCV conflicts with multi-CDN setups because just one party can hold the delegation.
Can I use Cloudflare for SaaS with apex domains? Only with an Enterprise plan, which is what enables apex proxying. On other plans customers point a subdomain at you. Apex domain support is a common reason teams outgrow the Business plan.
Try Domainee
Mint an API key. 50 customer domains free, no card, and every number published at /pricing. The first customer's domain can go live in the time it takes to read this paragraph.
If you want the deep-dive on the Connect API integration: docs/quickstart. For MCP setup: docs/mcp. For webhooks: docs/webhooks.