stayz3ro.dev

stayz3ro.dev on Cloudflare: one zone, a static blog and a VPS

The last post moved chrisalorenzo.com off GitHub Pages and ended with a note that stayz3ro.dev, this blog, was queued for the same treatment.

That move turned out easier and more interesting than the first one. Easier because the domain was registered fresh for this, so there were no legacy records to carry over. More interesting because this zone does not serve one thing. The apex is a static blog on Cloudflare Pages, and the subdomains point at a VPS.

The layout

NameTypeTargetProxy
stayz3ro.devCNAMEstayz3ro-dev.pages.devProxied
www.stayz3ro.devCNAMEstayz3ro-dev.pages.devProxied
status.stayz3ro.devA(VPS)DNS only

One Cloudflare zone, two backends. The blog is edge-hosted and fully proxied. status. (Uptime Kuma, with analytics. for Umami coming next) resolves straight to a Linux VPS running Caddy, which terminates its own Let’s Encrypt certificates. Those records are DNS-only, grey-cloud, with no Cloudflare proxy in front.

Cloudflare DNS records for stayz3ro.dev: apex and www as proxied CNAMEs
to stayz3ro-dev.pages.dev, a DNS-only A record for status, and three
DNS-only TXT records for DMARC, DKIM and SPF

Step 1: nameservers

Same as last time. Registration stays at Porkbun, only the nameservers move. Add the site in Cloudflare on the free plan, take the two assigned nameservers, and set them as authoritative at the registrar.

Porkbun domain details for stayz3ro.dev with authoritative nameservers
set to felipe.ns.cloudflare.com and melissa.ns.cloudflare.com

Because the domain was new, the zone Cloudflare imported was empty. Nothing to audit, nothing to preserve. The part that made the chrisalorenzo.com move fiddly (MX records, an SPF record, a URL-forward, all living in DNS) just wasn’t there.

Step 2: the Pages project

Workers & Pages, connect the stayz3ro-dev repo, and Astro’s preset fills in the rest.

SettingValue
Build commandnpm run build
Build output directorydist
Production branchmain
Automatic deploymentsEnabled

Cloudflare Pages build configuration for stayz3ro-dev: git repository
connected, build command npm run build, output directory dist, production
branch main, automatic deployments enabled

Every push to main builds and deploys. Branch pushes get their own preview URL. Merging the pull request for a post is the entire publish step.

Cloudflare Pages deployments list: production deployments from main tied
to their commits, plus a preview deployment from a post branch

Step 3: custom domains

In the Pages project, add stayz3ro.dev and www.stayz3ro.dev. Because DNS is already on Cloudflare, each is one click. Cloudflare writes the CNAME itself, flattens it at the apex, and provisions the certificate.

Cloudflare Pages custom domains: stayz3ro.dev and www.stayz3ro.dev both
Active with SSL enabled

Step 4: TLS end to end

SSL/TLS mode → Full (strict). Pages serves a valid certificate on its side and the VPS serves its own, so there is no reason to run anything looser. Full (strict) validates the origin certificate on every hop. Flexible or plain Full would leave a gap I don’t need.

Cloudflare SSL/TLS overview for stayz3ro.dev: encryption mode Full
(strict), browser to Cloudflare to origin all encrypted

Step 5: lock the mail shut

stayz3ro.dev will never send email. Cloudflare’s DNS panel flags that as a spoofing risk, and it’s right. A domain with no policy is one anyone can forge From: addresses on. Three DNS-only TXT records close it:

RecordValueEffect
stayz3ro.dev TXTv=spf1 -allno host is authorised to send
*._domainkey TXTv=DKIM1; p=null key, every DKIM signature fails
_dmarc TXTv=DMARC1; p=reject; sp=reject; adkim=s; aspf=sreject anything that slips through, subdomains included

Five minutes, and the domain is unspoofable for mail. Worth doing on every domain that only serves web.

What the VPS side buys

Keeping status. (and soon analytics.) on the VPS instead of proxying them through Cloudflare is deliberate:

  • Caddy already manages those certificates and access logs. Putting Cloudflare in front would mean two TLS layers and a second place to reason about caching and headers.
  • The blog and the services fail independently. A Pages build breaking doesn’t touch the status page. The VPS going down doesn’t touch the blog.
  • It keeps the VPS doing what it’s for, running actual services, while the static content lives somewhere I never have to patch.

status.stayz3ro.dev in a browser: valid HTTPS padlock, Uptime Kuma
loaded, served from the VPS on the same domain as the Pages-hosted
blog

Closing note

A few things I’d repeat. Register the domain first and point DNS second, because a greenfield zone has no load-bearing records you forgot about. One zone can serve many backends, and per-record proxy settings decide what goes through Cloudflare and what resolves straight through. Match the TLS mode to reality: both origins present real certificates, so Full (strict) is the only correct setting. And harden mail on web-only domains, because SPF -all, a null DKIM key, and DMARC p=reject cost nothing and remove a whole class of abuse.

The next record in this zone is analytics.stayz3ro.dev, Umami on the VPS, the first service here that needs a database. That’s its own post.