Overlay Networks¶
Overview¶
Overlay / mesh VPN products (WireGuard, Tailscale, Headscale, ZeroTier, and similar) give Nix fleets a reachability fabric: stable private addresses and encrypted paths so remote builders, remote deploy SSH, and private binary caches work across NATs and sites.
This is not a VPN tutorial. Host hostname, firewall defaults, and interface backends live in Networking. Overlay membership is only the reachability axis of a machine mesh—it does not grant build, binary, deploy, or secret trust; see Inter-machine trust.
Last checked: 2026-07-31 — NixOS module option names and Clan mesh-vpn pointers; confirm against your channel / Clan doc version.
Details¶
What Nix consumes¶
Nix and nixos-rebuild talk over ordinary hostnames and store URIs (ssh://…, http(s)://…). Once peers can resolve and route to each other on the overlay, builder lines, --target-host, and private substituter URLs look the same as on a LAN. Failures here are timeouts and unreachable hosts—not signature or trusted-users errors.
NixOS module map (verified options)¶
| Product | Role for Nix fleets | NixOS surface (nixpkgs) |
|---|---|---|
| WireGuard | Peer-to-peer or hub/spoke tunnels you configure yourself | networking.wireguard.enable, networking.wireguard.interfaces.<name>.* (optional networking.wireguard.useNetworkd) |
| Tailscale | Managed mesh client (coordination via Tailscale or Headscale) | services.tailscale.enable (and related: authKeyFile, openFirewall, useRoutingFeatures, extraUpFlags, …) |
| Headscale | Self-hosted coordination server for Tailscale clients | services.headscale.enable (+ address / port / settings) |
| ZeroTier | Mesh with network IDs / controller auth | services.zerotierone.enable, services.zerotierone.joinNetworks |
| Clan + ZeroTier | Declarative fleet mesh via Clan inventory (controller / peer / optional moon) |
Clan inventory zerotier instance—not raw services.zerotierone alone; see Clan mesh-vpn + zerotier service docs (26.05). Unstable Clan also documents inventory wireguard / mycelium networking services—cite that channel, not this table. |
Do not invent option names: confirm against NixOS option search for your channel. Keep private keys and auth material out of the store—prefer privateKeyFile / authKeyFile and secrets strategies.
Patterns that matter for Nix¶
Builders. Point builders / /etc/nix/machines at overlay hostnames or Stable IPs (ssh://nix@builder.tailnet-name.ts.net or ssh://nix@100.x.y.z). SSH must work non-interactively for the daemon user; overlay DNS (MagicDNS, Headscale DNS, ZeroTier names) is optional but convenient.
Deploy. nixos-rebuild --target-host / --build-host, Colmena, and deploy-rs need the same SSH reachability. Prefer overlay addresses when public IPs are dynamic or firewalled.
Private caches. Serve nix-serve / Harmonia / Attic on an overlay-only address (or bind on all interfaces and rely on firewall + overlay). Clients list that URL under substituters with the matching trusted-public-keys. Overlay reachability does not replace signatures.
Firewall. Open the overlay’s UDP listen port when peers must initiate to you (networking.firewall.allowedUDPPorts, or Tailscale’s services.tailscale.openFirewall / config.services.tailscale.port). Many setups trust the tunnel interface for inbound mesh traffic via networking.firewall.trustedInterfaces (e.g. tailscale0, wg0, zt*). For Tailscale routing features (subnet router / exit node), the module’s useRoutingFeatures may loosen reverse-path filtering; otherwise strict RPF can drop tunnel-related traffic—see option docs for services.tailscale.useRoutingFeatures and networking.firewall.checkReversePath.
Headscale vs Tailscale SaaS. Run services.headscale on a reachable control plane; clients still use services.tailscale and point login at Headscale (commonly via extraUpFlags, e.g. --login-server=https://headscale.example.com). Verify current Headscale client flags upstream when you wire this.
Clan mesh-vpn. On docs 26.05, Clan’s fully integrated mesh is ZeroTier through inventory roles (controller, peer, optional moon with stableEndpoints). Deploy the controller first, then peers (clan machines update). That fabric is what Clan expects for machine-to-machine reachability; fleet inventory and tooling: Clan and mesh. Unstable networking also lists inventory WireGuard and other overlays—vocabulary only here; options live upstream.
Anti-patterns¶
- Treating VPN membership as
trusted-users, cache-key acceptance, or deploy authority. - Putting WireGuard
privateKeyor Tailscale auth keys as plaintext evaluated strings (world-readable store). - Opening SSH / cache HTTP on the public Internet “because the overlay exists”—bind or firewall so only the fabric (or intentional public endpoints) can reach them.
- Running two overlays on the same hosts without a clear which-address policy for builders and deploy URIs.
Boundaries (what this page is not)¶
- Host firewall and interface policy—
networking.firewalland static addressing. - The machine mesh concept alone—trust and topology without overlay tooling.
- Remote deploy—SSH
nixos-rebuildand activation on peers.
Examples¶
Tailscale client (enable daemon; open UDP; optionally trust the interface for inbound mesh traffic):
{ config, ... }: {
services.tailscale.enable = true;
# Optional: services.tailscale.authKeyFile = "/run/secrets/tailscale_key";
# Optional: services.tailscale.openFirewall = true;
networking.firewall = {
trustedInterfaces = [ config.services.tailscale.interfaceName ];
allowedUDPPorts = [ config.services.tailscale.port ];
};
}
Minimal WireGuard interface (illustrative keys/addresses—replace; prefer privateKeyFile):
{
networking.firewall.allowedUDPPorts = [ 51820 ];
networking.wireguard.interfaces.wg0 = {
ips = [ "10.100.0.2/24" ];
listenPort = 51820;
privateKeyFile = "/var/lib/wireguard/privatekey";
peers = [{
publicKey = "PEER_PUBLIC_KEY_BASE64=";
allowedIPs = [ "10.100.0.1/32" ];
endpoint = "vpn.example.org:51820";
persistentKeepalive = 25;
}];
};
}
ZeroTier client (join a network ID; membership still requires controller approval unless the network is public):
After the overlay is up, a builder line is ordinary SSH over fabric addresses:
References¶
- NixOS manual — Networking
- NixOS option search —
networking.wireguard - NixOS option search —
services.tailscale - NixOS option search —
services.headscale - NixOS option search —
services.zerotierone - Clan — Mesh VPN (ZeroTier) — 26.05 — last checked 2026-07-31
- Clan — zerotier service (roles) — 26.05
- Clan — Networking (unstable priorities) —
wireguard/p2p-ssh-iroh/ … when not on 26.05 - WireGuard / Tailscale docs / Headscale / ZeroTier — product behavior beyond NixOS modules
See also¶
- Networking — hostName, firewall, interface backends (not overlays)
- Machine mesh — interconnect mental model
- Inter-machine trust — reachability vs build/binary/deploy/secret axes
- Clan and mesh — Clan fleet tooling; ZeroTier mesh-vpn as reachability
- Remote builders — builders over overlay SSH
- Store protocols — URI forms once peers route
- Remote deploy — hub SSH activate over fabric
- Colmena / deploy-rs — hub deploy needing reachability
- Binary cache hosting — private caches on overlay addresses
- Private cache mesh — substituter URLs over overlay DNS
- Secrets strategies — keys/auth material out of the store