
Networking
Self-Hosting the Mesh: Headscale and NetBird as a Tailscale Exit
Replacing the Tailscale coordination plane with self-hosted Headscale or NetBird: Kubernetes subnet routers, owned exit nodes, failure modes and the exit ramp.
Mesh VPNs won because they removed a category of work. No concentrator to size, no per-site tunnel matrix, no certificate ceremony to onboard a laptop — install an agent, authenticate, and every machine can reach every other machine it is allowed to reach. For a distributed Kubernetes estate with a few clusters, an edge site, and engineers in four time zones, that is an enormous amount of network engineering that simply stops existing.
The trade is easy to overlook because the thing you gave up is invisible when it works. A coordination server holds the registry of every device's public key, distributes the access-control policy, assigns addresses and DNS, approves route advertisements, and brokers NAT traversal. It cannot read your traffic — the tunnels are WireGuard, terminated on your machines — but it decides membership. Anything that can add a device to your network can reach whatever that device is permitted to reach.
This article treats that plane as the architectural object it is: what the coordinator actually knows, the two projects worth self-hosting and one worth understanding, how to wire Kubernetes clusters in as subnet routers and exit nodes, the failure modes that only appear at 3 a.m., and the exit ramp in both directions. It is the cross-site overlay counterpart to our guide on in-cluster networking with Cilium — a different layer entirely, and a decision that should be made separately rather than by analogy.
What the Coordination Server Actually Knows
Start with an honest description of the split, because the sovereignty case is weaker than the marketing against it and stronger than the marketing for it. The data plane is WireGuard: two peers exchange public keys, derive a session, and send UDP directly to each other. The coordinator never holds a private key and never sees a plaintext byte. Anyone claiming a SaaS mesh vendor can read your database traffic is wrong.
Now the part that matters. The coordinator maintains five things, and each is a control you either own or rent.
- The device registry. Every node's public key and its identity. Adding an entry adds a machine to your network — the single most consequential write in the system.
- The access policy. Which sources may reach which destinations on which ports. Distributed from the coordinator and enforced at each node, which means a policy push is an authorisation change with no deploy attached to it.
- Addressing and DNS. The overlay prefix, per-node addresses, and the internal names your engineers actually type. A name that resolves differently is a redirect.
- Route approval. A subnet router only carries a CIDR after the route is approved. The coordinator decides which machine is allowed to claim your production service range.
- Relay fallback. When direct traversal fails, traffic hairpins through a relay. The payload stays encrypted, but the relay observes who talks to whom, how much, and when — and its availability becomes your availability.
Read that list as a threat model rather than a feature list and the picture sharpens. A compromised or compelled coordinator does not decrypt your traffic; it enrols a node, tags it, and lets your own policy grant it access. Detection depends entirely on whether anyone is watching device registrations — which, in most estates, nobody is. That is a different risk from confidentiality and it is not addressed by end-to-end encryption.
Three Projects, Honestly Described
The filter is the one we apply to every tier: a licence that does not strand you, a genuine self-hosted deployment story rather than a hobbled community edition, and an operational surface a team can carry.
Headscale — the same clients, a control server you run
Headscale is an open-source reimplementation of the Tailscale coordination server. Its decisive property is that the client stays the official one: the same tailscale binary, the same MagicDNS, the same subnet-router and exit-node semantics, simply pointed at your server_url. Migration is a re-enrolment, not a re-platform — and so is migrating back.
The feature surface is more complete than its reputation suggests. The documented feature set covers subnet routers, exit nodes, an embedded DERP server, peer relays, and auto-approvers for both routes and exit nodes — which is the whole toolkit needed to attach a Kubernetes estate. Version 0.28.0, released 4 February 2026, tightened the gRPC API, added a headscale auth command group, and made policy testing first-class; it also collapsed the node's ForcedTags, ValidTags and InvalidTags fields into a single Tags field and moved pre-auth-key operations to ID-based commands. Those are the changes of a project hardening its interfaces, not chasing features.
The honest limits are three, and the project states them plainly. It is not affiliated with Tailscale Inc. — it tracks a protocol another company controls, so client-version skew is a real operational concern rather than a theoretical one. It implements a single tailnet per instance, so hard multi-tenancy means multiple instances. And the enterprise conveniences — device-posture attestation, SCIM provisioning, managed audit pipelines — are not there. In exchange you get a Go binary and a database you can read.
NetBird — self-hosting as the default posture
NetBird takes the opposite starting point: an overlay built to be self-hosted from the beginning, with its own agent rather than a compatible reimplementation. A self-hosted deployment is four components — a management server holding peers, groups and policies; a signal server brokering NAT traversal; a relay (TURN) for the fallback path; and a dashboard — with the agent establishing WireGuard tunnels underneath.
The recent development that revived evaluations is dual-stack addressing. NetBird v0.71.0, released 14 May 2026, gives each account its own IPv6 prefix alongside the existing IPv4 range — a /64 by default, configurable from /48 to /120 — with DNS, ACL, exit-node and network-route support across both families. For an estate that is already IPv6-native internally, an IPv4-only overlay is a translation layer nobody asked for, and removing it is worth more than a feature list suggests.
The trade against Headscale is precise. NetBird has richer built-in identity and policy machinery and no dependency on another vendor's protocol decisions. But because the agent is its own, adopting it means every device speaks NetBird — so leaving means re-enrolling every device, whereas leaving Headscale means changing one flag. Neither is wrong; they are different bets about where you want the switching cost to sit.
Netmaker — capable, but read the licence first
Netmaker builds WireGuard software-defined networks and is genuinely good at stitching VPCs to on-premises networks. The caveat is structural: the core is Apache-2.0 while the management UI is under the SSPL, which is not an OSI-approved open-source licence. If your criterion is "software we could fork and keep running under any circumstance", that split belongs in the evaluation rather than a footnote — the discipline we apply to every tier in the cloud repatriation playbook.
Wiring Kubernetes In: Subnet Routers and Exit Nodes
A Kubernetes cluster joins a mesh in one of two ways, and the choice is architectural. Either every pod gets an overlay address — dense, high-cardinality, and rarely what you want — or a small number of nodes advertise the cluster's CIDRs on behalf of everything behind them. The second is a subnet router, and it is the right default: two pods, not two thousand, and the cluster's internal addressing stays the cluster's business.
Start with the coordinator's own configuration, because two settings there decide how sovereign the result actually is.
# /etc/headscale/config.yaml
server_url: https://mesh.internal.example.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443
noise:
# Back this up. Lose it and every node must re-enrol.
private_key_path: /var/lib/headscale/noise_private.key
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
derp:
server:
enabled: true # your own relay, inside your boundary
region_id: 999
stun_listen_addr: 0.0.0.0:3478
urls: [] # drop the vendor DERP map — otherwise fallback
# traffic still transits someone else's fleet
auto_update_enabled: false
policy:
mode: file
path: /etc/headscale/policy.hujson # keep this file in gitThe embedded relay deserves a caveat the documentation gives directly: it exists to assist connectivity, with no speed or throughput optimisations, and it cannot serve the captive-portal check because it does not expose /generate_204 over plain HTTP. Treat it as a correctness fallback, capacity-plan for the case where a NAT-hostile site relies on it permanently, and alert on the ratio of relayed to direct sessions.
The subnet router itself is an ordinary Deployment. What makes it work is a TUN device and one capability — and what makes it fast is refusing userspace mode.
apiVersion: apps/v1
kind: Deployment
metadata:
name: mesh-subnet-router-a
namespace: mesh
spec:
replicas: 1 # HA = a second Deployment with its own state
selector:
matchLabels: { app: mesh-subnet-router-a }
template:
metadata:
labels: { app: mesh-subnet-router-a }
spec:
serviceAccountName: mesh-router
containers:
- name: agent
image: ghcr.io/tailscale/tailscale:v1.90.8
env:
- name: TS_AUTHKEY
valueFrom:
secretKeyRef: { name: mesh-authkey, key: authkey }
- name: TS_KUBE_SECRET # state, per instance
value: mesh-state-router-a
- name: TS_ROUTES
value: "10.42.0.0/16,10.43.0.0/16"
- name: TS_USERSPACE
value: "false" # kernel path, needs the TUN device
- name: TS_EXTRA_ARGS
value: >-
--login-server=https://mesh.internal.example.com
--advertise-tags=tag:k8s-router
securityContext:
capabilities:
add: ["NET_ADMIN"]
volumeMounts:
- name: dev-net-tun
mountPath: /dev/net/tun
resources:
requests: { cpu: 200m, memory: 128Mi }
limits: { memory: 512Mi }
volumes:
- name: dev-net-tun
hostPath: { path: /dev/net/tun, type: CharDevice }Three details there are load-bearing. TS_USERSPACE: "false" selects the kernel data path; the userspace netstack needs no TUN device and is markedly slower — fine for a jump host, wrong for a route carrying cluster traffic. TS_KUBE_SECRET is per instance, because two replicas sharing one state Secret fight over the same node identity: high availability here is a second single-replica Deployment with its own Secret and identical TS_ROUTES, not replicas: 2. And the tag is what policy keys on, so approving a route becomes a reviewed decision rather than a click.
{
"groups": {
"group:platform": ["[email protected]", "[email protected]"],
"group:sre": ["[email protected]", "[email protected]"],
},
"tagOwners": {
"tag:k8s-router": ["group:platform"],
"tag:k8s-exit": ["group:platform"],
},
"autoApprovers": {
"routes": {
"10.42.0.0/16": ["tag:k8s-router"],
"10.43.0.0/16": ["tag:k8s-router"],
},
"exitNode": ["tag:k8s-exit"],
},
"acls": [
// SRE reaches in-cluster services on TLS only — not the whole CIDR.
{ "action": "accept", "src": ["group:sre"],
"dst": ["10.43.0.0/16:443"] },
// Site A routers may reach Site B routers; sites are not transitive.
{ "action": "accept", "src": ["tag:k8s-router"],
"dst": ["tag:k8s-router:*"] },
],
}Exit nodes are the same pattern with one host-level prerequisite: forwarding must be enabled for both address families (net.ipv4.ip_forward=1 and net.ipv6.conf.all.forwarding=1), and the node needs an egress path you actually control. On a general-purpose distribution that is a sysctl someone sets and someone else forgets on node seven. On an immutable host it is machine configuration — which is one more argument for Talos Linux, where the kernel parameters a workload depends on are declared in the machine config rather than applied by hand.
If you prefer a controller to raw manifests, Tailscale's Kubernetes operator reconciles a `Connector` custom resource that deploys subnet routers, exit nodes and app connectors, plus an API-server proxy for kubectl access. Its API supports configuring the control-plane URL used by the resources it manages, which is the hook for pointing it at a self-hosted coordinator — verify that combination against the versions you intend to run, because it is the seam where a reimplementation is most likely to lag.
The Failure Modes That Only Appear at 3 a.m.
Self-hosting the coordinator moves an availability problem onto your side of the line, and the reasonable question is how bad the bad day is. The reassuring answer is that a coordination outage is not a network outage: established WireGuard sessions keep forwarding, because forwarding is a kernel function on each node and does not consult the coordinator per packet.
The unreassuring answer is what degrades. New nodes cannot join. Policy changes do not propagate, so an offboarding is not enforced. Route and DNS updates stall. Peers that lose their direct path cannot renegotiate through the relay. And node keys keep expiring on schedule — which turns a survivable outage into a hard one at exactly the moment nobody can fix it. Disable expiry deliberately on infrastructure nodes such as subnet routers and exit nodes, keep it short for human devices, and alert well before either boundary.
- The state that cannot be re-derived. The database and the coordinator's private keys are the whole system. Lose them and every device re-enrols by hand, at a moment you did not choose. Back them up on the same schedule and to the same standard as the cluster's own disaster-recovery material, and rehearse the restore.
- Overlapping CIDRs. Two clusters numbered identically cannot both advertise their range. Allocate non-overlapping pod and service ranges per site before the first join, or accept a renumbering later.
- Relay saturation. One NAT-hostile site pinning every session to a relay makes that relay your bottleneck and your single point of failure. Measure the direct-versus-relayed ratio; do not discover it from a latency complaint.
- Protocol skew. A reimplementation follows a protocol it does not control. Pin client versions, stage upgrades in a canary tailnet, and read the release notes of both projects before a fleet-wide bump.
- Route hijack by tag. An auto-approver keyed to a tag means anyone who can apply that tag can claim a production CIDR. Tag ownership is a privileged grant; review it like one.
What an Auditor Asks About Your Overlay
An overlay that reaches production is an in-scope system, and the questions are consistent regardless of regime. Four come up reliably.
- Who can add a device, and what evidence exists? Under SaaS the answer includes the vendor's operators; self-hosted, it is your administrators and your logs. Either way the control is only real if device registrations are recorded, retained, and reviewed by someone.
- Is the policy change history reviewable? A HuJSON policy in git with pull requests turns access changes into evidence: who proposed, who approved, when it applied. A policy edited in a web console is an assertion.
- Where does relay traffic go? Payloads stay encrypted, but a relay hop is a jurisdictional and metadata question — and with the default relay map, the answer is a fleet you do not operate.
- Is this coordinator on the third-party ICT register? For firms inside NIS2 and DORA scope, a SaaS coordination plane is an ICT service supporting critical functions, with the concentration and exit-strategy obligations that follow. Self-hosting does not delete the obligation — it converts a supplier assessment into an internal control you evidence directly.
The second is where most estates gain the most for the least work: moving the policy into git costs an afternoon and converts the network's most sensitive configuration from a screen someone changed into a reviewed, reversible artefact with an author and a timestamp.
The Exit Ramp, In Both Directions
Optionality here is unusually cheap, and it is worth being explicit about why. Because the data plane is WireGuard and the client is open source, the coordinator is a swappable component rather than a foundation. With Headscale the switch is literally one flag — tailscale up --login-server=https://mesh.internal.example.com — and the reverse move back to the SaaS coordinator is the same flag with a different value. That symmetry is the point: an exit you have never tested is a hope, but an exit that is one flag is a decision you can make on a Tuesday.
Three practices keep it that way. Keep the ACL policy in HuJSON in git, because both the SaaS product and Headscale consume that format — the most valuable artefact in the system is then portable by construction. Keep the overlay a connectivity layer only, never an authentication layer, so that swapping it does not touch your authorisation model. And keep cluster CIDRs non-overlapping and documented, so that any future topology remains expressible.
Understand the asymmetry before choosing NetBird: its agent is its own, so leaving means touching every device rather than changing a flag. That is not a reason to avoid it — a fully self-hostable stack with no dependence on another company's protocol decisions is a real advantage, and for disconnected edge fleets it is often the better fit. It is a reason to know which switching cost you are accepting.
Below every option sits the floor: plain WireGuard, in the mainline Linux kernel since 5.6, configured by whatever manages your machines. It is more work per site and it never disappears as an option. A mesh you can abandon entirely, at a known cost, is a mesh you are choosing rather than depending on.
The Long Game
Networking decisions outlive the teams that make them. The overlay you stand up this quarter will still be carrying traffic when the clusters under it have been rebuilt twice and the applications on top rewritten — which is why the layering matters more than the product comparison. WireGuard is a small, audited protocol in the kernel; it is the part of this stack you can safely expect to be unchanged in a decade. Coordination is the churn layer: pricing models change, licences change, companies get acquired, protocols shift under reimplementations.
So place the durable seam deliberately. Own the coordinator if the estate is small enough that a Go binary and a database is less operational weight than a procurement relationship — which, for most platform teams, it is. Rent it if the enterprise identity integrations are worth the dependency, but then treat it as a named third-party ICT service with an exit plan you have rehearsed, not a utility you assume. Either way, write down which machines may join your network and who may make that decision, because that sentence is the actual security boundary, and it is the one no product will write for you.
The engineering-sovereignty position is not that vendors are untrustworthy. It is that membership in your own network is too consequential a decision to hold by contract when it can be held by architecture — and that the difference costs, in this particular case, about a day of work and one flag.
§FAQ/Common questions
Frequently asked
What is the best self-hosted Tailscale alternative for a Kubernetes estate?
Headscale if you want to keep the official Tailscale clients and only replace the coordination server — subnet routers, exit nodes, MagicDNS and ACLs all keep working, and the switch is a single --login-server flag in either direction. NetBird if you want a stack designed to be self-hosted end to end, with its own agent, richer built-in policy machinery, and no dependence on another company's protocol decisions; the trade is that leaving means re-enrolling every device. Netmaker is capable for site-to-site work, but its management UI is under the SSPL rather than an OSI-approved open-source licence, which matters if your criterion is software you could fork and keep running.
Can the Tailscale coordination server read my traffic?
No. The data plane is WireGuard between peers, and the coordination server never holds a private key or sees plaintext. The reason to self-host is different: the coordinator holds the device registry, distributes access policy, assigns addresses and DNS, approves route advertisements, and provides relay fallback. Anything that can add a device to your network can reach whatever policy permits that device to reach, and relay hops reveal metadata — who talks to whom, how much, and when — even though the payload stays encrypted.
What happens if my self-hosted coordination server goes down?
Established connections keep working, because WireGuard forwarding is a kernel function on each node and does not consult the coordinator per packet. What stops is everything else: new nodes cannot join, policy changes do not propagate so offboarding is not enforced, route and DNS updates stall, and peers that lose a direct path cannot renegotiate through a relay. The dangerous interaction is key expiry — nodes whose keys lapse during the outage cannot re-register. Disable expiry on infrastructure nodes such as subnet routers and exit nodes, keep it short for human devices, and alert before either boundary.
How do I expose a Kubernetes cluster to a mesh VPN without putting an agent in every pod?
Run a subnet router: a single-replica Deployment of the mesh agent with NET_ADMIN and a /dev/net/tun host path, advertising the cluster's pod and service CIDRs. Peers then reach ClusterIP services directly with no ingress, no load balancer, and no public exposure. Set TS_USERSPACE to false so the kernel data path is used rather than the slower userspace netstack, give each instance its own state Secret, and achieve high availability with a second single-replica Deployment advertising identical routes rather than by scaling replicas. Non-overlapping pod and service CIDRs across sites are a precondition, since two clusters cannot both advertise the same range.
Does self-hosting Headscale keep relay traffic inside my infrastructure?
Only if you configure it to. Headscale ships an embedded DERP server, but the default relay map also references the vendor's public relay fleet, so fallback traffic can still transit infrastructure you do not operate. Enable the embedded server and set derp.urls to an empty list to keep the relay path inside your boundary. Note the documented limits: the embedded relay has no throughput optimisations and cannot serve the captive-portal check, so treat it as a correctness fallback, capacity-plan for NAT-hostile sites that depend on it, and monitor the ratio of relayed to direct sessions.
Further reading
- Cilium and eBPF: Zero-Trust Kubernetes Networking Without a Sidecar Mesh
- SPIFFE and SPIRE: Zero-Trust Workload Identity on Kubernetes
- Cloud Repatriation Done Right: The Engineering Playbook
- Talos Linux: A Hard-Numbers Security Case for an Immutable OS
- Edge Kubernetes with K3s and Fleet: Disconnected by Design
- NIS2, DORA and the EU AI Act on Self-Hosted Kubernetes
- Kubernetes Disaster Recovery: Velero, etcd, and Real RPO/RTO
- Infrastructure Capabilities
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.