← Back to blog

Hosting a Personal Site on Cloudflare Workers with a Custom Domain — What Nobody Tells You.

Three problems no tutorial mentions: apex domains, wildcard routes, and DNS parking pages.

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:

  1. Delete the parking page CNAME for @
  2. Cloudflare automatically creates a Worker type DNS record for the apex
  3. Add www.yourdomain.com as a Custom Domain — this creates its own Worker DNS record for www

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 www as 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.