The architecture

  • management server — the brain: stores peers, ACLs, manages config. Talks to clients over gRPC.
  • signal server — coordinates WireGuard public-key exchange + NAT-traversal hints between peers.
  • TURN server (coturn) — relay for peers behind symmetric NATs that can't NAT-traverse directly. Fallback only.
  • dashboard — web UI for managing the network.
  • clients — per-OS WireGuard wrapper that authenticates against your OIDC provider.

Install via docker compose

NetBird publishes a setup script that templates a full compose file:

curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started-with-zitadel.sh \
    -o getting-started.sh
chmod +x getting-started.sh

# Required env: your domain + admin email
NETBIRD_DOMAIN=netbird.example.com \
NETBIRD_LETSENCRYPT_EMAIL=admin@example.com \
./getting-started.sh

The script generates a compose file with:

  • NetBird management server
  • Signal server
  • TURN server (coturn)
  • Dashboard (web UI)
  • Zitadel as the bundled OIDC IdP (if you don't already have one)
  • Caddy for TLS termination + reverse proxy

For BYO OIDC (Authentik / Keycloak / Auth0), skip the Zitadel container and configure the management server to use your existing IdP via env vars.

Using your existing OIDC IdP

# Authentik example - in NetBird's management.json
{
  "HttpConfig": {
    "AuthIssuer": "https://auth.example.com/application/o/netbird/",
    "AuthAudience": "<client-id from Authentik>",
    "AuthKeysLocation": "https://auth.example.com/application/o/netbird/jwks/",
    "OIDCConfigEndpoint": "https://auth.example.com/application/o/netbird/.well-known/openid-configuration"
  },
  "IdpManagerConfig": {
    "ManagerType": "authentik",
    "ClientConfig": {
      "Issuer": "https://auth.example.com/application/o/netbird/",
      "TokenEndpoint": "...",
      "ClientID": "<client-id>",
      "ClientSecret": "<client-secret>"
    }
  }
}

Configure a NetBird application in Authentik (see Authentik tutorial) with the redirect URIs, copy client ID + secret, restart NetBird. Users log into the NetBird client via SSO; new peers join the mesh after auth.

Install a client

# Linux
sudo curl -fsSL https://pkgs.netbird.io/install.sh | sh
sudo apt install netbird

# macOS
brew install netbirdio/tap/netbird-ui

# Windows / iOS / Android
# Download from the NetBird site or app stores

# Connect (opens a browser to your OIDC IdP)
sudo netbird up --management-url https://netbird.example.com

# Verify
sudo netbird status

After login, the client requests a WireGuard public key, registers with NetBird's management server, and gets a peer ID + ACL group assignments. All other peers in the same network can now reach this one via WireGuard.

The dashboard

Browse to https://netbird.example.com. Login with SSO. The dashboard shows:

  • All peers + their connection status + last-seen time
  • Groups (per-user, per-team, or arbitrary labels)
  • Access policies (which groups can reach which groups, on which ports)
  • Activity log (who joined / left / failed auth)
  • Setup keys (for headless / unattended joins)

Group-based ACLs

Tailscale-style ACLs are per-tag JSON. NetBird's ACLs are visual / declarative:

  1. Create groups: "engineers", "servers-prod", "servers-dev", "support"
  2. Assign peers to groups via OIDC claims (auto-mapped) or manually in the UI
  3. Write policies: "engineers can SSH (port 22) to servers-prod"; "support can reach servers-dev:80,443"

Policies are evaluated at the WireGuard layer; non-matching traffic is dropped. No per-peer firewalls to maintain.

Setup keys for headless joins

For CI / Kubernetes pods / IoT devices that can't do interactive OIDC:

# In the dashboard: Setup Keys → Create new
# Pick: reusable / one-time, validity window, assigned groups

# On the headless machine
sudo netbird up --management-url https://netbird.example.com \
    --setup-key <key>

One-shot keys auto-expire on first use; reusable keys can onboard whole batches. Useful for Terraform-provisioned VMs that join the mesh automatically at boot.

Routing networks (exit nodes / subnet routes)

For "expose this LAN subnet to mesh peers":

# On a peer that's on the LAN you want to expose
sudo netbird routes add --network 10.0.5.0/24

# Or via the dashboard: configure a network route on that peer
# Other peers in matching ACL groups can now reach 10.0.5.0/24 via this peer

Combined with ACLs, this is the canonical "remote employees can reach office LAN" pattern.

NetBird vs alternatives

  • Tailscale — commercial SaaS; polished UX; free tier limited. Use if you want zero-ops.
  • Headscale — open-source Tailscale control-plane reimplementation. Drop-in for Tailscale clients. ACLs are Tailscale JSON. Smaller scope than NetBird (no built-in dashboard for general users).
  • NetBird — self-hostable; OIDC-first; group-based ACLs; nicer admin UI than Headscale. Different client (not compatible with Tailscale clients).
  • Nebula — Slack's mesh VPN. Lower-level; no central management plane; certificate-based. Different shape.
  • OpenZiti — richer policy engine; less WireGuard-specific.
  • Plain WireGuard (see that tutorial) — static config; no NAT traversal coordination; doesn't scale beyond ~10 peers without an orchestration layer.

When NetBird is the right pick

  • Team of 5-500 + already has an OIDC IdP.
  • Want self-hosted (not SaaS).
  • Want a dashboard for ACL management, not Tailscale-JSON files.
  • Mixed-OS fleet (NetBird clients exist for every major OS).

When it isn't

  • For a 2-person homelab, plain WireGuard is fine.
  • For a team already deep in Tailscale, switching to NetBird means re-onboarding everyone.
  • For very-high-throughput links (multi-Gbps continuous), raw WireGuard without NetBird's management overhead may win on per-packet latency.