Skip to content
minimachine.
← The path
Step 15 · Networking Intermediate · 15 min

☁️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.