Contents

Tailscale vs Netbird: Self-Hosted Mesh VPNs Compared

Two WireGuard meshes, one of which you can actually own end to end

Contents

I have lost count of the number of times I have recommended Tailscale to people. It is, genuinely, the easiest way to get a flat, encrypted network across machines that have no business being on the same subnet — your laptop in a café, a VPS in Frankfurt, the NAS in the cupboard under the stairs. You install it, you log in, and suddenly everything can ping everything else over WireGuard with no port forwarding and no faff. It feels like magic. If you want the gentle introduction to that magic before this comparison, I wrote one up in Tailscale: a zero-config mesh VPN; this piece assumes you already know what a tailnet is and want to know whether to own the control plane yourself.

But there is a catch I have learned to be honest about: the coordination server — the bit that hands out keys and tells nodes where to find each other — is Tailscale’s, not yours. The data plane is peer-to-peer WireGuard, so your traffic isn’t flowing through their servers. The control plane, however, is a SaaS you don’t run. For most people that’s fine. For some of us it itches. This is where Netbird, and Tailscale’s own open-source control server Headscale, come in.

How they actually differ

Advertisement

Both Tailscale and Netbird build a WireGuard mesh. The honest difference is governance.

Tailscale’s client is open source; its coordination server is not. You can swap in Headscale, a community reimplementation of that control server, and self-host the whole thing — but Headscale is a separate project, not blessed by Tailscale, and you’re slightly off the beaten path.

Netbird, by contrast, is open source top to bottom — client, management server, signal server, relay, and a built-in identity layer — and is explicitly designed to be self-hosted. It uses WireGuard for the data plane, coordinates connections through its signal server, and falls back to a relay (a Coturn-based TURN server, in WebRTC terms) when a direct tunnel can’t be punched. Since version 0.29 the server side consolidated: management and signal now share ports via HTTP/2 negotiation, and the default deployment can run management, signal, relay and STUN in a single combined server, which makes self-hosting markedly less fiddly than it used to be. It also ships a tidy web dashboard.

So the real comparison is: polished SaaS with optional unofficial self-hosting (Tailscale + Headscale) versus self-hosting as a first-class, supported path (Netbird).

Standing up Netbird

Netbird’s quick-start is a single Docker Compose stack. After cloning their infra repo and running their setup script you’ll have something resembling this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
  management:
    image: netbirdio/management:latest
    restart: unless-stopped
    volumes:
      - ./management.json:/etc/netbird/management.json
      - netbird-mgmt:/var/lib/netbird
    command:
      - "--port=80"
      - "--log-file=console"
      - "--disable-anonymous-metrics=true"
  signal:
    image: netbirdio/signal:latest
    restart: unless-stopped
  dashboard:
    image: netbirdio/dashboard:latest
    restart: unless-stopped
    environment:
      - NETBIRD_MGMT_API_ENDPOINT=https://netbird.example.com

volumes:
  netbird-mgmt:

You point an identity provider at it (Netbird bundles Keycloak in the full deploy, or you can wire up your own OIDC), and then on each machine:

1
2
3
4
5
6
7
$ curl -fsSL https://pkgs.netbird.io/install.sh | sh
$ netbird up --management-url https://netbird.example.com
Please do the SSO login in your browser.
Connecting to NetBird... Connected.
$ netbird status
Peers count: 4/4 Connected
NetBird IP: (assigned from your configured mesh CIDR)

Compare that to a Tailscale node pointed at self-hosted Headscale, which is almost identical in feel:

1
2
3
4
$ tailscale up --login-server https://headscale.example.com
$ tailscale status
nas          user   linux   -
laptop       user   linux   active; direct

(I have trimmed the mesh IP addresses from the output above; they come from the CGNAT range and are stable per device, but they aren’t the interesting part.)

The --login-server flag is the whole trick for pointing Tailscale at Headscale instead of Tailscale’s own servers.

Access control: the part you’ll actually live in

Advertisement

For a mesh with more than a handful of nodes, the flat “everything reaches everything” default is a liability, and how each tool lets you carve it up is where day-to-day life diverges. Both use a group-and-policy model rather than raw IP rules, which is the right abstraction — you reason about roles, not addresses.

Netbird’s dashboard makes this genuinely pleasant. You define groups in the web UI, assign machines and users to them, and write policies as plain statements: the dev group may reach the databases group on port 5432, and nothing else. Because it is visual, an access review is something you can do with your eyes rather than by diffing a config file, and that matters when you’re the one who has to justify the rules six months later.

Headscale expresses the same ideas through an ACL policy file (HuJSON, Tailscale’s commented-JSON dialect):

1
2
3
4
5
6
7
{
  "groups": { "group:dev": ["alice", "bob"] },
  "tagOwners": { "tag:db": ["group:dev"] },
  "acls": [
    { "action": "accept", "src": ["group:dev"], "dst": ["tag:db:5432"] }
  ]
}

It is powerful and version-controllable — a real advantage if you like your infrastructure in git — but it is a text file you edit and reload, not a console you click through, and the feedback loop when a rule is wrong is slower. Which of these you prefer is largely temperament: dashboard people and config-file people rarely convert each other.

Clients and platform reach

A mesh is only as useful as the devices it covers, and this is Tailscale’s clearest lead. The official client is polished on every platform that matters — macOS, Windows, Linux, iOS, Android, even Apple TV and various NAS firmwares — with an app-store presence and an interface your non-technical family members can actually operate. Point that same client at Headscale and most of it works, but you are relying on an unofficial server keeping pace with an official client, and occasionally a feature expects server support Headscale hasn’t shipped.

Netbird’s clients are good and cover the major desktop and mobile platforms, but the ecosystem is younger and thinner at the edges — fewer embedded-device integrations, a smaller community when something obscure breaks. For a fleet that is mostly laptops and phones, either is fine. If you need to get a mesh client onto an odd platform, check support before committing, because that is exactly where the two diverge.

Where each one earns its keep

Netbird’s web dashboard is its real selling point. Access control via “groups” and policies is genuinely pleasant — you define groups, write rules like “the dev group may reach the databases group on port 5432”, and it just works. Headscale, by contrast, has historically been an ACL-file-and-CLI affair; capable, but you live in config.yaml and JSON policy files.

Tailscale’s edge is maturity and the surrounding features: MagicDNS, exit nodes, subnet routers, Taildrop, and an app-store-grade client on every platform including iOS and Android. Headscale supports a good chunk of this but lags the upstream, and you will occasionally find a feature that the official client expects the official server to provide.

NAT traversal is roughly a wash — both lean on WireGuard plus a relay (DERP for Tailscale, the Coturn-based TURN relay for Netbird) when a direct connection can’t be punched. In practice you’ll see most peers connect directly after a few seconds of negotiation, with the relay catching only the awkward double-NAT cases. Worth noting too: with Netbird you’re running and updating the signal and management servers yourself, which is one more thing to monitor, whereas the official Tailscale path has none of that operational burden.

The identity provider is where the evening goes

Both self-hosted paths share the same tax, and it is worth naming plainly because it dominates the setup experience: you need an OIDC identity provider, and standing one up is where the time actually goes.

Netbird bundles Keycloak in its full deploy, which is comprehensive and correspondingly heavy — realms, clients, redirect URIs, and a first-login admin ritual that is easy to fumble. You can point Netbird at an existing provider instead (Authentik, Zitadel, Auth0, your Google Workspace), and if you already run one, do that; you’ll save yourself an hour. Headscale takes a different tack: it can run with no external IdP at all, minting pre-authenticated keys from the CLI, which is genuinely simpler for a handful of personal machines but doesn’t scale to per-user access control the way an IdP does.

My honest advice: decide the identity question first, before you touch either mesh. If you want single sign-on and group-based policies, budget the evening for Keycloak or Authentik and treat the VPN as the easy part that comes after. If you just want three of your own boxes to reach each other, Headscale with CLI auth keys is the path of least resistance.

Gotchas and how to fix them

The failures I’ve actually hit self-hosting these:

  • The management server’s public URL must be exactly right. Both Netbird’s NETBIRD_MGMT_API_ENDPOINT and Headscale’s server_url are baked into what clients are told to dial. Get the scheme, port or trailing behaviour wrong — or put it behind a reverse proxy that rewrites paths — and clients register but never fully connect. Set it to the externally reachable HTTPS URL and don’t proxy-rewrite the coordination paths.
  • TLS is not optional and self-signed certificates cause grief. The coordination servers expect valid TLS. A self-signed cert leads to clients silently refusing to trust the server. Terminate TLS with a real certificate (an ACME reverse proxy in front is the tidy answer).
  • Headscale lags upstream features. A shiny Tailscale client feature — a new Taildrop behaviour, an ACL syntax — may expect server support Headscale hasn’t shipped yet. Pin your client versions to what your Headscale release supports, and read the compatibility notes before upgrading clients.
  • Nobody notices when the control plane dies. Because the data plane keeps existing tunnels alive after the coordination server goes down, a dead management server is invisible until a device tries to reconnect — usually at the worst moment. Put the coordination server behind a health check. I point Uptime Kuma at the management endpoint so a failed control plane pages me before a roaming laptop discovers it the hard way.

The verdict

If you want the least friction and you can live with a third-party control plane, use Tailscale proper. It is the best in class and I still run it for my own laptops.

If self-hosting the control plane is a hard requirement, the choice is between Headscale (if you’re already deep in the Tailscale ecosystem and want its client features) and Netbird (if you want one coherent, fully open project with a dashboard and policy engine that were designed to be self-hosted from day one). For a fresh deployment where I’m doing it all myself, Netbird is the one I reach for — it’s less clever plumbing and more product, and the consolidated single-server deployment has made it noticeably less fiddly to stand up than it was a couple of years ago. Just budget an evening for the identity-provider setup, because that is the part everyone underestimates, and put a health check on the control plane before you trust it with anything you’d miss at 3am.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.