No Open Ports: Exposing a Home Server Safely with Cloudflare Tunnel
The problem with running your own services from home
The moment you self-host anything you actually want to reach from outside your house, you hit the same fork in the road: open a port on your router and hope you configured everything else correctly, or find a way to reach your services from the outside without ever exposing your home network’s IP address in the first place. I went with the second option, and it’s the one piece of infrastructure almost every other project in my homelab depends on.
What it does
Cloudflare Tunnel runs a small background service, cloudflared, that opens outbound-only connections from my server to Cloudflare’s network. I map subdomains — plex.mydomain.com, warden.mydomain.com, and so on — to local ports, and Cloudflare routes public traffic through that tunnel. Nothing inbound touches my router at all, and every subdomain gets free TLS automatically.
The real prerequisite nobody mentions first
Before any of that works, you need a domain — that’s the actual prerequisite, and the tunnel is just what you do once you have one. I priced this out across a few registrars (Cloudflare Registrar itself sells at cost with no renewal price jump, Hostinger bundles domain/hosting/email together, Porkbun and Namecheap often undercut both on first-year pricing but not necessarily at renewal) and landed on the option that kept domain and hosting in one dashboard. For anyone not ready to commit to a paid domain yet, a dynamic DNS service like DuckDNS is a legitimate way to test the whole pattern for free — you just lose the “your own subdomain for everything” benefit that makes owning a domain worth it long-term.
Two ways to run it, and why I picked the harder one
Cloudflare actually supports two setup paths, and I want to be upfront that the “easy” one is genuinely fine for a lot of people:
- Dashboard-managed: click through the Cloudflare UI, no config file to write, survives a full OS reinstall since the config lives in the cloud. The tradeoff is every hostname change is a dashboard click, and there’s no single place to see “everything this tunnel exposes” at a glance.
- Config-file managed — what I actually run: one
config.ymlwith atunnel:UUID, acredentials-file:, and aningress:list mapping each hostname to a local service, ending in a required catch-allhttp_status:404.
I chose the config-file route because I wanted the config to be something I could read end-to-end, back up, and eventually put under version control — a single file that answers “what does this tunnel expose” is worth the extra YAML-editing comfort it requires. It also scales better: adding a tenth service is one more ingress entry, not a tenth dashboard session.
What was actually tricky
The ingress list is order-sensitive and has to end with that http_status:404 catch-all or the whole config is invalid — an easy thing to get wrong once, and mildly infuriating to debug the first time. Backend services with self-signed certificates (a NAS admin UI, for instance) need originRequest: noTLSVerify: true on that specific ingress entry rather than falling back to plain HTTP, which would undo the whole point of running behind TLS. And it’s worth actively pruning old tunnels you’re not using — an abandoned tunnel with valid credentials sitting in your Cloudflare account isn’t a security hole by itself, but it’s exactly the kind of stale credential that becomes a problem later if you forget it exists.
What it demonstrates
This is infrastructure-layer thinking: understanding why inbound port-forwarding is the riskier default, not just following a tutorial that says to avoid it. Picking the config-file path over the dashboard path was a deliberate tradeoff — more upfront friction in exchange for something auditable and repeatable — which is the same tradeoff that shows up constantly in real infrastructure work. And because nearly every other service I run sits behind this tunnel, getting it right the first time mattered more than usual: a mistake here doesn’t just break one service, it’s the front door for all of them.
Part of the Homelab AI Blueprints series — see all nine blueprints for building and operating a self-hosted, AI-agent-run home server.
Discover more from Michael Allen
Subscribe to get the latest posts sent to your email.
