Tailscale vs Cloudflare Tunnel vs Bore: What I Use For My Daemon's Tunnel
For private remote access to a homelab box: Tailscale. For exposing a self-hosted web app to the public: Cloudflare Tunnel. For embedding "give this binary a public URL" inside another binary you ship: bore. They're not really competitors — different problems, picked the right one for each.
Anyone who's run a homelab has fought the "how do I reach this from outside" problem. The popular three answers each solve a different version of the question. People online compare them as if they're substitutes; XDA's writeup gets it right that the answer depends on what you're exposing and to whom.
The three jobs
| Job | Best fit |
|---|---|
| "My laptop should reach my homelab from anywhere" | Tailscale |
| "My self-hosted dashboard should have a public HTTPS URL" | Cloudflare Tunnel |
| "My binary should expose a port without an account or signup" | bore (or ngrok / cloudflared anonymous mode) |
Tailscale
What it is: a peer-to-peer mesh built on WireGuard. Every device has a stable Tailscale IP. Devices connect to each other directly when they can; relay through DERP when they can't (CGNAT, symmetric NAT).
What it's great at: private networking. Your laptop sshes to my-server.tail-scale.ts.net from any network, any time. Latency is excellent because most pairs find a direct path.
What it's not: a public-internet front door. Tailscale Funnel exists but is a partial story — it gives you one HTTPS URL routing to one Tailscale device; not a full reverse-proxy story.
Cost: free for personal use up to 100 devices. Paid plans for teams.
I use it for: ssh, internal services that should never be public, sharing a NAS with my partner.
Cloudflare Tunnel
What it is: an outbound persistent connection from your machine (cloudflared) to Cloudflare's edge. The edge proxies inbound HTTPS to your service. CGNAT-friendly because all connections are outbound.
What it's great at: public web apps. You get a real HTTPS URL on your domain (or a *.trycloudflare.com subdomain). Cloudflare WAF, caching, DDoS in front for free.
What it's not: a private network. Anyone with the URL can hit your service. You can layer Cloudflare Access on top for auth, but that's a separate product.
Cost: free.
I use it for: anything I want shareable via URL — Plex, public web demos, ad-hoc "let my mom watch this stream."
bore
What it is: a tiny Rust reverse-tunnel binary. ~5MB. No account, no auth (you bring your own). One command opens a tunnel between a port on your machine and a port on a relay server.
What it's great at: embedding inside another binary. The relay can be your own — bored is the server. The client is a single static binary you ship.
What it's not: production-grade exposure. No TLS termination, no auth, no traffic shaping out of the box. You build those on top.
Cost: free. (Run your own relay on a $5/mo VPS.)
Bernato used to ship the same embedded-client shape with a bundled frpc; today it ships no tunnel binary at all. When the dashboard can't reach 127.0.0.1:33120, the daemon's single outbound WebSocket connection to the Bernato broker carries the traffic, reverse-proxied to a per-machine host on *.bernato.dev. The whole thing is invisible to the user.
The case where they overlap
For a single-developer homelab where you want both private access (ssh to the box) and a public URL for one specific service, you can run Tailscale + Cloudflare Tunnel side by side. They don't fight. Tailscale handles the device-to-device case; Cloudflare Tunnel handles the public-URL case.
What about ngrok?
ngrok is the original. It does everything — public URLs, custom domains, TCP tunneling. Free tier is severely limited (random URL, low rate limit). The paid plans are fine but expensive for casual use. Hookdeck has been quietly eating ngrok's developer-tooling lunch with a free tier and an MCP server for AI agents.
ngrok and bore solve the same problem; bore solves it as code you can vendor; ngrok solves it as a SaaS you pay for. Pick based on whether you're embedding it.
Why Bernato uses an embedded tunnel
- It's a single binary shipped inside the Go daemon and exec'd as a child.
- The daemon opens a single outbound
wss://connection to the Bernato broker — no bundled tunnel binary, no inbound ports, no third-party tunnel service. - nginx in front terminates TLS. Let's Encrypt. CAA-pinned to LE + four other CAs. The user's traffic is HTTPS end-to-end.
- Per-machine hosts:
<uid6>-<machine-slug>.bernato.dev. Routing by Host header. No one machine can impersonate another. - The daemon authenticates the connection with its Firebase identity and still enforces its own auth and capability gates on every relayed request.
Result: zero user setup. The user doesn't sign up for ngrok. They don't run cloudflared. They don't install Tailscale. The daemon ships, runs, and "off-LAN works." That's the goal.
What this means for you
If you're building a tool that needs reachability and you don't want the user to manage tunnel auth: embed a tunnel client (bore, frp, or ngrok-paid if you want SaaS) and run your own relay. If you're building for yourself: Tailscale + Cloudflare Tunnel side by side covers both jobs and costs $0. Don't pick one of the three; pick the right one for each problem you actually have.
FAQ
Can I use Tailscale Funnel instead of Cloudflare Tunnel for public URLs?
Yes for simple cases. You get one URL per device, three devices max on the free plan. For more devices or custom domains, Cloudflare Tunnel is still better.
Why not just use SSH reverse port forward?
It works. You need a public-IP server somewhere to forward through, you handle reconnects yourself, no TLS termination. frp + nginx is the productionized version of exactly this pattern.
Is bore safe?
The protocol is simple — TCP tunneled inside TCP. Auth is whatever you put on top. For Bernato, the broker requires the daemon's Firebase identity on the outbound connection, and the daemon still requires a Firebase ID token check on every relayed request. Without those, anyone hitting the public URL gets a 401.
Does bore work behind CGNAT?
Yes — same reason Cloudflare Tunnel does. The connection is outbound from your machine to the relay; the inbound side is the relay's public IP.