Wireguard and Netbird Conflict #41

Closed
opened 2026-10-24 07:33:34 +00:00 by rpickler · 3 comments
Owner

@hermes-bot I have two machines enrolled in both wireguard, entirely defined in this repository, and my work's netbird vpn.

  • kansas
    • enrolled fully through this system
    • Cannot have wireguard and netbird on at the same time. Nothing works
  • pc183
    • Not part of our management here
    • enrolled manually through the app scripts
    • Can have both wireguard and netbird on at the same time

I believe the discrepancy is between the Allowed Hosts field on the fully managed wireguard definitions. My idea is to make two wireguard profiles for each host that is fully managed. One directs all traffic through my home network, and the other only directs only internal ip addresses. They can be called home-full and home, respectively.

Take a look at my configuration and make a plan for implementing. Post your analysis here as a response.

@hermes-bot I have two machines enrolled in both wireguard, entirely defined in this repository, and my work's netbird vpn. * kansas * enrolled fully through this system * Cannot have wireguard and netbird on at the same time. Nothing works * pc183 * Not part of our management here * enrolled manually through the app scripts * Can have both wireguard and netbird on at the same time I believe the discrepancy is between the Allowed Hosts field on the fully managed wireguard definitions. My idea is to make two wireguard profiles for each host that is fully managed. One directs all traffic through my home network, and the other only directs only internal ip addresses. They can be called home-full and home, respectively. Take a look at my configuration and make a plan for implementing. Post your analysis here as a response.
Member

Analysis

The hunch is right — and the [ipv4] lines make it worse. The two enrollment paths in this repo produce fundamentally different routing:

  • kansas (fully managed) gets the NetworkManager connection from mkNetworkManagerConfig (lib/wireguard.nix:32):
    • allowed-ips=0.0.0.0/0 (line 48) — full tunnel.
    • [ipv4] address1=…/24,192.168.73.1 (line 53) — a gateway, so NM installs a default route via wg-home. NM gives VPN connections a better route metric than physical devices, so wg-home's default route wins over the uplink's — and over netbird's anything-not-explicitly-routed traffic.
    • dns=192.168.73.1 (line 54) with no DNS priority set — VPN DNS also outranks every other link, so all resolution (work names included) goes home → dnsmasq → home router → public DNS. Work-internal names fail; IP traffic outside netbird's specific routes gets blackholed or asymmetric through home. Hence "nothing works" rather than "slow".
  • pc183 (manual) gets mkClientConfig (lib/wireguard.nix:58): AllowedIPs = 192.168.97.0/24,192.168.73.0/24 (line 71) — split tunnel only, no default route, netbird keeps the default path and its DNS. That is exactly why both can be on at once there.

Footnote: pc183's manual config still carries DNS = 192.168.73.1 in [Interface]; if name resolution ever misbehaves there too, that's the leftover.

Plan: two profiles per managed host

Hosts affected: kansas, pumpkin (rpickler.vpn.enable = true). The pi4 server needs no change — its peer entries are per-/32 allowedIPs, independent of what clients route.

1. lib/wireguard.nix

Add a profile table:

wgProfiles = {
  # internal-only: coexists with netbird
  home      = { allowedIPs = [ "192.168.73.0/24" "192.168.97.0/24" ]; neverDefault = true; dnsPriority = 500; };
  # full tunnel: default route + DNS via home — either/or with netbird, by design
  home-full = { allowedIPs = [ "0.0.0.0/0" ]; neverDefault = false; dnsPriority = null; };
};

and parameterize mkNetworkManagerConfig by profile:

  • [connection] id=wg-<profile>, but keep interface-name=wg-home for both — same keypair, the server keeps seeing one peer, zero server-side churn.
  • allowed-ips= comes from the profile.
  • home: adds [ipv4] never-default=true (kills the competing default route) and dns-priority=500 (positive = low rank), so systemd-resolved (already running on kansas for netbird) ranks wg-home's DNS below netbird's link; the existing dns-search=vpn is pushed to resolved as a routing domain, so *.vpn names still resolve via home dnsmasq and everything else keeps work/local DNS. Gateway line stays in address1 but is inert with never-default.
  • home-full: byte-for-byte today's behavior (0.0.0.0/0, gateway, VPN DNS priority).

2. modules/wireguard-client.nix

Render both templates with lib.genAttrs over wgProfiles → sops.templates."wg-<name>.nmconnection" into /etc/NetworkManager/system-connections/. Same sops private key for both, autoconnect=false, mode 0600 as today.

3. Untouched

Server module, peer keys/rotation, apps/wireguard.nix (manual peers already split-tunnel; a full-tunnel variant for them is a possible follow-on).

Usage after deploy

  • nmcli con up wg-home → home internal ranges + *.vpn DNS while netbird stays up (what kansas wants day-to-day).
  • nmcli con up wg-home-full → current full-tunnel behavior; netbird off — two default-route providers never coexist, that conflict is inherent, not a bug.

Verification on kansas

  • ip route — no default dev wg-home while wg-home is active; netbird's routes untouched.
  • resolvectl query <work-internal-name> → work DNS; resolvectl query <peer>.vpn → 192.168.73.1.
  • ping 192.168.73.1, netbird stays connected.
  • Then flip to wg-home-full and confirm the old full-tunnel behavior is unchanged.

Caveats

  • If the work netbird network advertises its own exit-node default route, wg-home no longer competes with it at all — that's the point of the split.
  • Rollback is a plain revert: the connections are fully rendered from nix (sops templates) and are removed from system-connections/ on switch.

Estimate: ~40 lines across the two files. Happy to open a PR if the profile shape above looks right.

## Analysis **The hunch is right — and the `[ipv4]` lines make it worse.** The two enrollment paths in this repo produce fundamentally different routing: - **kansas (fully managed)** gets the NetworkManager connection from `mkNetworkManagerConfig` (`lib/wireguard.nix:32`): - `allowed-ips=0.0.0.0/0` (line 48) — full tunnel. - `[ipv4] address1=…/24,192.168.73.1` (line 53) — a gateway, so NM installs a **default route via wg-home**. NM gives VPN connections a better route metric than physical devices, so wg-home's default route wins over the uplink's — and over netbird's anything-not-explicitly-routed traffic. - `dns=192.168.73.1` (line 54) with no DNS priority set — VPN DNS also outranks every other link, so *all* resolution (work names included) goes home → dnsmasq → home router → public DNS. Work-internal names fail; IP traffic outside netbird's specific routes gets blackholed or asymmetric through home. Hence "nothing works" rather than "slow". - **pc183 (manual)** gets `mkClientConfig` (`lib/wireguard.nix:58`): `AllowedIPs = 192.168.97.0/24,192.168.73.0/24` (line 71) — split tunnel only, no default route, netbird keeps the default path and its DNS. That is exactly why both can be on at once there. Footnote: pc183's manual config still carries `DNS = 192.168.73.1` in `[Interface]`; if name resolution ever misbehaves there too, that's the leftover. ## Plan: two profiles per managed host Hosts affected: **kansas, pumpkin** (`rpickler.vpn.enable = true`). The pi4 server needs no change — its peer entries are per-/32 `allowedIPs`, independent of what clients route. ### 1. `lib/wireguard.nix` Add a profile table: ```nix wgProfiles = { # internal-only: coexists with netbird home = { allowedIPs = [ "192.168.73.0/24" "192.168.97.0/24" ]; neverDefault = true; dnsPriority = 500; }; # full tunnel: default route + DNS via home — either/or with netbird, by design home-full = { allowedIPs = [ "0.0.0.0/0" ]; neverDefault = false; dnsPriority = null; }; }; ``` and parameterize `mkNetworkManagerConfig` by profile: - `[connection] id=wg-<profile>`, but keep **`interface-name=wg-home` for both** — same keypair, the server keeps seeing one peer, zero server-side churn. - `allowed-ips=` comes from the profile. - **`home`**: adds `[ipv4] never-default=true` (kills the competing default route) and `dns-priority=500` (positive = low rank), so systemd-resolved (already running on kansas for netbird) ranks wg-home's DNS *below* netbird's link; the existing `dns-search=vpn` is pushed to resolved as a routing domain, so `*.vpn` names still resolve via home dnsmasq and everything else keeps work/local DNS. Gateway line stays in `address1` but is inert with `never-default`. - **`home-full`**: byte-for-byte today's behavior (0.0.0.0/0, gateway, VPN DNS priority). ### 2. `modules/wireguard-client.nix` Render both templates with `lib.genAttrs` over `wgProfiles` → `sops.templates."wg-<name>.nmconnection"` into `/etc/NetworkManager/system-connections/`. Same sops private key for both, `autoconnect=false`, mode 0600 as today. ### 3. Untouched Server module, peer keys/rotation, `apps/wireguard.nix` (manual peers already split-tunnel; a full-tunnel variant for them is a possible follow-on). ### Usage after deploy - `nmcli con up wg-home` → home internal ranges + `*.vpn` DNS **while netbird stays up** (what kansas wants day-to-day). - `nmcli con up wg-home-full` → current full-tunnel behavior; netbird off — two default-route providers never coexist, that conflict is inherent, not a bug. ### Verification on kansas - `ip route` — no `default dev wg-home` while `wg-home` is active; netbird's routes untouched. - `resolvectl query <work-internal-name>` → work DNS; `resolvectl query <peer>.vpn` → 192.168.73.1. - `ping 192.168.73.1`, netbird stays connected. - Then flip to `wg-home-full` and confirm the old full-tunnel behavior is unchanged. ### Caveats - If the work netbird network advertises its own exit-node default route, `wg-home` no longer competes with it at all — that's the point of the split. - Rollback is a plain revert: the connections are fully rendered from nix (sops templates) and are removed from `system-connections/` on switch. Estimate: ~40 lines across the two files. Happy to open a PR if the profile shape above looks right.
Author
Owner

Looks perfect. Go ahead and push a pr when you're ready.

Looks perfect. Go ahead and push a pr when you're ready.
Member

Opened PR #47 implementing the approved plan: two profiles per managed host — wg-home (internal ranges only, never-default, low DNS precedence — coexists with NetBird) and wg-home-full (today's full tunnel). Same keypair/interface-name, so no server-side change. Verified via nix eval on kansas/pumpkin that both .nmconnection templates render as intended.

One refinement over the plan above: the home profile keeps dns=192.168.73.1 on the link but at dns-priority=500. resolved merges search domains across links regardless of priority, so *.vpn still resolves via home dnsmasq while NetBird keeps the default resolver — dropping DNS entirely would have silently broken *.vpn lookups. Details in the PR body.

Opened PR #47 implementing the approved plan: two profiles per managed host — `wg-home` (internal ranges only, `never-default`, low DNS precedence — coexists with NetBird) and `wg-home-full` (today's full tunnel). Same keypair/interface-name, so no server-side change. Verified via `nix eval` on kansas/pumpkin that both `.nmconnection` templates render as intended. One refinement over the plan above: the `home` profile keeps `dns=192.168.73.1` on the link but at `dns-priority=500`. resolved merges *search domains* across links regardless of priority, so `*.vpn` still resolves via home dnsmasq while NetBird keeps the default resolver — dropping DNS entirely would have silently broken `*.vpn` lookups. Details in the PR body.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
richardpickler.com/nixos#41
No description provided.