Cloudflare Tunnel: exposing your homelab without opening a single port

Publishing a self-hosted service with no port forwarding and no static IP: how an outbound tunnel actually works, what it really protects, and the pitfalls that break an installation in production.

The classic reflex for reaching your NAS or your server from outside is port forwarding: open 443 on the router, point it at the machine, done. Except that amounts to publishing your homelab to the entire internet. Mass scanners find the port within hours — not because anyone is targeting you, but because the whole IPv4 space is swept continuously.

An outbound tunnel solves the problem from the other end: instead of letting traffic in, you go out.

The principle: reverse the direction of the connection

A connector installed on your network opens a persistent outbound connection to Cloudflare's infrastructure. When a visitor requests your domain name, the request lands at Cloudflare, which sends it back down that already-established channel to the connector, which hands it to the local service.

Three direct consequences:

  • No inbound port to open. Your firewall can drop every bit of inbound traffic and the tunnel keeps working.
  • Your public IP is never exposed. Visitors only ever see Cloudflare addresses. Your home line stops being a target.
  • The IP can change freely. No static IP and no dynamic DNS needed: you are the one initiating the connection.

What it protects, and what it does not

This is the point worth stating plainly, because the shortcut "it's behind a tunnel, so it's secure" does real damage.

What genuinely goes away: port scanning, direct exploitation of an exposed service, brute-force attempts against an open port, and volumetric DDoS — absorbed upstream by Cloudflare.

What does not change: legitimate HTTP traffic still reaches your application. An admin interface with no password still has no password, an outdated version is still vulnerable, an injection is still injectable. The tunnel moves the network boundary; it does not harden the software.

Worth remembering: a tunnel with no authentication layer in front of it merely makes your service reachable more cleanly. It does not decide who is allowed in.

The layer that often goes missing: authentication

This is where Cloudflare Access comes in. It sits ahead of the tunnel, before the request ever reaches it, and enforces authentication — an identity provider, a one-time code by email, or a service token for machine access.

The difference is structural: an unauthenticated visitor is rejected at the edge of Cloudflare's network, without ever touching your machine. Your service only ever sees traffic that has already been validated. For anything that looks like an admin interface, a dashboard, or a personal service, this is the tunnel's mandatory companion — not a comfort option.

For automated calls that cannot go through a login screen, service tokens keep the protection in place without breaking your integrations.

The pitfalls that break an installation

The connector started by hand

The most mundane failure there is. The connector gets started in a terminal to test, everything works, and you move on to something else. The first time the machine reboots, it all goes down. It has to be installed as a supervised service, with automatic restart — exactly like the service it publishes.

The connector with too much reach on the network

The tunnel removes external exposure, but the machine hosting it keeps full access to the local network. If it gets compromised, it becomes a way into everything else. The connector should be able to reach the service it publishes, and nothing else. A dedicated container on a restricted network is healthier than a binary running as root on the hypervisor.

The missing catch-all rule

The ingress configuration maps hostnames to internal destinations, and it is evaluated in order. Without an explicit final rule that rejects everything else, a request matching no hostname can land on an unintended destination. The last rule must always be a deny.

The wrong port in the configuration

A quiet pitfall, and a brutally time-consuming one: a rule pointing at a port where nothing is listening does not raise a configuration error. The tunnel comes up normally, the domain answers, and you end up with inconsistent behavior depending on the path. After any ingress change, check that the target port really matches the service that is actually listening.

The limits to know about before you commit

The tunnel routes your traffic through a third party, which decrypts it in order to apply its rules. For a personal service that is a reasonable trade-off; for regulated data, the question deserves to be raised up front rather than afterwards.

Beyond that, everything hangs on a single provider: an outage on their side makes your services unreachable even though your machines are running perfectly. That is the price of the simplicity, and it is better paid knowingly.

In summary

The outbound tunnel is the right answer to a bad reflex. It eliminates port forwarding, static IPs and direct exposure — three major causes of homelab compromise — for a very small implementation cost.

But it replaces neither authentication, nor updates, nor network segmentation. A tunnel in front of an unauthenticated, unmaintained service is a better-hidden door on a lock that is still broken. The tunnel handles exposure; the rest of the work is still yours.

Frequently asked questions

Does a Cloudflare tunnel replace a VPN?
No, they answer different needs. A VPN puts you inside the network and gives you access to everything on it, with a client to install. A tunnel publishes one specific service at a URL, reachable from any browser with no client at all. For admin work across several machines the VPN is still the better fit; for sharing a service with third parties the tunnel is far simpler.
Does the tunnel protect a vulnerable service?
No, and this is the most expensive misunderstanding of all. The tunnel removes network exposure: no open port, nothing left to scan, no public IP to target. But legitimate HTTP traffic still reaches your application. An authentication flaw or an injection remains exploitable exactly as before. The tunnel changes the attack surface; it does not fix the code.
Do you absolutely need a static IP or dynamic DNS?
No, and that is precisely the point. Since the connection goes from your network out to Cloudflare, your public address is irrelevant and can change at any time. That removes the need for a static IP, for DynDNS, and for port forwarding on the router — three classic sources of breakage.
What happens if the machine hosting the connector reboots?
The tunnel drops and the service becomes unreachable. This is the most common failure on hand-rolled setups: the connector is launched in a terminal, it survives the session but not the reboot. It has to run as a supervised service, with automatic restart, on exactly the same footing as the service it publishes.
Can you expose several services through a single tunnel?
Yes. One connector carries several ingress rules, each mapping a hostname to an internal destination. That is the recommended layout: one tunnel per machine rather than one tunnel per service, with a catch-all rule that rejects anything not matching a declared hostname.

Did you enjoy this article?

Comments

Morgann Riu

Cybersecurity and Linux administration expert. I help companies secure and optimize their critical infrastructures.

Back to the blog

Checklist Sécurité Linux

30 points essentiels pour sécuriser un serveur Linux. Recevez aussi les nouveaux tutoriels par email.

Pas de spam. Désabonnement en 1 clic.