☁️Cloudflare Tunnel: expose things cleanly
Put a service online on your own domain, over HTTPS, without opening a single port on your router. The tunnel goes out from your home, it doesn't come in.
Tailscale solves one problem: you, from your devices, reach your machine from anywhere, privately. Perfect for working. But sometimes you want the opposite: for the public to reach an app you’ve just set up. A demo to show a colleague, a dashboard for the team, a page you want to share with a simple link. Tailscale can’t do that, it’s private by nature.
The classic answer is port forwarding on the router. It’s fragile (your IP changes, the router reboots, the config breaks), and above all it exposes your home’s IP address to the whole world. Bad idea. Cloudflare Tunnel solves all of this in a smarter way.
The thing that changes everything: the tunnel goes out, it doesn’t come in
Here’s the idea to keep firmly in mind. A small program, cloudflared, runs on your mini-PC and opens a connection toward Cloudflare, just like your browser opens a connection toward a site. The public traffic that arrives at myapp.your-domain.com then comes back down through that same tunnel to your local app.
The concrete consequences, and they’re beautiful:
- No port open on your router. Nothing to configure on the Freebox/Livebox side.
- Your home IP stays hidden. The world sees Cloudflare, never your address.
- Free, automatic HTTPS. The certificate is managed by Cloudflare.
- DDoS protection included, because everything goes through their network first.
The named tunnel, the method that lasts
There’s an express tunnel (we’ll come back to it at the end), but for a service that needs to last over time, you create a named tunnel. It survives reboots, keeps the same config, and plugs in cleanly as a system service. Here’s the full procedure.
Install cloudflared on the mini-PC
We go through Cloudflare’s official apt repository, to get updates automatically:
# Add Cloudflare's GPG key
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
| sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
# Add the repository
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" \
| sudo tee /etc/apt/sources.list.d/cloudflared.list
# Install
sudo apt update && sudo apt install cloudflared
Connect cloudflared to your account
cloudflared tunnel login
This opens your browser. You choose the domain to authorize, and Cloudflare drops a certificate into ~/.cloudflared/. That’s what proves this tunnel has the right to act on your domain.
Create the named tunnel
cloudflared tunnel create mini
This creates a tunnel called mini and writes a credentials file in JSON (something like ~/.cloudflared/<a-long-id>.json). Note the identifier shown: we need it right after.
Write the config file
Create ~/.cloudflared/config.yml. It’s the one that says: “traffic from the mini tunnel arriving at such-and-such domain goes to such-and-such local service.”
tunnel: <your-tunnel-id>
credentials-file: /home/your-user/.cloudflared/<your-tunnel-id>.json
ingress:
# myapp.your-domain.com → the app running locally on port 8099
- hostname: myapp.your-domain.com
service: http://localhost:8099
# Everything else gets a 404, required at the end of the list
- service: http_status:404
The ingress block reads top to bottom. The last line http_status:404 is a required catch-all: without it, cloudflared refuses to start. You can add other hostname entries above it to expose several apps through the same tunnel.
Route the DNS
cloudflared tunnel route dns mini myapp.your-domain.com
This command creates the DNS record at Cloudflare that points your subdomain to the tunnel. Once per hostname.
Test, then install as a service
Run the tunnel by hand to check that everything answers:
cloudflared tunnel run mini
Open https://myapp.your-domain.com in a browser. If your local app responds, you’ve won. Stop it with Ctrl+C, then install it as a service so it survives reboots:
sudo cloudflared service install
A thing that’s exposed is a thing that’s public
Let’s be clear about the risk, because it’s real. As soon as you route a hostname, anyone on the Internet can knock at that door. So:
- Put authentication in front of anything sensitive. Cloudflare Access can filter by email (only the accounts you authorize get through), it’s free for a handful of users and it saves you from exposing an admin dashboard to all comers.
- Never tunnel your raw services: SSH, the Ollama API (port 11434), a database. Those stay on Tailscale, full stop. Cloudflare Tunnel is for web apps meant to be public.
The mental rule: Tailscale for you, Cloudflare Tunnel for the world. Before exposing anything, take a pass through Securing access to know what to lock down.
The express tunnel, for a five-minute demo
Don’t need a permanent tunnel? One command, no config, no domain:
cloudflared tunnel --url http://localhost:8099
Cloudflare spits out a random trycloudflare.com URL you can share right away. Ideal for showing something to a colleague in a meeting. But it’s ephemeral: the URL changes every launch and disappears when you stop the command. For a service meant to stick around, go back to the named tunnel above.