NetBird vs Headscale vs Cloudflare — Remote Access Comparison¶
Date: 2026-07-05
Scope: Access layer only, evaluated against revised requirements. Follows up on 18-ARCHITECTURE-ALTERNATIVES-PROPOSAL.md.
Revised Requirements¶
- ~100 remote RPis; MQTT (EMQX, mTLS) stays unchanged — independent of this decision.
- Admin plane: push-based Ansible when the operator decides, plus on-demand SSH / Node-RED editor access. (Pull-based convergence explicitly rejected.)
- User plane: end users access the per-device Node-RED dashboard in a browser to fine-tune parameters (dashboard widgets may write values), but must not be able to modify flows or reach the editor/admin API.
The Deciding Constraint: Clientless Browser Access¶
End users open a dashboard URL in a browser. This splits the candidates immediately:
| Cloudflare Access + Tunnel | NetBird | Headscale | |
|---|---|---|---|
| User opens URL, logs in with Google, sees dashboard | ✅ Native (clientless ZTNA) | ❌ Requires NetBird client installed + enrolled on every user machine | ❌ Requires Tailscale client installed + enrolled |
| Path-level restriction (dashboard yes, editor no) | ✅ Tunnel ingress path rules + Access policies per app | ⚠️ Network-level only (IP:port); path control needs a reverse proxy on each device | ⚠️ Same — ACLs stop at IP:port |
Mesh VPNs are network access tools. Once a user's machine is on the mesh and allowed to reach device:1880, nothing in NetBird/Headscale distinguishes /dashboard from /admin — that enforcement would have to live on each device (nginx or Node-RED itself). Cloudflare enforces it before traffic ever reaches the device.
Conclusion for the user plane: Cloudflare is effectively the only fit unless every end user installs and maintains a VPN client. NetBird/Headscale compete only for the admin plane.
Three-Way Comparison (admin plane)¶
| Criterion | Cloudflare Tunnel + Access | NetBird (self-hosted) | Headscale |
|---|---|---|---|
| Architecture | Outbound cloudflared per device → CF edge; SSH via cloudflared access ssh ProxyCommand |
WireGuard mesh; self-hosted control plane (single unified binary since v0.65); direct peer-to-peer where NAT allows | Self-hosted reimplementation of Tailscale control server; official Tailscale clients; embedded DERP relay |
| Ansible push | Works today. Every SSH connection proxied through CF edge → added latency per connection; 100-host play noticeably slower than direct | Devices get stable mesh IPs/DNS names; native WireGuard SSH ≈ LAN-like; fastest plays | Same as NetBird (WireGuard direct); relay fallback through single DERP |
| On-demand connect | cloudflared access ssh or browser-rendered SSH terminal |
ssh device.netbird.local once admin client running |
ssh device.tailnet via Tailscale client |
| Access control | Access policies per hostname/path; IdP-integrated (Google/GitHub already configured) | Group-based ACL policies in web UI; posture checks (OS, client version, geo, process) | ACL policy file; no dynamic ACLs; OIDC groups not usable in ACLs |
| Self-hosted infra to run | None | 1 server (unified binary + Coturn); built-in IdP since v0.62 — no external IdP needed | 1 binary + SQLite; embedded DERP = single point of failure for relayed peers |
| Feature gating | Free ≤50 users, 1000 tunnels, 500 Access apps; >50 users → $7/user/mo for all | Self-hosted: full feature set, no seat caps, free | Free, open source; community project — Tailscale Inc. does not support it; upgrades must track Tailscale client compatibility |
| Maturity / ops risk | Managed, SLA on paid; vendor dependence | All-OSS product company; more moving parts historically, much simpler since 2026 releases | Solid at ~100 nodes, but SPOF, upgrade coupling, HA requires extra work (e.g. k8s operator) |
| Device footprint | cloudflared daemon (already deployed) |
netbird agent (WireGuard, kernel module — light) |
tailscaled (light) |
| Terraform support | Full (already in repo) | Provider exists (netbirdio/netbird) | Limited; ACL file + CLI/API |
Cost at 100 devices, <50 users¶
| Option | Monthly | New infra to operate |
|---|---|---|
| Cloudflare only (status quo, tightened) | $0 | none |
| Cloudflare (users) + NetBird (admin) | $0 + VPS you already have (can co-locate on EMQX host) | NetBird server |
| Cloudflare (users) + Headscale (admin) | $0 + same | Headscale server (SPOF unless HA work) |
| Cloudflare (users) + Tailscale SaaS (admin) | ~$50–100 (tagged devices >50 @ $1/mo) | none |
Node-RED: users fine-tune parameters, cannot touch flows¶
Independent of the access-layer choice — do both:
- Path enforcement at the edge (Cloudflare): dashboard hostname's tunnel ingress routes only the dashboard path (
/dashboardfor Dashboard 2.0,/uifor classic — include the websocket sub-paths, e.g./dashboard/socket.io/*), catch-all → 404. Editor hostname (admin-only Access policy) routes/. This replaces the current Host-header middleware insettings.js— enforcement moves out of Node-RED into infrastructure users cannot reach around. adminAuthas backstop on every device: admins get"*"permission; end users get no editor accounts at all. Dashboard widgets run onhttpNodeRoot, completely separate from the admin API — users can write parameters through widgets without any editor permission. Even if someone reaches port 1880 directly, the editor demands a login.
Parameter writes via dashboard widgets flow through the flows themselves (inject → change → persist), never through the admin API, so "users adjust setpoints but can't edit flows" is exactly the boundary these two layers enforce.
Recommendation¶
- User plane: stay on Cloudflare. Clientless browser access + IdP login + path-level policy is the requirement, and only Cloudflare provides it without installing clients on user machines. Free at current scale (1000-tunnel limit is 10× headroom).
- Tighten Node-RED now (both steps above) — this is the real security gap and costs nothing.
- Admin plane: keep Cloudflare tunnels for now. Push Ansible over
cloudflared access sshalready works; the pain is only speed/latency. If 100-host plays become too slow or you want direct low-latency SSH: - Add NetBird self-hosted on the EMQX VPS for the admin plane only. Preferred over Headscale: full web UI + group ACLs + posture checks, all-OSS with vendor backing, built-in IdP, no DERP single point of failure coupled to a community project's release cycle.
- Pilot on 2–3 devices; Ansible inventory simply gains mesh IPs as
ansible_host. Cloudflare SSH hostnames stay as break-glass. - Headscale only makes sense if you specifically want official Tailscale clients and accept static-file ACLs, no OIDC groups in ACLs, and single-DERP SPOF. For this fleet NetBird dominates it.
Sources¶
- Cloudflare One account limits (1000 tunnels, 500 apps) · Zero Trust plans · 50-user free plan discussion
- NetBird self-hosted vs cloud (no feature limits) · access control: groups & policies · self-hosting quickstart · NetBird vs Tailscale
- Headscale ACLs · DERP reference · production-readiness issue #1534 · Headscale caveats write-up
- NetBird vs Headscale vs Tailscale 2026 · Tailscale pricing 2026 analysis
- Node-RED — Securing Node-RED (adminAuth)