I spent an afternoon setting up what should have been a simple personal landing page on Cloudflare Workers with a custom domain. It worked — but not before hitting three problems that no tutorial mentioned. This post documents exactly what broke and how I fixed it, so you don't lose the same time.
The Stack
Nothing exotic:
- Cloudflare Workers — serves the HTML directly from the edge, no origin server
- Wrangler — deploys the Worker from the CLI
- Custom domain registered and managed in Cloudflare Registrar
The Worker itself is simple: a fetch handler that returns an HTML string with Content-Type: text/html. No framework, no build step, no S3 bucket.
// src/worker.js
export default {
async fetch(request, env, ctx) {
return new Response(HTML, {
headers: {
'Content-Type': 'text/html;charset=UTF-8',
'Cache-Control': 'public, max-age=3600',
},
});
},
};
Deploy is one command:
npx wrangler deploy
That gives you a working URL at your-worker.workers.dev. The hard part is connecting your own domain.
Problem 1: The Apex Domain Isn't a Custom Domain
Cloudflare Workers lets you add Custom Domains under Workers & Pages → your worker → Domains. You type in your domain and it wires everything up automatically.
The catch: it doesn't accept apex domains (yourdomain.com). It only accepts subdomains (www.yourdomain.com, blog.yourdomain.com, etc.).
If you try typing yourdomain.com into the Custom Domain field, you get:
Only domains active on your Cloudflare account can be added.
Which is a confusing error because your domain IS active on your account. The real reason is that apex domains can't be CNAME'd — it's a DNS spec limitation, not a Cloudflare bug.
The fix: Add www.yourdomain.com as a Custom Domain. Cloudflare creates the Worker DNS record automatically. Then handle the apex separately.
Problem 2: The Wildcard Route Broke Everything
While trying to cover the apex, I added a Route: *.yourdomain.com/*. The intent was to catch all subdomains and route them to the Worker.
This broke both www and the apex. The Worker stopped responding entirely — curl was timing out on port 443.
The reason: the wildcard route was intercepting requests before the Custom Domain mapping could handle them, and since *.yourdomain.com/* doesn't match the apex, the apex got no handler at all.
The fix: Remove the wildcard route entirely. Routes and Custom Domains conflict when they overlap. Use one or the other, not both.
Problem 3: The Apex DNS Record
With the wildcard route gone, www.yourdomain.com worked but yourdomain.com still returned NXDOMAIN or a 522.
The issue: the DNS had a CNAME for the apex pointing to Cloudflare's default parking page. That record was blocking the Worker from owning the apex.
The sequence that worked:
- Delete the parking page CNAME for
@ - Cloudflare automatically creates a
Workertype DNS record for the apex - Add
www.yourdomain.comas a Custom Domain — this creates its own Worker DNS record forwww
Final DNS State
| Type | Name | Content | Proxy |
|---|---|---|---|
| Worker | yourdomain.com |
your-worker | ✅ On |
| Worker | www |
your-worker | ✅ On |
| MX | yourdomain.com |
mail server | ⬜ Off |
| TXT | yourdomain.com |
SPF record | ⬜ Off |
What I'd Do Differently
- Start with
wwwas the primary. Add it as a Custom Domain first, verify it works, then deal with the apex. - Don't touch Routes unless you have a specific routing need. For a simple single-Worker site, Custom Domains are enough.
- Delete the parking page CNAME early. It blocks the Worker from claiming the apex and the error messages don't make this obvious.
The Result
A static landing page served from Cloudflare's edge in ~50ms globally, with no origin server, no maintenance, and a $0 hosting bill. The only cost is the domain registration.
The Worker approach is underrated for personal sites. You get full control over the response — headers, caching, routing logic — without managing any infrastructure.
Next post: adding a blog to a Cloudflare Worker without a CMS.