Skip to content

update netbird server modules - #487367

Closed
shuuri-labs wants to merge 15 commits into
NixOS:masterfrom
shuuri-labs:update-netbird-server-components
Closed

update netbird server modules#487367
shuuri-labs wants to merge 15 commits into
NixOS:masterfrom
shuuri-labs:update-netbird-server-components

Conversation

@shuuri-labs

@shuuri-labs shuuri-labs commented Feb 5, 2026

Copy link
Copy Markdown

Motivation

The existing NetBird server modules have limited configurability - management and signal lack TLS options, there's no relay server module, and the embedded IDP isn't exposed. Essentially, the NixOS module's options lag pretty far behind the latest versions of NetBird's various components

Changes

Bug fix: signal state directory

The signal server's RuntimeDirectory, StateDirectory, and WorkingDirectory all pointed to netbird-mgmt instead of netbird-signal. This caused signal and management to share (and potentially corrupt) each other's files when both ran on the same machine.

New module: services.netbird.server.relay

Full relay server module with:

  • Typed port option (default 33080) for the internal listen port - avoids conflicts with nginx on port 443
  • Secure secret injection via systemd LoadCredential → EnvironmentFile → NB_AUTH_SECRET env var (secret never appears in /proc or the Nix store)
  • Embedded STUN server with configurable UDP ports
  • Nginx reverse proxy with WebSocket upgrade headers
  • Firewall integration (openFirewall)
  • extraOptions escape hatch for advanced CLI flags (direct TLS, etc.)
  • DynamicUser = true and full systemd hardening (ProtectSystem = "strict", etc.)

The orchestrator module (server.nix) gains useRelay and relayAuthSecretFile options to wire relay + management together automatically.

Expanded: services.netbird.server.management

  • Relay integration: relayAddresses and relaySecretFile (via _secret pattern for JSON config injection)
  • Database backends: store.engine (sqlite/postgres/mysql) with store.postgres.dsnFile / store.mysql.dsnFile for secret DSN handling
  • Embedded IDP: idp.embedded.enable toggle - sets ManagerType = "integrated" and provides sensible ProviderConfig defaults derived from domain. Users customize via the freeform settings option (e.g.
    settings.ProviderConfig.Owner.Email)
  • TLS support (Let's Encrypt or manual certs)
  • GeoLite updates disabled by default (privacy for self-hosted)
  • Backwards-compat aliases: singleAccountModeDomain → singleAccountMode.domain, disableSingleAccountMode → removed with migration message
  • ProtectSystem = "strict" and full systemd hardening

Expanded: services.netbird.server.signal

  • TLS support (Let's Encrypt or manual certs) with validation assertions
  • Firewall integration (openFirewall)
  • Metrics port option
  • ProtectSystem = "strict", restartTriggers, directory mode hardening

Expanded: services.netbird.clients (client module)

  • Multi-instance support via services.netbird.clients.
  • DNS, routing, and firewall configuration options
  • Rosenpass (post-quantum cryptography) support
  • SSH server integration
  • Self-hosted server connection options

Commits

  1. nixos/netbird: expand client module options - multi-instance client support, DNS/routing/firewall options, Rosenpass, SSH server, self-hosted connection; expanded netbird.md documentation; register new heading
    IDs in redirects.json
  2. nixos/netbird: fix signal server state directory - 3-line bug fix: netbird-mgmt → netbird-signal in RuntimeDirectory/StateDirectory/WorkingDirectory
  3. maintainers: add shuuri-labs - add maintainer entry to maintainer-list.nix
  4. nixos/netbird: update server maintainer to shuuri-labs - change meta.maintainers in server.nix
  5. nixos/netbird: add TLS and firewall options to signal server - TLS (Let's Encrypt / manual certs), openFirewall, metricsPort, assertions, ProtectSystem = "strict", restartTriggers
  6. nixos/netbird: add relay server module - new relay.nix with secure secret handling (LoadCredential + EnvironmentFile), STUN, nginx, firewall, DynamicUser = true; server.nix gains useRelay/relayAuthSecretFile
    orchestration and unconditional turnDomain default
  7. nixos/netbird: expand management server options - relay config, database backends, embedded IDP, TLS, backwards-compat aliases, ProtectSystem = "strict", Relay.Secret warning fix, defaultText corrections
  8. nixos/tests/netbird: reorganize and add server tests - move client test into directory, add server-management (3 nodes), server-signal, server-relay tests
  9. nixos/netbird: update server documentation - expanded server.md with quickstart, relay vs Coturn, embedded IDP, database, and complete examples

Tests

  • Reorganized nixos/tests/netbird.nix → nixos/tests/netbird/ directory with client.nix, server-management.nix, server-signal.nix, server-relay.nix
  • Management test covers 3 configurations: basic, with relay, with PostgreSQL
  • Signal test verifies service startup, correct ports, and isolated state directory
  • Relay test verifies service startup, port binding, state directory, and firewall rules

Documentation

  • Expanded netbird.md (client) with DNS, routing, security, Rosenpass, SSH, and self-hosted sections
  • Expanded server.md with quickstart guides (Coturn and relay), relay vs Coturn comparison table, embedded IDP setup, PostgreSQL backend example, relay standalone configuration, and a complete self-hosted example

How to test

# Run all NetBird tests
nix build .#checks.x86_64-linux.nixosTests.netbird -L

# Run individual tests
nix build .#checks.x86_64-linux.nixosTests.netbird.server-management -L
nix build .#checks.x86_64-linux.nixosTests.netbird.server-relay -L
nix build .#checks.x86_64-linux.nixosTests.netbird.server-signal -L

Things done

  • Built on platform:
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • Tested, as applicable:
  • Ran nixpkgs-review on this PR. See nixpkgs-review usage.
  • Tested basic functionality of all binary files, usually in ./result/bin/.
  • Nixpkgs Release Notes
    • Package update: when the change is major or breaking.
  • NixOS Release Notes
    • Module addition: when adding a new NixOS module.
    • Module update: when the change is significant.
  • Fits CONTRIBUTING.md, pkgs/README.md, maintainers/README.md and other READMEs.

@nixpkgs-ci nixpkgs-ci Bot added 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 12.first-time contribution This PR is the author's first one; please be gentle! 9.needs: reviewer This PR currently has no reviewers requested and needs attention. 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: module (update) This PR changes an existing module in `nixos/` labels Feb 5, 2026
@shuuri-labs
shuuri-labs force-pushed the update-netbird-server-components branch from 9091206 to a73ec87 Compare February 6, 2026 13:10
@nixpkgs-ci
nixpkgs-ci Bot requested a review from GetPsyched February 6, 2026 13:16
@nixpkgs-ci nixpkgs-ci Bot added 8.has: documentation This PR adds or changes documentation and removed 9.needs: reviewer This PR currently has no reviewers requested and needs attention. labels Feb 6, 2026
@shuuri-labs
shuuri-labs force-pushed the update-netbird-server-components branch 2 times, most recently from 3861769 to 4ec6429 Compare February 6, 2026 13:56
@PatrickDaG

Copy link
Copy Markdown
Contributor

I was about to actually take the time and review this insanely huge, single commit PR when I found my name in there.

I don't know what happened but I strongly think you took my PR piped it through your LLM of choice and then posted it. I'm not generally against the usage of LLM however when it's this obvious I would have liked at least a notice.

Anyway what I am really against is setting me as a maintainer without asking, or even notifying me. Please do not do that. As I've stated in the other netbird PR I am not interested in maintaining it anymore.

If you actually want to adopt/modernize the netbird module:

  • add yourself as a maintainer
  • split up this PR into multiple smaller commits
  • minimize the change
    I haven't completely read everything but from skimming it seems to contain a lot of unnecessary option declarations and other things that just blow up the changeset.

@PatrickDaG PatrickDaG left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated above.

@shuuri-labs
shuuri-labs force-pushed the update-netbird-server-components branch from 4ec6429 to c2ca54f Compare February 25, 2026 16:43
@nixpkgs-ci nixpkgs-ci Bot added the 8.has: maintainer-list (update) This PR changes `maintainers/maintainer-list.nix` label Feb 25, 2026
@shuuri-labs
shuuri-labs force-pushed the update-netbird-server-components branch 3 times, most recently from b42d5bc to 22fe74f Compare February 25, 2026 19:16
@shuuri-labs

shuuri-labs commented Feb 26, 2026

Copy link
Copy Markdown
Author

@PatrickDaG apologies, I'm new to nixpkg submissions and forgot to update the maintainer. I've since removed you in lieu of myself. This PR is based on main, where you're the listed maintainer. No PR of yours was run through an LLM, and though I did leverage Claude for assistance, the PR isn't 100% vibe-coded. As far as I can see, that's fine by the contribution guidelines, but if a heads-up on this is helpful to reviewers then sure, Ican include one in future!

I've re-read the contribution guidelines in-depth, received some advice on a few key RFCs on another PR of mine here and re-written this PR in accordance. Notably, I've split this PR into several smaller commits (each detailed in the description) and limited the addition of new typed options. Hopefully this is more parseable for the reviewer :) if there's any additional feedback, I'll do my best to implement it ASAP!

@debtquity

Copy link
Copy Markdown
Contributor

@shuuri-labs thanks for contributing this, I have been itching to re-write the services.netbird module since upstream included a reverse proxy feature [1].

On that note, I looked through changes and noticed you decided to keep nginx. Is there any consideration for adding a traefik option? Nginx does not work well with netbird reverse proxy due to lack of TLS pass through.

If there's no time, I may add this option myself at a later date once this gets merged.

[1] https://docs.netbird.io/selfhosted/migration/enable-reverse-proxy

@shuuri-labs

Copy link
Copy Markdown
Author

Hey @debtquity, you're welcome :)

Two reasons for not making the switch to Traefik just yet:

  • Trying to keep this first PR (my first submission to nixpkgs) reasonable in size
  • Would like to look into adding Caddy support for NetBird's 'permiter' reverse proxy, too - arguably more 'Nix-y' than Traefik and in theory also supports the the TLS passthrough we need (with the help of a plugin)

Definitely not done with the NetBird module after this gets merged, don't worry!

@bct

bct commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

This Caddy configuration worked for me: https://github.com/bct/nix-config/blob/a147a7172527af8ae9998f6ecaede7a86cbd1956/nixos/cloud/megahost-one/netbird.nix#L70-L119

@nixpkgs-ci nixpkgs-ci Bot added the 2.status: merge conflict This PR has merge conflicts with the target branch label Mar 17, 2026

@PatrickDaG PatrickDaG left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope I didn't come of too direct in the last message. Sorry about that. I should rather thank you that someone finally adopted the netbird module and will hopefully modernize it and add support for all the new things.

The multiple commits make it much more readable, thank you for that. I've added some small questions/suggestion, but all in all looks promising.

Oh, if you want you can of course take a look at my PR #354032, or have claude take a look, then I can at least feel like all the time spend on it wasn't completely wasted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the official nixpkgs stance is on this but I feel like this basically just lists the options and doesn't really add anything apart from a whole bunch of text to the manual.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair enough. I've trimmed the docs to remove sections that just listed individual options

description = "Anonymize sensitive information in logs.";
};

extraEnvironment = mkOption {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why you declare all these options? If not I would think an generic RFC 42 style ´settings´ option would keep the module smaller.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'venow replaced them with the existing extraEnvironment (freeform attrsOf str) for NB_* env vars and config (freeform JSON) for config.json overrides. Removed just under 200 lines in the process :) hope this is more RFC 42 compliant!

Comment thread nixos/tests/netbird/client.nix Outdated

for name in instances:
node.wait_for_unit(f"{name}.service")
node.wait_for_file(f"/var/run/{name}/sock")

for name in instances:
wait_until_rcode(node, f"{name} status |& grep -C20 Disconnected", 0, retries=5)

# Verify environment variables are set correctly for advanced client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like testing for the environment here is not really useful since it just tests the module options we have created instead of the interaction with the actual program that we want to test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed them along with the extra test clients. The test now checks core functionality (services start and reach 'Disconnected' state)

stopIfChanged = false;
};

systemd.services.netbird-relay.serviceConfig.EnvironmentFile = "/run/netbird-relay/env";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work? According to ´systemd.exec´ it should fail because the file will be accessed befor preStart runs?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this is a bug. Fixed now and tested on my actual NetBird self-hosted instance. I found a fixed a couple of extra bugs which I've detailed in my comment below

The signal server's RuntimeDirectory, StateDirectory, and
WorkingDirectory were incorrectly set to "netbird-mgmt" (copied from
the management module). Fix them to use "netbird-signal".
CredentialsTTL = "12h";
Secret = "not-secure-secret";
TimeBasedCredentials = false;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not remove this from the default if the builtin relay is now recommended? Enabling both by default seems wrong to me.

RafaelKr pushed a commit to RafaelKr/nixpkgs that referenced this pull request Jul 7, 2026
Sync the server module from NixOS#487367 (head) onto
release-26.05: introduces the netbird native relay module, wires relay
into management, and adds the embedded IdP option. Verified compatible
with the netbird 0.74.2 binary (flags and management.json schema
unchanged 0.69 -> 0.74.2).
RafaelKr added a commit to RafaelKr/nixpkgs that referenced this pull request Jul 7, 2026
The derived EmbeddedIdP settings seed the initial owner via an Owner.Password field, but netbird reads the bcrypt hash from Owner.Hash. OwnerConfig in management/server/idp/embedded.go has fields Email/Hash/Username and no Password, so the admin is never created and cannot log in. Fix the derived block and the server.md example, and switch the doc from htpasswd to mkpasswd (idiomatic, no apacheHttpd dependency, native $2b$ variant; -R 10 because the mkpasswd default cost is 5).

Verified against netbird 0.69.0 (the version this module targets) and 0.74.2.

Fix for NixOS#487367 - feel free to cherry-pick.
RafaelKr added a commit to RafaelKr/nixpkgs that referenced this pull request Jul 7, 2026
The TURNConfig.Secret world-readable check fired whenever the value is a string, regardless of whether TURN is used. On the relay path (useRelay = true, no coturn) TURNConfig.Turns is empty and the secret is never read, yet the warning still fired, including for the relay example in server.md. Gate the check on Turns being non-empty, mirroring the Relay.Secret check which is already gated on relayAddresses.

Fix for NixOS#487367 - feel free to cherry-pick.
RafaelKr pushed a commit to RafaelKr/nixpkgs that referenced this pull request Jul 8, 2026
Sync the server module from NixOS#487367 (head) onto
release-26.05: introduces the netbird native relay module, wires relay
into management, and adds the embedded IdP option. Verified compatible
with the netbird 0.74.2 binary (flags and management.json schema
unchanged 0.69 -> 0.74.2).

@RafaelKr RafaelKr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together — the native relay and embedded IdP are exactly what a self-hosted NetBird needs (no external IdP, no coturn), and it's great to have server-side tests to go with them. I've been running this module for my small home-network since last weekend, rebased onto release-26.05 and with netbird bumped to 0.74.2; the module works unchanged against that newer binary (management.json keys and CLI flags are unchanged from 0.69), which is a good forward-compat signal. The rebased branch is here: https://github.com/RafaelKr/nixpkgs/tree/nixos-26.05-netbird — each point below is already implemented as a commit there, so feel free to cherry-pick.

A few things worth addressing before merge:

  • Functional bug: the embedded-IdP owner is seeded with an Owner.Password field, but netbird only reads Owner.Hash (bcrypt) — with Password the initial admin cannot log in. This is not version drift: the embedded IdP was introduced in netbird v0.62.0, and its OwnerConfig has had Hash (never Password) from that first release through the current v0.74.2. Same key in the server.md example.
  • Invalid default secret: DataStoreEncryptionKey defaults to "very-insecure-key", which is not valid base64 — netbird base64-decodes it and log.Fatalfs on startup, so any deployment that doesn't override it already fails at runtime. Better to require it (default null + assertion), turning that runtime crash into a clear build-time error.
  • Redundant default TURN: the defaults ship a TURN server plus secret, enabled alongside the now-recommended native relay. @b12f already flagged this (#487367 (comment)). Emptying TURNConfig.Turns and nulling TURNConfig.Secret gives relay/no-TURN setups a clean config; coturn is unaffected (it overrides Turns via server.nix). Note the Stuns list should stay — STUN is needed for direct P2P even with the relay (the relay ships an embedded STUN on :3478, which the default Stuns entry targets); only Turns is relay-redundant.

Nothing blocking beyond the owner-field bug; the rest is security/UX hardening of the defaults.

@@ -75,7 +85,17 @@
Datadir = "${stateDir}/data";
DataStoreEncryptionKey = "very-insecure-key";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"very-insecure-key" is not valid base64 (it contains -). netbird base64-decodes this key (util/crypt/crypt.go, NewFieldEncrypt) and log.Fatalfs at startup, so any deployment that doesn't override the default already fails at runtime with a cryptic base64 error. Defaulting to null and asserting it is set turns that into a clear build-time error. (Also worth noting in the docs: netbird has no key-rotation command — the store is single-key AES-GCM — so the key must be chosen once with e.g. openssl rand -base64 32 and kept stable.)

Suggested change
DataStoreEncryptionKey = "very-insecure-key";
DataStoreEncryptionKey = null;

and, in the assertions list:

{
  assertion = managementConfig.DataStoreEncryptionKey != null;
  message = ''
    services.netbird.server: settings.DataStoreEncryptionKey must be set.
    Generate a key with `openssl rand -base64 32` and provide it as a secret, e.g.
    settings.DataStoreEncryptionKey._secret = "/run/secrets/netbird-datastore-key".
  '';
}

Implemented in ad57cf1 — cherry-pick-ready.

Comment on lines +166 to +170
Owner = {
Email = "";
Password = "";
Username = "";
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The embedded IdP's owner is seeded here with a Password field.

The embedded IdP reads the owner's bcrypt hash from Owner.Hash, not Owner.Password. OwnerConfig in management/server/idp/embedded.go has exactly three fields — Email, Hash, Username (no Password) — and the owner is only seeded when c.Owner.Hash != "", so an Owner.Password key is silently dropped by the JSON decoder and the admin is never created (login fails). Renaming the key to Hash fixes it.

This is not version drift — Password has never existed. The embedded IdP was introduced in netbird v0.62.0 (embedded.go is absent in v0.61.0 and earlier), and OwnerConfig has used Hash from that first release through the current v0.74.2. The value the PR supplies is already a bcrypt hash (per the htpasswd comment), so only the key name is wrong.

Likely origin of the name: the owner is mapped into a Dex dex.Password{ Hash: []byte(c.Owner.Hash) } where it's consumed — the Dex type is named Password, but its credential field is Hash. Easy to conflate the type name with the config key.

Suggested change
Owner = {
Email = "";
Password = "";
Username = "";
};
Owner = {
Email = "";
Hash = "";
Username = "";
};

Implemented in 0e667c8 (with the server.md fix below) — cherry-pick-ready.

Comment on lines 51 to 64
TURNConfig = {
Turns = [
{
Proto = "udp";
URI = "turn:${cfg.turnDomain}:${toString cfg.turnPort}";
Username = "netbird";
Password = "netbird";
}
];

CredentialsTTL = "12h";
Secret = "not-secure-secret";
TimeBasedCredentials = false;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @b12f correctly noted, shipping a default TURN server alongside the recommended native relay is wrong — and it also drags in two placeholder secrets (the Turns plaintext password and Secret) that trip the world-readable warnings on every relay-only setup, including the relay example in this PR's own server.md:

The TURNConfig.Secret is world-readable in the Nix Store, you should provide it as a _secret.
A TURNConfig.Turns password is world-readable in the Nix Store, you should provide it as a _secret.

Emptying Turns and nulling Secret fixes both: coturn overrides Turns via server.nix (mkDefault), and relay/no-TURN setups get a clean config. With Secret = null the existing isString world-readable check is already correct, so no warning-gating is needed. (For TimeBasedCredentials, add a small assertion that Secret is set when it is enabled.) A fuller alternative is to drop the whole TURNConfig block from the defaults and populate it only under the coturn branch — cleaner separation, but more invasive. The Stuns list must stay: STUN is needed for direct P2P even with the relay (the relay ships an embedded STUN on :3478, which the default Stuns entry targets).

Suggested change
};
TURNConfig = {
Turns = [ ];
CredentialsTTL = "12h";
Secret = null;
TimeBasedCredentials = false;
};

Implemented in 2a9d1aa — cherry-pick-ready.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow the suggestion code-range in the preview is off here (should replace full 51-64). I don't know why this is. But it's correct in my commit, I you descide to cherry-pick it as is.

Comment on lines +101 to +102
# Generate with: htpasswd -bnBC 10 "" 'your-password' | tr -d ':\n'
Password._secret = "/run/secrets/admin-password-hash";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things here:

  1. The key must be Hash, not Password — same bug as the derived block in management.nix (the value is already a bcrypt hash, per the comment). As written, copy-pasting this leaves the admin unable to log in.
  2. mkpasswd is the more idiomatic generator for a NixOS doc than htpasswd: it's a small standalone package (htpasswd pulls in apacheHttpd), prints just the hash (no tr -d ':\n'), emits the native $2b$ bcrypt variant, and can read the password from a prompt instead of argv. Its default cost is only 5, so pass -R 10. (htpasswd's $2y$ also works — Go's bcrypt gates on the major version only — so this is ergonomics, not a correctness fix.)
Suggested change
# Generate with: htpasswd -bnBC 10 "" 'your-password' | tr -d ':\n'
Password._secret = "/run/secrets/admin-password-hash";
# Generate with: mkpasswd -m bcrypt -R 10 (type the password when prompted)
Hash._secret = "/run/secrets/admin-password-hash";

Implemented in 0e667c8 (same commit as the management.nix owner fix) — cherry-pick-ready.

@shuuri-labs

shuuri-labs commented Jul 8, 2026

Copy link
Copy Markdown
Author

I'm no longer with netbird, and since I've switched to Tailscale in my own homelab, so I'll be ceasing work on this PR. @RafaelKr has integrated my work into his PR, so that should serve as the successor to this one. Thanks for all the reviews/contributions, everyone! Closing.

@shuuri-labs shuuri-labs closed this Jul 8, 2026
@RafaelKr

RafaelKr commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

That's unfortunate. But thank you very much for your initial effort, I really appreciate it! I probably won't have the time to become the lone maintainer of the netbird module, especially because it's pre-1.0 and moving pretty fast right now.

Still here are some notes that might be helpful:

  • For mutually exclusive options we might want to use a lib.types.attrTag instead of enable options. For example instead of using options.services.netbird.server.relay.enableNginx we would use options.services.netbird.server.relay.webserver.nginx, options.services.netbird.server.relay.webserver.traefik, ...
    This way it's even possible to pass additional options for the different webservers.
    This also applies to the IdP and TURN/relay config.
    See services.misskey.reverseProxy.webserver for an example of lib.types.attrTag.
  • It might make sense to implement this as an experimental NixOS Modular Service as it would allow for composition of the different services. This composition pattern would allow to "easily" scale the deployment across multiple hosts

@jvanbruegge

Copy link
Copy Markdown
Contributor

I'd be up to comaintain the module, but I currently don't have time to help with the inital creation

@JuliusFreudenberger

Copy link
Copy Markdown
Contributor

For me it's the same. I use netbird currently in a dockerized setup managed through virtualisation.oci-containers, but would love to migrate to a native approach and maintain the module.
While I don't have the capacity to setup another PR and make big changes, I gladly have a close look over changes and test them.

@RafaelKr

Copy link
Copy Markdown
Contributor

Just to let you know, I'm working on it and making good progress. I already implemented my proposed usage of types.attrTag for the webserver configuration and really like the outcome. I also added traefik as a webserver option and on top of that I got the netbird-proxy (the new beta Reverse Proxy) component configured and running. It's not yet polished enough to be published and it probably will take some more hours of work, but I'm getting there.

I'll do incremental PRs for easy reviewability. So first doing a polished version of the current PR state. Then do another PR for implementing traefik on top. Also for traefik we might wait on #490985. And then the proxy module in another separate PR.

In another PR Caddy could also be added, as it's the third wesberver option that upstream netbird supports, if there's a need for it. For my setup I don't need it.

@TheRealGramdalf

Copy link
Copy Markdown
Contributor

Just to let you know, I'm working on it and making good progress. I already implemented my proposed usage of types.attrTag for the webserver configuration and really like the outcome.
I also added traefik as a webserver option and on top of that I got the netbird-proxy (the new beta Reverse Proxy) component configured and running. It's not yet polished enough to be published and it probably will take some more hours of work, but I'm getting there.

When you say traefik as a webserver, do you mean as a proxy? I don't think traefik can actually host content directly, usually that requires a dedicated webserver such as nginx

Then do another PR for implementing traefik on top. Also for traefik we might wait on #490985. And then the proxy module in another separate PR.

That would be ideal, yes. When the time comes please put any routing configuration under services.traefik.extraFiles."netbird".settings, there will be further documentation on how exactly to do this coming with the PR. Feel free to ping/request my review if you wish.

@woile

woile commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Hey @RafaelKr are you still going to publish a PR?
I'm working on the relay and I was thinking of upstreaming it soon.

Instead of a big PR I want to start uploading chunks. Maybe the proxy next.

@RafaelKr

Copy link
Copy Markdown
Contributor

Yes, I would like to. Hopefully I'll find some time on the next weekend.

When talking about "proxy" we need to be careful what we're actually talking about. Because there are two things that can be called reverse proxy:

  1. The reverse proxy that sits before the netbird components (relay, signal, management, dashboard) and routes traffic depending on the Path. In the netbird documentation it's called External Reverse Proxy. In my upcoming PR I like to call this the "Ingress Proxy", but this may be called differently, if someone has a better name for it.
  2. The NetBird reverse proxy service that launched with v0.65.0.

The second one depends on the first one, as it requires traefik as its ingress proxy. (The External Reverse Proxy documentation states this in their first info-block as "This is not to be confused with the NetBird reverse proxy service that launched with v0.65.0. The NetBird reverse proxy feature is only compatible with Traefik because it requires TLS passthrough, which Traefik supports natively.")

So actually we need to rework the ingress proxy first, to be able to use traefik and only then can introduce the reverse proxy service. And some people suggested to wait for the traefik refactor in #490985 (or maybe #553100 which might become its successor) before doing it here.

So actually we should implement in the following order:

  1. Native relay + embedded IdP server (this PR)
  2. Rework the ingress proxy: Currently it only supports nginx, we need to rebuild this to support other reverse proxy software aswell, starting with additional support for traefik
  3. Now we can introduce the netbird reverse proxy service feature

I'll tag you as soon as I open the PR.

@woile

woile commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

When talking about "proxy" we need to be careful what we're actually talking about

Agreed, I generally assume reverse proxies are an external component of the netbird service, and proxy means the netbird-proxy itself.

Native relay + embedded IdP server (this PR)

Do we need an IdP? I've been running the netbird-relay fine (via podman) using using the NB_AUTH_SECRET. It doesn't seem to require an IdP.

Rework the ingress proxy
Now we can introduce the netbird reverse proxy service feature

Can't we just start with something small? let the netbird-proxy start under systemd, and let users configure their reverse proxies? It could be explained in the markdown.
Being traefik the netbird's "choice" for reverse-proxies (as proxy only works with it), it feels weird having a mix of many reverse-proxies (enableNginx, enableTraefik, etc).

Shouldn't we follow a more single-responsability-principle and focus on netbird only? And start with systemd, and if later it's needed, we can add the modular service, but at least we would have a clear and well-defined interface.

Maybe in the future, if RFC 189 is ever approved, we'll be able to specify "we need any reverse proxy with x and y features".

@RafaelKr

RafaelKr commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Do we need an IdP? I've been running the netbird-relay fine (via podman) using using the NB_AUTH_SECRET. It doesn't seem to require an IdP.

It's not about netbird-relay, It's just much more convenient to have the embedded IdP when you run netbird fully managed with NixOS. Else you need to also configure another external IdP software.

Overall in the end I'd like to be as near to the official way NetBird does things now by default (embedded IdP, netbird-relay instead of coturn, allow Traefik for the netbird-proxy), by still allowing the other (legacy) options that NetBird offers.

Unfortunately I didn't have time to get anything ready for a PR. Still I want to provide a little sneak peek what I'm currently using to sucessfully run 2 NixOS 26.05 hosts to run netbird (one with the reverse proxy feature): https://github.com/RafaelKr/nixpkgs/commits/wip/nixos-26.05-netbird

The first 5 commits are just some fixes on top of this PR (some could already be applied to master, independently of this PR). Then comes an interesting commit, opening up the possibility for different ingress reverse proxies, still only providing nginx at that point. Later there is nixos/netbird: add Traefik ingress backend and then on top of that the reverse proxy component is introduced.

Please don't go into much detail yet, but it's more or less how the final configuration shape may look.

@JuliusFreudenberger

Copy link
Copy Markdown
Contributor

It's just much more convenient to have the embedded IdP when you run netbird fully managed with NixOS. Else you need to also configure another external IdP software

In my current containerized setup I also use the internal IdP but then include my PocketID instance. I deactivated internal authentication mechanism, so I get directly redirected there for SSO.

I agree, that keeping to that structure (and therefore close to upstream's default deployment) is favorable.

@RafaelKr

RafaelKr commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

@woile something I also wanted to comment about:

Can't we just start with something small? let the netbird-proxy start under systemd, and let users configure their reverse proxies? It could be explained in the markdown.
Being traefik the netbird's "choice" for reverse-proxies (as proxy only works with it), it feels weird having a mix of many reverse-proxies (enableNginx, enableTraefik, etc).

Shouldn't we follow a more single-responsability-principle and focus on netbird only? And start with systemd, and if later it's needed, we can add the modular service, but at least we would have a clear and well-defined interface.

Maybe in the future, if RFC 189 is ever approved, we'll be able to specify "we need any reverse proxy with x and y features".

Could you elaborate how you mean that? My understanding is you would like to first provide all current available netbird packages and a basic module layer and then let the users add the "glue" code like reverse proxy routing, do I understand that correctly?

I agree with "it feels weird having a mix of many reverse-proxies" when using enableX flags. I approached a much more modular architecture than that, interestingly it seems to somewhat being a bit related to what RFC 189 is describing. You define the ingress proxy on the netbird module root level. And each netbird service provides its routing configuration based on the selected ingress proxy.

I'll put up the option interface rendered with https://github.com/NuschtOS/search tomorrow, so it's easier to reason about it.

@JuliusFreudenberger

I agree, that keeping to that structure (and therefore close to upstream's default deployment) is favorable.

One thing I'm not sure about is if we should follow netbirds direction with the combined binary. Currently we still use their multi-binary setup. But I think it's mentioned less and less in their documentation. So I don't know if they might drop it and we should also switch to their new binary soon, so instead of having different pkgs per service we have just one package.

@TheRealGramdalf

Copy link
Copy Markdown
Contributor

This is probably already accounted for, but I just wanted to make sure- when you say starting with the embedded IdP, do you mean to say that external IdP would become unavailable until a later PR?
I prefer an external IdP, so I need to be able to set that up even if embedded is the default going forward

@RafaelKr

RafaelKr commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

This is probably already accounted for, but I just wanted to make sure- when you say starting with the embedded IdP, do you mean to say that external IdP would become unavailable until a later PR?
I prefer an external IdP, so I need to be able to set that up even if embedded is the default going forward

You can still use the same oidcConfigEndpoint option which is already available in the current netbird module on the stable channel. Enabling the embedded IdP is fully optional.

Edit: A little clarification: NetBird uses embedded as their new default, but for the NixOS module you still need to actively decide if you enable the embedded IdP or if you set the oidcConfigEndpoint.

@woile

woile commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

My understanding is you would like to first provide all current available netbird packages and a basic module layer and then let the users add the "glue" code like reverse proxy routing, do I understand that correctly?

In principle, yes. I see a lot of PR's trying to do too much, and never landing. I would like to have well discussed interfaces designed for the different chunks (netbird-relay, netbird-proxy, fixes to the dashboard, etc), which are easier to review, and later have the third party things like IdP or reverse proxy, but once we have a solid foundation of the core netbird module.

IdP and reverse proxy also increase the complexity of the module, make it require more maintainability, and a lot of people already have them and can reuse, if not, it's documented. But still, it could be included, and the longer we post-pone, the higher the chances we can use something like RFC 189

netbirds direction with the combined binary

do you have examples in the documentation? We should try to be aligned with the docs, so it feels familiar to the users.

@RafaelKr

Copy link
Copy Markdown
Contributor

@woile got it. Then let's try to get it done chunk by chunk.

As promised I published an option search for the current state: https://rafaelkr.github.io/nixos-search/wip-netbird-f7b59ee5/options

Unfortunately the NüschtOS Search lists options sorted by their string length, but searching for each chunk helps. Those filters are the most interesting ones where feedback for the option structure would be welcome:

  • s.n.s.management.idp (embedded Dex IdP)
  • s.n.s.relay (netbird-relay)
  • s.n.s.ingress (extendable attrTag, search for s.n.s.ingress.nginx or s.n.s.ingress.traefik in particular)
  • services.netbird.server.reverseProxy (netbird-proxy)

Also reading https://github.com/RafaelKr/nixpkgs/blob/f7b59ee5d7fb9a995c530a985dc6e3d65e894c67/nixos/modules/services/networking/netbird/server.md helps.

This is the config I currently use to run my 2 NixOS hosts: https://gist.github.com/RafaelKr/1e21a7cdf123ff08a546735b7d42c7e9

As told, this is only a sneak peek. So it is not to be meant to run in production, doing so is on your own risk.
I need to work through every commit again, clean up and polish things.

I'll be on vacation soon, so realistically I'll only get to work on it at mid to end september again.
@woile at #354032 (comment) you offered to give a shot at implementing e.g. the relay. As I don't want to block the progress on this, you may want to do so. We could also move this discussion to e.g. a matrix room, so we don't flood this discussion further. If you like to do so you can hit me up at the same username I use on GitHub.

P.S. The relay.nix in my fork is still mostly the same version as the one introduced with this PR we're commenting on. I only improved some descriptions and made it compatible with my ingress pattern.
And I just noticed that this PR implemented services.netbird.server.relay.openFirewall in a way that would open up UDP 3478 required for STUN, but also TCP 33080 (relay port) which is not required when running behind the ingress reverse proxy. This may be splitted into services.netbird.server.relay.openFirewall for the TCP relay port and into services.netbird.server.relay.stun.openFirewall for the STUN UDP port.

@RafaelKr

Copy link
Copy Markdown
Contributor

netbirds direction with the combined binary

do you have examples in the documentation? We should try to be aligned with the docs, so it feels familiar to the users.

In their 0.65 release blog post they describe it as

Self-Hosted Improvements

Version 0.65 also introduces a unified NetBird server binary that consolidates multiple services into a single container for self-hosted deployments. This means fewer containers to manage, simpler Docker Compose stacks, and faster setup. If you're running an existing deployment, the quick start script has been updated and we've published a migration guide to walk you through enabling the reverse proxy on your current installation.

We've also switched the default reverse proxy in the quick start script from Caddy to Traefik. Traefik is required for the reverse proxy feature because it supports TLS passthrough, which lets the NetBird proxy handle TLS termination directly.

And it's just the default they now use when using their quickstart installation guide.

IIRC in the past they still used the multi-container setup for a distributed architecture (running accross multiple servers) / high-availability (commercial license required). But I think they updated their docs now and also use the combined binary there.

So maybe it would be better if we would also add the combined binary as a nix package and use that in each netbird component. Maybe adding it would also be a good start instead of adding the relay first.

BTW: https://github.com/netbirdio/netbird/blob/v0.77.1/infrastructure_files/getting-started.sh is always a good reference to see how things are setup.

@adamcstephens

adamcstephens commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

I recently merged the addition of the netbird-combined package in #554815

After stumbling through the complexity of the current module, I realized I still didn't have a fully working setup and pivoted to setting up the combined. I've been proving a private module for this out and had planned to submit it once I've got a bit more runtime on it.

So maybe it would be better if we would also add the combined binary as a nix package and use that in each netbird component.

The combined binary takes a config that manages everything. I'm not sure I'd try and use it in each component, but instead create a new standalone module.

@adamcstephens

adamcstephens commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

I also submitted this idea: netbirdio/netbird#7290

This simple patch exposes dex's connectors config for pre-population of an oidc provider. The management server currently supports this option. If we want, we could apply this patch to add this bit of declarative config in nixos. With this, my config is:

services.netbird.server = {
  domain = site.services.netbird;

  dashboard = {
    enable = true;
    package = inputs.nixos-unstable.legacyPackages.${pkgs.stdenv.hostPlatform.system}.netbird-dashboard;
    enableNginx = true;
    managementServer = "https://${config.services.netbird.server.domain}";
    settings = {
      AUTH_AUTHORITY = "https://${config.services.netbird.server.domain}/oauth2";
      AUTH_CLIENT_ID = "netbird-dashboard";
      AUTH_AUDIENCE = "netbird-dashboard";
      AUTH_SUPPORTED_SCOPES = "openid profile email groups";
      AUTH_REDIRECT_URI = "/nb-auth";
      AUTH_SILENT_REDIRECT_URI = "/nb-silent-auth";
      USE_AUTH0 = "false";
    };
  };

  combined = {
    enable = true;
    package =
      inputs.nixpkgs-netbird-combined.legacyPackages.${pkgs.stdenv.hostPlatform.system}.netbird-combined.overrideAttrs
        (old: {
          patches = (old.patches or [ ]) ++ [ ./auth-dex-config.patch ];
        });

    settings.server = {
      listenAddress = ":${toString port}";
      # port is required
      exposedAddress = "https://${site.services.netbird}:443";
      authSecret._secret = config.age.secrets.netbird-relay-auth-secret.path;
      disableAnonymousMetrics = false;
      disableGeoliteUpdate = false;
      auth = {
        issuer = "https://${config.services.netbird.server.domain}/oauth2";
        localAuthDisabled = true;
        signKeyRefreshEnabled = true;
        dashboardRedirectURIs = [
          "https://${config.services.netbird.server.domain}/nb-auth"
          "https://${config.services.netbird.server.domain}/nb-silent-auth"
        ];
        # openssl rand -base64 32
        sessionCookieEncryptionKey._secret = config.age.secrets.netbird-cookie-key.path;

        staticConnectors = [
          {
            type = "oidc";
            id = "robins";
            name = "Robins";
            config = {
              issuer = "https://${site.services.kanidm}/oauth2/openid/netbird";
              clientID = "netbird";
              clientSecret._secret = config.age.secrets.netbird-oauth-secret.path;
              redirectURI = "https://${config.services.netbird.server.domain}/oauth2/callback";
              scopes = [
                "openid"
                "profile"
                "email"
                "groups"
              ];
            };
          }
        ];
      };

We could probably make this a bit more user friendly too, by pre-configuring dashboard.

@woile

woile commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

IIRC in the past they still used the multi-container setup for a distributed architecture (running accross multiple servers) / high-availability (commercial license required). But I think they updated their docs now and also use the combined binary there.

Combined binary or not, I think to the service interface we expose, it doesn't matter. E.g: Users should be able to run multiple relays, without having to run everything else. This can be done with the combined binary or just with netbird-relay.

Agreed that we should adopt it for the module. Although, it might be better to ask netbird for clarification.

I've asked in matrix for a new space, I'll share it once available, otherwise I'll open a Github issue here.

@adamcstephens

Copy link
Copy Markdown
Contributor

I created a Matrix room at #nixos-netbird:robins.wtf . We can get it adopted by the project, but creating it is the first step.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: documentation This PR adds or changes documentation 8.has: maintainer-list (update) This PR changes `maintainers/maintainer-list.nix` 8.has: module (update) This PR changes an existing module in `nixos/` 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin. 10.rebuild-linux: 1-10 This PR causes between 1 and 10 packages to rebuild on Linux. 12.first-time contribution This PR is the author's first one; please be gentle!

Projects

None yet

Development

Successfully merging this pull request may close these issues.