Building a Secure Hetzner VM: Cloudflare Tunnel, Tailscale, and a Locked-Down Firewall

The previous two posts covered two separate pieces: routing public traffic through a Cloudflare Tunnel, and reaching a server for administration through Tailscale. This post connects them, end to end, starting from an empty Hetzner account and finishing with a VM that has no open inbound ports at all — not even one.


1. Provision the VM in Hetzner

  • Create a new project in the Hetzner Cloud Console

  • Add a server: pick a nearby location, a minimal Debian or Ubuntu image, and the smallest instance type that fits the workload — this kind of setup doesn’t need much horsepower

  • Add your SSH public key during creation so root password login is never even enabled

  • Skip enabling any extra public services at this stage; we’ll lock the network down before anything else touches it


2. Attach a Deny-by-Default Hetzner Firewall

Before installing anything else, attach a Hetzner Cloud Firewall to the server with no inbound rules at all. All outbound traffic stays allowed, since both Cloudflare Tunnel and Tailscale only ever initiate outbound connections — the VM reaches out to them, never the other way around. That single firewall, with an empty inbound rule set, is what makes the rest of this setup safe: even if a service on the VM were misconfigured later, there’s no network path in for it to be reached through.


3. Set Up the Cloudflare Tunnel

Follow the steps from the Cloudflare Tunnel post: install cloudflared, authenticate it, create the tunnel, and point its ingress rule at whatever is serving the site locally (port 80 on the VM). Since the firewall has no inbound rules, this is the only way public traffic reaches the server, and it does so without a single port being opened for it.


4. Join the Tailnet for SSH

Follow the steps from the Tailscale post: install Tailscale on the VM, authenticate it into the same tailnet as your laptop, and enable Tailscale SSH. Because the Hetzner firewall already blocks all inbound traffic, port 22 was never reachable from the public internet in the first place — Tailscale just gives you, specifically, a private path back in.


5. The Resulting Architecture

Layer Handles Reachable by
Hetzner Cloud Firewall Default-deny inbound safety net Nobody — zero inbound rules
Cloudflare Tunnel Public website traffic Anyone, via Cloudflare’s edge
Tailscale SSH / administrative access Only devices enrolled in the tailnet

Three layers, each doing exactly one job, and none of them requiring a single open inbound port on the VM itself. The firewall stays closed by default because it never has to be opened — both the traffic visitors send and the traffic you send as an admin arrive through connections the VM initiated outward, not ones the internet initiated inward.