Merge remote-tracking branch 'origin/main' into feat/kimi-3-agent-networks

This commit is contained in:
mlsmaycon
2026-07-22 17:45:41 +00:00
33 changed files with 1121 additions and 81 deletions

View File

@@ -8,11 +8,23 @@ on:
pull_request:
paths:
- 'docker/**'
- '.dockerignore'
- '.github/workflows/build_n_push.yml'
- 'next.config.mjs'
- 'package.json'
- 'package-lock.json'
workflow_dispatch:
permissions:
contents: read
# Serialise non-PR runs while allowing superseded PR validation to be canceled.
# Builds publish only an immutable SHA tag; the mutable ref tag is promoted
# separately after the build and a final ref check.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
docs_build_n_push:
runs-on: ubuntu-latest
@@ -55,7 +67,10 @@ jobs:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Docker build and push
# Non-PR runs publish an immutable image first. A canceled or stale run
# can leave this tag behind, but it cannot change :main (or another
# mutable ref tag).
- name: Docker build and publish immutable image
uses: docker/build-push-action@v6
with:
context: .
@@ -64,5 +79,39 @@ jobs:
# Keep a single-arch manifest (no attestation index) so the server's
# `docker compose pull` stays happy.
provenance: false
tags: ${{ steps.meta.outputs.tags }}
tags: netbirdio/docs.netbird.io:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}
# Promotion is a single remote manifest-tag update after the expensive
# build. Combined with serialisation and the final ref comparison, an
# older run cannot replace the mutable tag after a newer run publishes.
- name: Promote current image to ref tag
if: github.event_name != 'pull_request'
env:
SOURCE_IMAGE: netbirdio/docs.netbird.io:${{ github.sha }}
TARGET_TAGS: ${{ steps.meta.outputs.tags }}
run: |
remote_sha="$(git ls-remote origin "${GITHUB_REF}" | awk 'NR == 1 { print $1 }')"
if [ -z "$remote_sha" ]; then
echo "Could not resolve ${GITHUB_REF} on origin" >&2
exit 1
fi
if [ "$GITHUB_SHA" != "$remote_sha" ]; then
echo "Skipping tag promotion: ${GITHUB_SHA} is stale; ${GITHUB_REF} is now ${remote_sha}"
exit 0
fi
if [ -z "$TARGET_TAGS" ]; then
echo "Docker metadata produced no target tags" >&2
exit 1
fi
while IFS= read -r target_tag; do
if [ -n "$target_tag" ]; then
docker buildx imagetools create \
--prefer-index=false \
--tag "$target_tag" \
"$SOURCE_IMAGE"
fi
done <<< "$TARGET_TAGS"

View File

@@ -8,6 +8,19 @@ on:
default: "refs/tags/vX.Y.Z"
type: string
permissions:
actions: read
contents: read
# One run at a time: overlapping dispatches (several release tags in a day
# happen — see run history) regenerate the same files and would collide. A
# queued run superseded by a newer dispatch is fine: every run regenerates the
# whole directory from its own tag, so the latest dispatch is the end state.
# NB "latest dispatched", not "newest tag" — re-dispatching an older tag after
# a newer one regresses the pages to the older spec.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
generate_api_pages:
runs-on: macos-latest
@@ -23,6 +36,16 @@ jobs:
with:
token: ${{ secrets.DEV_GITHUB_TOKEN }}
# A run queued behind another (see concurrency above) checks out the
# commit pinned at its dispatch time, not the branch's current tip.
# Sync before generating so the diff is computed against reality —
# otherwise the pre-push rebase replays a stale-base snapshot, and a
# file the newer spec removed could silently survive from the prior run.
- name: Sync to branch tip
run: |
git fetch origin "${GITHUB_REF_NAME}"
git reset --hard "origin/${GITHUB_REF_NAME}"
- name: Create directory
run: mkdir -p generator/openapi
@@ -43,8 +66,17 @@ jobs:
- name: Remove old generated files
run: rm -rf src/pages/ipa/resources/*
- name: Npm install
run: npm install
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
# npm ci: this workflow never changes dependencies, so install exactly
# the committed lockfile and never mutate it (macos-latest's npm can
# differ from the one that generated package-lock.json).
- name: Install dependencies
run: npm ci
- name: Generate api pages for netbird main openapi definition
run: npx ts-node generator/index.ts gen --input generator/openapi/expanded.yml --output src/pages/ipa/resources
@@ -52,18 +84,53 @@ jobs:
- name: Check git diff and untracked files
id: git_diff
run: |
if [ -n "$(git status --porcelain)" ]; then
if [ -n "$(git status --porcelain src/pages/ipa/resources)" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
- name: Commit and push changes
# Concurrency serialises runs but does not guarantee dispatch order. A
# delayed older run must not overwrite output from a newer dispatch.
- name: Check whether this is the latest dispatch
id: freshness
if: steps.git_diff.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
latest_run_id="$(
curl --fail --silent --show-error \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--get \
--data-urlencode "event=workflow_dispatch" \
--data-urlencode "branch=${GITHUB_REF_NAME}" \
--data-urlencode "per_page=1" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/workflows/generate_api_pages.yml/runs" \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["workflow_runs"][0]["id"])'
)"
if [ "$GITHUB_RUN_ID" = "$latest_run_id" ]; then
echo "push=true" >> "$GITHUB_OUTPUT"
else
echo "push=false" >> "$GITHUB_OUTPUT"
echo "Skipping generated-page commit: run ${GITHUB_RUN_ID} was superseded by ${latest_run_id}"
fi
- name: Commit and push changes
if: steps.git_diff.outputs.changed == 'true' && steps.freshness.outputs.push == 'true'
run: |
git config --global user.email "dev@netbird.io"
git config --global user.name "netbirddev"
git add -A
# Stage only the regenerated API pages — never sweep up incidental
# changes like a rewritten package-lock.json.
git add src/pages/ipa/resources
git commit -m "Update API pages with v${{ steps.semver_parser.outputs.fullversion }}"
git push --force
# The run takes minutes; if the branch moved meanwhile, replay our
# single generated-files commit on top instead of failing the push.
# A conflict is only possible against a concurrent edit of the
# generated files themselves and fails the run loudly.
git pull --rebase origin "${GITHUB_REF_NAME}"
git push

View File

@@ -16,7 +16,7 @@ There is no test suite in this project. Validate changes with `npm run build`.
npm install # Install dependencies
npm run dev # Start dev server (also runs gen:llm, gen:edit-routes, gen:last-updated, gen:sitemap)
npm run build # Production build (also runs gen:llm, gen:edit-routes, gen:last-updated, gen:sitemap)
npm run start # Serve the production build
npm run start # Serve the production build (warns under `output: 'standalone'` — safe to ignore locally; prod runs `node server.js` from `.next/standalone`)
npm run lint # ESLint (next/core-web-vitals) on src/
npm run gen # Regenerate API docs from NetBird OpenAPI spec
npm run gen:llm # Regenerate LLM-friendly markdown (auto-runs with dev/build)

View File

@@ -66,6 +66,9 @@
<key>disableAutoConnect</key>
<false/>
<key>disableAutostart</key>
<false/>
<key>disableClientRoutes</key>
<false/>

View File

@@ -103,6 +103,8 @@
<!--
<key>disableAutoConnect</key>
<false/>
<key>disableAutostart</key>
<false/>
<key>disableClientRoutes</key>
<false/>
<key>disableServerRoutes</key>

View File

@@ -58,6 +58,7 @@ preSharedKey="$NULL" # secret; redacted in log
allowServerSSH='true'
blockInbound="$NULL"
disableAutoConnect="$NULL"
disableAutostart="$NULL"
disableClientRoutes="$NULL"
disableServerRoutes="$NULL"
disableMetricsCollection="$NULL"
@@ -167,6 +168,7 @@ main() {
is_set "$allowServerSSH" && emit_bool allowServerSSH "$allowServerSSH"
is_set "$blockInbound" && emit_bool blockInbound "$blockInbound"
is_set "$disableAutoConnect" && emit_bool disableAutoConnect "$disableAutoConnect"
is_set "$disableAutostart" && emit_bool disableAutostart "$disableAutostart"
is_set "$disableClientRoutes" && emit_bool disableClientRoutes "$disableClientRoutes"
is_set "$disableServerRoutes" && emit_bool disableServerRoutes "$disableServerRoutes"
is_set "$disableMetricsCollection" && emit_bool disableMetricsCollection "$disableMetricsCollection"

View File

@@ -24,6 +24,9 @@
<string id="DisableAutoConnect_Name">Disable auto-connect</string>
<string id="DisableAutoConnect_Help">When enabled, the NetBird tunnel does not auto-connect at daemon startup. Equivalent to --disable-auto-connect.</string>
<string id="DisableAutostart_Name">Disable autostart</string>
<string id="DisableAutostart_Help">When enabled, the NetBird GUI is prevented from registering itself as an OS autostart entry on fresh installs, and any existing OS autostart entry registration is removed on the next GUI launch (Windows Registry Run key, macOS Login Item, Linux .desktop). Once the admin lifts the policy, the setting stays off until the user re-enables it in Settings.</string>
<string id="DisableClientRoutes_Name">Disable client routes</string>
<string id="DisableClientRoutes_Help">When enabled, this client will not consume routes advertised by routing peers. Equivalent to --disable-client-routes.</string>

View File

@@ -64,6 +64,18 @@
<disabledValue><decimal value="0" /></disabledValue>
</policy>
<policy name="DisableAutostart"
class="Machine"
displayName="$(string.DisableAutostart_Name)"
explainText="$(string.DisableAutostart_Help)"
key="Software\Policies\NetBird"
valueName="DisableAutostart">
<parentCategory ref="NetBird" />
<supportedOn ref="SUPPORTED_NetBird_All" />
<enabledValue><decimal value="1" /></enabledValue>
<disabledValue><decimal value="0" /></disabledValue>
</policy>
<policy name="DisableClientRoutes"
class="Machine"
displayName="$(string.DisableClientRoutes_Name)"

View File

@@ -1,25 +1,5 @@
import { execSync } from 'child_process'
/**
* Get the last modified date for a file from git history.
* Returns YYYY-MM-DD or null if the file is not tracked / git is unavailable.
*
* Prefer buildGitDateMap() when you need dates for many files — this spawns a
* git process per call, which is ~300x slower across a full page tree.
*/
export function getGitLastModified(filePath) {
try {
const date = execSync(`git log -1 --format=%cI -- "${filePath}"`, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'ignore'],
}).trim()
return date ? date.split('T')[0] : null
} catch {
return null
}
}
let _dateMapCache
/**
@@ -31,22 +11,28 @@ let _dateMapCache
* most recent commit — the same value `git log -1 -- <path>` returns. Paths are
* repo-relative with forward slashes, matching path.relative(repoRoot, file).
*
* Returns an empty map if git is unavailable (e.g. inside the Docker image,
* which has no git binary) or the checkout is shallow; callers then fall back
* to no date, exactly as the per-file getGitLastModified() did.
* Note: `git log --name-only` lists no files for merge commits, so content
* introduced by a conflict-resolving merge is attributed to its source
* commits. That matches this repo's squash-merge workflow; revisit with
* `--diff-merges=first-parent` if long-lived branches are ever merged.
*
* Returns an empty map — with a warning, since every page then renders without
* an "Updated" date and the sitemap loses <lastmod> — when git is unavailable
* or the checkout is shallow (e.g. actions/checkout without fetch-depth: 0,
* where git log would attribute one identical, wrong date to every file).
*/
export function buildGitDateMap() {
if (_dateMapCache) return _dateMapCache
const map = new Map()
try {
// On a shallow clone (e.g. actions/checkout without fetch-depth: 0) git log
// only sees the fetched commits, so every file would report the same wrong
// date. Emit no dates rather than wrong ones.
const shallow = execSync('git rev-parse --is-shallow-repository', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'ignore'],
}).trim()
if (shallow === 'true') {
console.warn(
'[git-dates] shallow clone detected — emitting no per-page dates (fetch full history to enable them)'
)
_dateMapCache = map
return map
}
@@ -68,8 +54,10 @@ export function buildGitDateMap() {
}
if (currentDate && !map.has(line)) map.set(line, currentDate)
}
} catch {
// git unavailable — return the empty map, callers fall back to null.
} catch (err) {
console.warn(
`[git-dates] could not read git history — emitting no per-page dates: ${err.message}`
)
}
_dateMapCache = map
return map

View File

@@ -544,6 +544,7 @@ export const docsNavigation = [
links: [
{ title: 'Quickstart Guide', href: '/selfhosted/selfhosted-quickstart' },
{ title: 'Automated Setup', href: '/selfhosted/automated-setup' },
{ title: 'Commercial License', href: '/selfhosted/enterprise' },
{
title: 'Infrastructure as Code',
isOpen: false,
@@ -571,6 +572,10 @@ export const docsNavigation = [
title: 'Environment Variables',
href: '/selfhosted/environment-variables',
},
{
title: 'Admin CLI',
href: '/selfhosted/maintenance/admin-cli',
},
{
title: 'Scaling Your Deployment',
href: '/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment',
@@ -808,6 +813,8 @@ export const docsNavigation = [
links: [
{ title: 'Desktop App', href: '/client/desktop-app' },
{ title: 'Profiles', href: '/client/profiles' },
{ title: 'gRPC Daemon Socket', href: '/client/grpc-socket' },
{ title: 'HTTP/JSON Daemon Socket', href: '/client/json-socket' },
{ title: 'Environment Variables', href: '/client/environment-variables' },
{ title: 'MDM Integration', href: '/client/mdm-integration' },
{

View File

@@ -0,0 +1,235 @@
import { Note, Warning } from '@/components/mdx'
export const description =
'Connect applications and development tools to the local NetBird daemon through its gRPC socket.'
# gRPC Daemon Socket
The NetBird daemon exposes a local gRPC API that the NetBird CLI and desktop app use. Your integration can use the same socket to check status, manage the connection, inspect configuration, work with profiles and networks, and call other local daemon operations.
The gRPC socket is the primary local daemon API. If your integration cannot use gRPC or generated Protocol Buffer bindings, use the [HTTP/JSON daemon socket](/client/json-socket) instead.
<Note>
The gRPC socket controls the **local NetBird daemon**. It is separate from the
NetBird Management API and does not replace it.
</Note>
## Default Addresses
The HTTP/JSON gateway is optional. The gRPC socket is available whenever the NetBird daemon is running.
| Platform | Default address |
| --------------- | ------------------------------ |
| Linux and macOS | `unix:///var/run/netbird.sock` |
| Windows | `tcp://127.0.0.1:41731` |
<Warning>
**Security warning:** On supported Linux installations, the default Unix
socket allows read and write access for local users. The API exposes control
operations as well as status. If you require local-user isolation, place a
custom socket inside a restricted directory and ensure that the directory is
recreated securely at boot.
</Warning>
On Linux installations that use an instance-specific systemd service, the socket can instead be under `/var/run/netbird/<instance>.sock`. The NetBird CLI automatically uses the only socket in that directory when the default socket does not exist. If multiple instance sockets exist, pass the intended address explicitly with `--daemon-addr`.
You can see the address option in the CLI help:
```shell
netbird --help
```
## Configure the Address
Use the global `--daemon-addr` option when installing or reconfiguring the service. The address must use one of these formats:
```text
unix:///path/to/netbird.sock
tcp://host:port
```
<Warning>
**Connectivity warning:** Reconfiguring the service restarts NetBird and can
briefly interrupt tunnel connectivity, routes, and DNS.
</Warning>
### Use a Custom Unix Socket
```shell
sudo netbird service reconfigure \
--daemon-addr unix:///var/run/netbird-integration-grpc.sock
```
The parent directory must already exist and be writable by the NetBird service. If you use a dedicated directory to restrict access, configure systemd or tmpfiles to recreate it securely because directories under `/run` do not persist across reboots.
After changing the socket, pass the same address to NetBird CLI commands that need to contact the daemon:
```shell
netbird --daemon-addr unix:///var/run/netbird-integration-grpc.sock status
```
<Note>
This `--daemon-addr` command only works after configuring the custom gRPC
listener above. If the daemon uses its default listener, run `netbird status`
instead. Do not pass an HTTP/JSON socket to `--daemon-addr`.
</Note>
### Use a TCP Socket
```shell
sudo netbird service reconfigure \
--daemon-addr tcp://127.0.0.1:41731
```
Then specify that address when using the CLI on platforms where it is not the default:
```shell
netbird --daemon-addr tcp://127.0.0.1:41731 status
```
<Warning>
**Security warning:** The daemon gRPC server does not add authentication or
TLS. Keep TCP listeners bound to a trusted interface such as `127.0.0.1` and
do not expose them to an untrusted network. The API includes operations that
can read or change the local NetBird daemon's state.
</Warning>
## Service Definition
The socket provides `daemon.DaemonService`. NetBird's
[`client/proto/daemon.proto`](https://github.com/netbirdio/netbird/blob/main/client/proto/daemon.proto)
file lists the available methods, streaming types, and request and response schemas.
Here are a few common methods. Check `daemon.proto` for the full list.
| Method | Type | Purpose |
| ----------------- | ---------------- | --------------------------------------------------------- |
| `Status` | Unary | Read connection status and peer information. |
| `SubscribeStatus` | Server streaming | Receive the current status and subsequent status changes. |
| `GetConfig` | Unary | Read the active daemon configuration. |
| `ListNetworks` | Unary | List networks available to the client. |
| `Up` | Unary | Start the NetBird connection. |
| `Down` | Unary | Stop the NetBird connection. |
| `ListProfiles` | Unary | List configured client profiles. |
| `SubscribeEvents` | Server streaming | Receive daemon system events. |
The service also includes methods that change configuration, profiles, network selection, logging, and other daemon state. Only give socket access to processes that you trust to control the local NetBird client.
<Note>
The daemon does not enable gRPC server reflection. Tools and integrations must
use `daemon.proto`, generated client bindings, or a compiled descriptor set to
learn the service schema.
</Note>
## Test the Socket with grpcurl
[`grpcurl`](https://github.com/fullstorydev/grpcurl) is a command-line tool for invoking gRPC methods. Because it needs access to the `.proto` files to perform requests, we need to download the repository at the same version that the daemon to not have a mismatch on the Protocol Buffer definition.
```shell
export NETBIRD_VERSION=v$(netbird version)
git clone --depth 1 \
--branch "$NETBIRD_VERSION" \
https://github.com/netbirdio/netbird.git
cd netbird
```
Run the following examples from the root of the cloned `netbird` repository.
### Query Status Through the Unix Socket
```shell
grpcurl \
-plaintext \
-unix \
-import-path ./client/proto \
-proto daemon.proto \
-d '{}' \
unix:///var/run/netbird.sock \
daemon.DaemonService/Status
```
To request full peer status, set fields from `StatusRequest` in the JSON request body:
**Sensitive output:** Full peer status may contain network topology, endpoint, route, and peer information. Review or redact it before sharing the output.
```shell
grpcurl \
-plaintext \
-unix \
-import-path ./client/proto \
-proto daemon.proto \
-d '{"getFullPeerStatus": true}' \
unix:///var/run/netbird.sock \
daemon.DaemonService/Status
```
### Query Status Through a TCP Socket
For the default Windows listener or a custom loopback TCP listener:
```shell
grpcurl \
-plaintext \
-import-path ./client/proto \
-proto daemon.proto \
-d '{}' \
127.0.0.1:41731 \
daemon.DaemonService/Status
```
`-plaintext` is required because the local daemon socket does not use TLS. For a Unix socket, pass the full `unix:///...` URI and include `-unix`. The URI form also avoids a Unix-socket regression in some `grpcurl` 1.9.x builds.
### Subscribe to Status Changes
Server-streaming methods remain connected and print each response as it arrives. For example:
```shell
grpcurl \
-plaintext \
-unix \
-import-path ./client/proto \
-proto daemon.proto \
-d '{}' \
unix:///var/run/netbird.sock \
daemon.DaemonService/SubscribeStatus
```
Press `Ctrl+C` to end the stream.
## Build an Integration
For a long-running integration, generate a gRPC client from the same `daemon.proto` revision as the installed NetBird client:
1. Download or vendor `client/proto/daemon.proto` from the NetBird repository.
2. Generate client bindings with the Protocol Buffer and gRPC tools for your language.
3. Create a local gRPC channel to the configured Unix or TCP address without TLS. That does not make the socket safe to expose remotely.
4. Create a `daemon.DaemonService` client from that channel.
5. Call unary methods or consume server streams using the generated request and response types.
Use a protobuf definition and generated bindings that match your deployed NetBird version. A newer client may call methods or use fields that an older daemon does not support.
The HTTP/JSON gateway exposes this service for environments where generated gRPC clients are unavailable. See [HTTP/JSON Daemon Socket](/client/json-socket) for setup and HTTP examples.
## Troubleshooting
### The Unix Socket Does Not Exist
Check that the service is running:
```shell
sudo netbird service status
```
If you use an instance-specific service, inspect `/var/run/netbird/` for its socket or pass the configured address with `--daemon-addr`.
### A Tool Reports That Reflection Is Unsupported
This is expected. Supply `daemon.proto` with your tool's equivalent of the `grpcurl -proto` and `-import-path` options, or use a descriptor set generated from that file.
### A Client Receives an Unavailable or Connection-Refused Error
Confirm that the daemon is running and that the integration uses the same socket address as the service. For a Unix socket, also verify access to the socket and each parent directory. For TCP, verify the host and port and ensure the listener remains bound to a trusted interface.
### A Method or Field Is Unimplemented
The integration's generated bindings may be newer than the installed NetBird daemon. Compare the installed client version with the revision of `daemon.proto` used to generate the bindings, then use a compatible schema or upgrade NetBird.

View File

@@ -0,0 +1,246 @@
import { Note, Warning } from '@/components/mdx'
export const description =
'Use the NetBird daemon HTTP/JSON socket to build local integrations in environments where gRPC is unavailable.'
# HTTP/JSON Daemon Socket
The NetBird daemon can expose its local API over HTTP with JSON request and response bodies. A [gRPC-Gateway](https://grpc-ecosystem.github.io/grpc-gateway/) receives those HTTP/JSON requests and passes them to the daemon's gRPC API.
The daemon's primary local API is documented in [gRPC Daemon Socket](/client/grpc-socket). Use that socket when your integration supports gRPC and generated Protocol Buffer bindings.
Use the JSON socket when your integration cannot use gRPC. For example, it works well in runtimes that only have an HTTP client, local monitoring agents, and application sandboxes where adding a gRPC client and generated protobuf bindings is not practical.
<Note>
The HTTP/JSON daemon socket requires NetBird client v0.75.0 or later.
</Note>
<Note>
The HTTP/JSON socket controls the **local NetBird daemon**. It is separate
from the NetBird Management API and does not replace it.
</Note>
## Enable the JSON Socket
The JSON socket is disabled by default. To enable it while installing the NetBird service, run:
```shell
sudo netbird service install --enable-json-socket
```
The default address is:
```text
unix:///var/run/netbird-http.sock
```
<Warning>
**Security warning:** On supported Linux installations, the default Unix
socket allows read and write access for local users. The API exposes control
operations as well as status. If you require local-user isolation, place a
custom socket inside a restricted directory and ensure that the directory is
recreated securely at boot.
</Warning>
To enable it on an existing installation, reconfigure the service:
<Warning>
**Connectivity warning:** Every `netbird service reconfigure` command on this
page restarts NetBird and can briefly interrupt tunnel connectivity, routes,
and DNS.
</Warning>
```shell
sudo netbird service reconfigure --enable-json-socket
```
NetBird saves this setting with the service configuration, so it stays enabled after a restart.
### Use a Custom Unix Socket
Pass an address with the `unix://` scheme to change the socket path:
```shell
sudo netbird service reconfigure \
--enable-json-socket \
--json-socket unix:///var/run/netbird-integration-http.sock
```
The parent directory must already exist and be writable by the NetBird service. If you use a dedicated directory to restrict access, configure systemd or tmpfiles to recreate it securely because directories under `/run` do not persist across reboots.
`--json-socket` configures an HTTP endpoint. Query it with an HTTP client such as `curl`; do not pass this address to `netbird --daemon-addr`.
```shell
curl --silent --show-error \
--unix-socket /var/run/netbird-integration-http.sock \
--request POST \
--header 'Content-Type: application/json' \
--data '{}' \
--write-out '\n' \
http://localhost/daemon.DaemonService/Status
```
### Use a TCP Socket
Pass an address with the `tcp://` scheme to expose the gateway over TCP:
```shell
sudo netbird service reconfigure \
--enable-json-socket \
--json-socket tcp://127.0.0.1:8080
```
<Warning>
**Security warning:** The gateway does not add authentication or TLS. Keep TCP
listeners bound to a trusted interface such as `127.0.0.1` and do not expose
them to an untrusted network. The API includes operations that can read or
change the local NetBird daemon's state.
</Warning>
To disable the gateway again, run:
```shell
sudo netbird service reconfigure --enable-json-socket=false
```
## Make Requests
Each daemon RPC is exposed as an HTTP `POST` endpoint using this path format:
```text
/daemon.DaemonService/<MethodName>
```
Send the request message as JSON with the `Content-Type: application/json` header. If an RPC has no required request fields, send an empty JSON object (`{}`). Responses use the standard [Protocol Buffers JSON mapping](https://protobuf.dev/programming-guides/json/).
### Query Status Through the Unix Socket
With the default socket path:
```shell
curl --silent --show-error \
--unix-socket /var/run/netbird-http.sock \
--request POST \
--header 'Content-Type: application/json' \
--data '{}' \
--write-out '\n' \
http://localhost/daemon.DaemonService/Status
```
The response is a JSON representation of the daemon's status response. For example, it includes the daemon status and version:
```json
{
"status": "Connected",
"daemonVersion": "..."
}
```
The exact fields and values depend on the client version and current connection state.
### Query Status Through a TCP Socket
If the gateway is listening on `tcp://127.0.0.1:8080`, use a normal HTTP request:
```shell
curl --silent --show-error \
--request POST \
--header 'Content-Type: application/json' \
--data '{}' \
--write-out '\n' \
http://127.0.0.1:8080/daemon.DaemonService/Status
```
### Call the API from an Integration
The following Python example calls the status endpoint over a Unix socket using only the standard library:
```python
from http import client
from json import dumps, load
from socket import AF_UNIX, SOCK_STREAM, socket
class UnixHTTPConnection(client.HTTPConnection):
def __init__(self, socket_path):
super().__init__("localhost")
self.socket_path = socket_path
def connect(self):
self.sock = socket(AF_UNIX, SOCK_STREAM)
self.sock.connect(self.socket_path)
connection = UnixHTTPConnection("/var/run/netbird-http.sock")
connection.request(
"POST",
"/daemon.DaemonService/Status",
body=dumps({}),
headers={"Content-Type": "application/json"},
)
response = connection.getresponse()
if response.status != 200:
raise RuntimeError(f"NetBird returned HTTP {response.status}: {response.read().decode()}")
status = load(response)
print(status["status"])
connection.close()
```
For a TCP listener, any standard HTTP client can call the same path and JSON body without Unix-socket support.
## Map gRPC Methods to HTTP
The gateway exposes every method in `daemon.DaemonService`, and every endpoint uses
`POST`. Add the method name to the service path:
```text
daemon.DaemonService/Status
/daemon.DaemonService/Status
```
For example:
| gRPC method | HTTP endpoint |
| -------------- | ------------------------------------ |
| `Status` | `/daemon.DaemonService/Status` |
| `GetConfig` | `/daemon.DaemonService/GetConfig` |
| `ListNetworks` | `/daemon.DaemonService/ListNetworks` |
| `Up` | `/daemon.DaemonService/Up` |
| `Down` | `/daemon.DaemonService/Down` |
The [`DaemonService` protobuf definition](https://github.com/netbirdio/netbird/blob/main/client/proto/daemon.proto)
is the complete API reference for method names and request and response schemas.
See [gRPC Daemon Socket](/client/grpc-socket#service-definition) for an overview of
the service and guidance on using the protobuf definition.
### Server-Streaming Methods
Server-streaming RPCs use the same path. The HTTP request stays open and returns
JSON messages until the stream ends or the caller closes the connection. Check the
protobuf definition for streaming methods and their response types.
The gateway exposes control operations as well as read-only methods. Only give
socket access to processes that you trust to control the local NetBird client.
## Troubleshooting
### The Socket File Does Not Exist
Confirm that the service was installed or reconfigured with `--enable-json-socket`, then check the service status:
```shell
sudo netbird service status
```
If you supplied `--json-socket` without `--enable-json-socket`, NetBird rejects the configuration. Setting a custom address does not enable the gateway on its own.
### Curl Reports Permission Denied
Verify that the process running the integration can access the socket and every parent directory in its path. A custom restricted directory can prevent access even when the socket itself allows it.
### A TCP Request Cannot Connect
Confirm that the host and port in the request match the value passed to `--json-socket`. Prefer `127.0.0.1` over `0.0.0.0` unless remote access is explicitly required and protected by an additional security boundary.

View File

@@ -71,6 +71,7 @@ PascalCase variant in the Group Policy Editor — both are recognized.
| `wireguardPort` | integer | UDP port the local WireGuard interface binds to. Range `165535`. |
| `allowServerSSH` | boolean | Allow the embedded NetBird SSH server on this peer. |
| `disableAutoConnect` | boolean | Skip auto-connecting on startup; require an explicit `netbird up`. |
| `disableAutostart` | boolean | Prevent the GUI from registering itself as an OS autostart entry on fresh installs, and — when enabled at any later point — remove an existing registration on the next GUI launch (Windows Registry `Run` key, macOS Login Item, Linux `.desktop`). Desktop GUIs only; no-op on iOS/Android. Once the admin lifts the policy, the setting stays off until the user re-enables it in Settings. |
| `rosenpassEnabled` | boolean | Turn on the post-quantum Rosenpass key exchange. |
| `rosenpassPermissive` | boolean | Permissive mode for Rosenpass (interop with non-Rosenpass peers). |
| `blockInbound` | boolean | Drop all inbound traffic except established/related — kill-switch style. |
@@ -96,6 +97,11 @@ PascalCase variant in the Group Policy Editor — both are recognized.
`VpnService.Builder.addAllowedApplication()` flow; on Windows and
macOS the daemon parses the keys but ignores them. They are safe to
ship in a cross-platform payload.
- `disableAutostart` only affects the desktop GUI's OS autostart entry
(Windows Registry `Run` key, macOS Login Item, Linux `.desktop`).
iOS and Android have no equivalent user-space autostart mechanism —
the daemon runs as a system service — so the key is parsed and
ignored there. Safe to ship in cross-platform payloads.
- The `disableMetricsCollection` key is reserved for an upcoming
metrics integration; the client recognizes it today but no metrics
pipeline is shipped yet.

View File

@@ -427,6 +427,26 @@ Peers count: 2/3 Connected
The peer with IP `100.119.85.4` wasn't returned because it was not connected
</Note>
#### Health checks
Use `netbird status --check` to run a health probe from a script or container orchestrator. These probes do not display the normal status report. A successful probe produces no output and exits with code `0`; a failed probe exits with code `1`.
- `live`: succeeds when the daemon responds to the status request.
- `ready`: succeeds when the daemon status is `Idle`, `Connecting`, or `Connected`. Authentication-required states (`NeedsLogin`, `LoginFailed`, and `SessionExpired`) and unexpected states fail the check.
- `startup`: succeeds when management and signal are connected and, if any NetBird relays are configured, at least one relay is available.
For example, the following loop makes each probe result visible:
```shell
for check in live ready startup; do
if netbird status --check "$check"; then
echo "$check: PASS"
else
echo "$check: FAIL ($?)"
fi
done
```
### ssh
Command to connect via SSH to a remote peer in your NetBird network. The `ssh` command has several subcommands for different operations.

View File

@@ -87,6 +87,8 @@ volumes:
In case you are activating a server peer, you can use a [setup key](/manage/peers/register-machines-using-setup-keys) as described in the steps below.
> This is especially helpful when you are running multiple server instances with infrastructure-as-code tools like ansible and terraform.
For unattended deployments across many containers, pre-populate the client config and inject the setup key at runtime. See [Bootstrap peers via config file](/manage/peers/bootstrap-via-config-file).
1. Login to the Management Service. You need to have a `setup key` in hand (see [setup keys](/manage/peers/register-machines-using-setup-keys)).
```bash

View File

@@ -329,6 +329,8 @@ Check connection status:
In case you are activating a server peer, you can use a [setup key](/manage/peers/register-machines-using-setup-keys) as described in the steps below.
> This is especially helpful when you are running multiple server instances with infrastructure-as-code tools like ansible and terraform.
For unattended deployments across many machines, pre-populate the client config so each peer registers on first start. See [Bootstrap peers via config file](/manage/peers/bootstrap-via-config-file).
1. Login to the Management Service. You need to have a `setup key` in hand (see [setup keys](/manage/peers/register-machines-using-setup-keys)).
```bash

View File

@@ -138,6 +138,8 @@ Check connection status:
In case you are activating a server peer, you can use a [setup key](/manage/peers/register-machines-using-setup-keys) as described in the steps below.
> This is especially helpful when you are running multiple server instances with infrastructure-as-code tools like ansible and terraform.
For unattended deployments across many machines, pre-populate the client config so each peer registers on first start. See [Bootstrap peers via config file](/manage/peers/bootstrap-via-config-file).
1. Login to the Management Service. You need to have a `setup key` in hand (see [setup keys](/manage/peers/register-machines-using-setup-keys)).
```bash

View File

@@ -145,6 +145,33 @@ By default, pfSense uses automatic outbound NAT which randomizes source ports. T
- Run `netbird service restart` on the device.
- Run `netbird status -d` to verify the connection.
## Updating
To upgrade an existing install, fetch the latest packages and re-run `pkg add -f` over the current ones. There is no need to `pkg delete` first: the `-f` flag upgrades the packages in place.
1. **Download the latest packages**
From the [latest pfSense NetBird release](https://github.com/netbirdio/pfsense-netbird/releases/latest), fetch both `.pkg` files for your architecture, replacing the tag, version, and architecture values with those from the release page (as in [Installation](#installation)):
```sh
fetch https://github.com/netbirdio/pfsense-netbird/releases/download/<RELEASE_TAG>/netbird-<VERSION>-<ARCH>.pkg
fetch https://github.com/netbirdio/pfsense-netbird/releases/download/<RELEASE_TAG>/pfSense-pkg-NetBird-<VERSION>-<ARCH>.pkg
```
2. **Upgrade both packages in place**
Using the filenames you downloaded, run `pkg add -f` on both over the existing install:
```sh
pkg add -f netbird-<VERSION>-<ARCH>.pkg
pkg add -f pfSense-pkg-NetBird-<VERSION>-<ARCH>.pkg
```
3. **Restart and verify**
```sh
netbird service restart
netbird status -d
```
## Uninstallation
From a shell on your pfSense system, run:

View File

@@ -97,6 +97,8 @@ Check connection status:
In case you are activating a server peer, you can use a [setup key](/manage/peers/register-machines-using-setup-keys) as described in the steps below.
> This is especially helpful when you are running multiple server instances with infrastructure-as-code tools like ansible and terraform.
For unattended deployments across many machines, pre-populate the client config so each peer registers on first start. See [Bootstrap peers via config file](/manage/peers/bootstrap-via-config-file).
1. Login to the Management Service. You need to have a `setup key` in hand (see [setup keys](/manage/peers/register-machines-using-setup-keys)).
For all systems:

View File

@@ -483,28 +483,60 @@ Running the NetBird client directly **on** a Domain Controller is a separate cas
**Symptoms**:
- Match-domain names don't resolve on a Windows client, even though `netbird status -d` shows the nameserver as Available and the client log records the NRPT (Name Resolution Policy Table) rule as written.
- The machine is off-domain (not currently on the company network), often a remote or personal device that was once domain-joined.
- Resolving the same host by IP still works; only name resolution for the match domain fails.
- Common on machines that are domain-joined, or were once domain-joined (a remote or personal device that has since gone off-domain).
**Diagnosis**:
NetBird writes its NRPT rule correctly, but a stale Windows Group Policy Object (GPO) `DnsPolicyConfig` container forces the rule into the *policy* store. On an off-domain machine Windows does not apply policy-store NRPT rules, so the rule exists but is never effective. The write succeeds while Windows quietly drops it from resolution.
To steer match-domain queries to its resolver, the NetBird client writes a *local* NRPT rule on Windows. Windows gives **Group Policy** NRPT rules precedence over local ones: they live in the `DnsPolicyConfig` policy store, and while that store is present it overrides the rule NetBird wrote. Two variants cause this:
From a [debug bundle](/help/troubleshooting-client#debug-bundle), `client.log` and the matching entry in `state.json` show whether the NetBird client detected a GPO in place.
- **A GPO defines NRPT rules.** The policy-store rules win over NetBird's local rule, so match-domain queries never reach NetBird's resolver.
- **A lingering empty NRPT list.** NRPT rules created and then deleted under older Windows versions leave an empty list behind in the GPO's `registry.pol`. Applied to a device, that empty policy store still takes precedence and leaves no working rule.
Then compare the rule NetBird wrote against what Windows is actually applying (PowerShell):
1. Confirm NetBird's rule is written but not effective (PowerShell):
```powershell
Get-DnsClientNrptRule # the rule NetBird wrote
Get-DnsClientNrptPolicy -Effective # what Windows is actually applying
```
If the rule appears in `Get-DnsClientNrptRule` but not in the `-Effective` output, a lingering GPO container is blocking it.
If the rule shows in `Get-DnsClientNrptRule` but not in the `-Effective` output, a Group Policy container is overriding it. A [debug bundle](/help/troubleshooting-client#debug-bundle)'s `client.log` and `state.json` also record whether the client detected a GPO.
2. Confirm a GPO is responsible by checking for the policy-store container on the device:
```
HKLM\Software\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig
```
If that key exists, Group Policy NRPT rules are being applied.
3. Find which GPO. On the device, generate a policy report and look for an NRPT section:
```powershell
gpresult /h GPReport.html
```
To locate the source across the domain, scan the `SYSVOL` share for `registry.pol` files that carry NRPT (`DnsPolicyConfig`) entries:
```powershell
# Set this to your domain's SYSVOL Policies path, for example:
# \\dc1.corp.example.com\sysvol\corp.example.com\Policies
$sysvolPath = "\\<DC FQDN>\sysvol\<domain FQDN>\Policies"
$matches = Get-ChildItem -Path $sysvolPath -Recurse -Filter "registry.pol" -File |
Where-Object { (Get-Content -Path $_.FullName -Encoding Unicode) -like "*dnspolicyconfig*" }
if ($matches) { "GPOs with NRPT rules:"; $matches.FullName }
else { "No GPOs with NRPT rules found." }
```
**Solutions**:
This is a Windows Group Policy state issue, not a NetBird misconfiguration, and the fix has to happen locally:
- Have local IT clear the stale `DnsPolicyConfig` GPO container from the machine's registry, or
- Connect the machine to the company network and run `gpupdate /force` so Windows reconciles and removes the lingering container.
This is a Windows Group Policy state issue, not a NetBird misconfiguration, so the fix is on the Windows side. Pick by situation:
- **Device-local (off-domain machines):** have local IT clear the stale `DnsPolicyConfig` container from the device's registry, or reconnect the machine to the company network and run `gpupdate /force` so Windows reconciles and drops the lingering container.
- **At the source, empty lingering list:** edit the offending GPO's NRPT section. If it's empty, add a throwaway rule, update the policy, delete that rule, and update again. This removes the leftover `DnsPolicyConfig` from `registry.pol` that an older Windows version created.
- **At the source, real rules:** if the section actually contains rules, confirm they're still needed and delete them if not. If they are needed, note that applying that GPO to a device running the NetBird client will override NetBird's rule and break match-domain resolution. The two have to be planned around each other.
NetBird cannot remove a Group Policy container from the client side.

View File

@@ -12,6 +12,8 @@ This unlocks automated, unattended deployments and integrates cleanly with infra
netbird up --setup-key <SETUP KEY>
```
You can also pre-populate the client configuration ahead of first start, so peers come up fully configured and register with no manual steps. See [Bootstrap peers via config file](/manage/peers/bootstrap-via-config-file).
## Types of Setup Keys
There are two types of setup keys:

View File

@@ -286,6 +286,8 @@ Switch to the **Access Control** tab to restrict access by IP address, country,
- Add **allowed countries** or **blocked countries** to restrict by geographic location.
- Set **CrowdSec IP Reputation** to **enforce** or **observe** to block or monitor known malicious IPs (when available on the proxy cluster).
By default, restrictions of different types are combined with a logical **AND**: a connection must satisfy all of them (for example, come from an allowed CIDR *and* an allowed country) to be allowed through.
Access restrictions are evaluated before authentication: if a connection is blocked by an access restriction rule, it is rejected before any authentication check.
<Note>

View File

@@ -38,7 +38,7 @@ You can use JumpCloud as your Identity Provider with NetBird, but it will requir
9. Record the Client ID and Client Secret that JumpCloud generates for your application.
10. Share your Client ID, and Client Secret with our team. Please use a secure method for sharing this information.
10. Share your Client ID and Client Secret with our team, along with the region your JumpCloud console is in (US, EU, or India). Please use a secure method for sharing this information.
<Note>
We recommend using a secure channel to share the Clients secret. You can send a separate email and use a secret sharing service like: <br/>

View File

@@ -0,0 +1,84 @@
import { Note } from '@/components/mdx'
export const description = 'What the NetBird Enterprise Commercial License adds to a self-hosted deployment, and answers to common questions about high availability, upgrades, scale, multi-tenancy, and evaluation.'
# NetBird Enterprise Commercial License
If you are weighing whether to replace your VPN with a self-hosted NetBird control plane, the first question is usually what the commercial license buys you over the free open source version, and whether a self-hosted deployment can meet real production requirements: surviving upgrades without downtime, scaling as you add peers, and serving more than one set of users.
This page explains the **NetBird Enterprise Commercial License** and answers the questions we hear most often from teams evaluating self-hosted NetBird for production. It runs entirely on your own infrastructure.
Throughout this page, **control plane** means the server-side services you self-host: **Management**, **Signal**, and **Relay**. It does not mean your traffic. NetBird connections run directly between peers, or through Relay, and do not pass through Management.
<Note>
**"Business plan" is a NetBird Cloud term, not a self-hosted one.** Team and Business are subscription tiers of [NetBird Cloud](https://netbird.io/pricing) (the SaaS we host for you). When you self-host on your own infrastructure, the paid features come from the **Enterprise Commercial License** instead, available on the [on-prem pricing page](https://netbird.io/pricing#on-prem). High availability, SCIM, and traffic-flow logging on a self-hosted deployment are unlocked by the commercial license, not by a "Business plan."
</Note>
## How the license works on your own cloud
The commercial license is designed for running NetBird on infrastructure you control: your own cloud account, data center, or private environment. Nothing phones home for your network traffic.
With the license, NetBird issues you three things: an install script, a GitHub Container Registry (GHCR) token to pull the enterprise container images (the `-cloud` image variants), and a license key. You set the key as `NB_LICENSE_KEY` in the deployment's `.env` file; on startup the server validates it and unlocks the licensed features.
To obtain a license, contact the NetBird team through the [on-prem pricing page](https://netbird.io/pricing#on-prem).
## What the license unlocks
The core connectivity, access control, and routing features are the same in the open source Community Edition and the commercial build. The commercial license adds the capabilities most teams need in production:
| Capability | Community Edition (open source) | Enterprise Commercial License |
| --- | --- | --- |
| Core networking, access control, and routing | Yes | Yes |
| Active-active high availability (Management + Signal) | No | Yes |
| SCIM user and group provisioning | No | Yes |
| EDR and MDM integrations (CrowdStrike, SentinelOne, Intune, and others) | No | Yes |
| Traffic-flow event logging and streaming | No | Yes |
| Support | Community (Slack, GitHub) | Standard support included |
## Frequently asked questions
### Can we move from the open source Community Edition without starting over?
Yes. A script migrates your existing open source deployment to the commercial license in place, keeping your account, peers, and configuration intact, so you don't rebuild your network. NetBird provides it during your proof of concept and when you roll out to production.
### Can we upgrade the control plane with zero downtime?
Yes, with the commercial license. It helps to be precise about what "downtime" means here, because control-plane downtime is not the same as network downtime.
**Single-server deployment (no HA).** Upgrading restarts the server for the duration of the update. **Direct peer-to-peer connections keep working throughout**, because they do not pass through your servers. **Relayed sessions depend on your topology.** In the default combined deployment, the `netbird-server` container bundles Management, Signal, and Relay together, so recreating it restarts Relay and active relayed sessions reconnect. If you run Relay as a separate external service, it stays up during a Management restart and relayed sessions continue as well. While the server is down you cannot change configuration, apply new policies, or onboard new peers. For many teams a short maintenance window is acceptable.
**High-availability deployment (commercial license).** Management, Signal, and Relay each run as a load-balanced pool of two or more instances, backed by shared PostgreSQL, Redis, and NATS. You upgrade by draining one instance, upgrading it, returning it to the load balancer, and repeating. This is a rolling upgrade with **no control-plane downtime** at any point. Schema migrations run once, automatically, on the first upgraded Management instance.
### Does the control plane slow down as we add peers, policies, and users?
Bulk changes (adding many peers, policies, or users at once) are handled by Management and do **not** affect the throughput of your network traffic, which stays peer-to-peer. There is no known control-plane bottleneck at the scales teams typically run.
When you do need more headroom, there are two paths:
- **Scale vertically.** Run the control plane on a larger host. This is the simplest option and is available without high availability.
- **Scale horizontally.** With the commercial license, run Management as a load-balanced pool backed by **PostgreSQL** (not the default SQLite store), which also gives you high availability. This is the right choice for large fleets.
For sizing a large deployment, contact the NetBird team.
### Can we put multiple customers under one deployment, with tenant separation?
No. This is the point where self-hosting has a hard boundary worth stating plainly. A self-hosted NetBird deployment, open source or commercial, is a **single account**. The commercial license does not add multi-tenancy. Within that one account you can segment users and resources with groups, policies, and networks, but that is segmentation, not isolated tenants. Everyone shares the same account.
To serve multiple isolated customers you have two options:
- **NetBird Cloud MSP Portal.** Purpose-built for managing many customer tenants from one place, with per-tenant configuration and billing. This is a [Cloud feature](/manage/for-partners/msp-portal) and is not available in a self-hosted deployment.
- **A separate self-hosted control plane per customer.** You run one independent stack per customer. Commercial licenses are issued per legal entity, so each customer's stack needs its own license. Talk to the NetBird team about licensing for multiple deployments.
### How does evaluation work, and for how long?
You can start evaluating today for free: the **open source Community Edition is free to self-host for as long as you like**, with no license and no time limit. That is the right way to try core NetBird (the control plane, peers, policies, and routing) while you decide whether it fits.
To evaluate the commercial-only features (active-active HA, SCIM, traffic-flow logging), contact the sales team through the [on-prem pricing page](https://netbird.io/pricing#on-prem) to run an assisted proof of concept with the commercial license. You install and run the stack; NetBird provides the license and guidance. A commercial proof of concept runs for 30 days by default.
## Summary
- The **Enterprise Commercial License** is the paid tier for self-hosting on your own infrastructure (the equivalent of Cloud's Business plan, but for the stack you run).
- It unlocks **active-active high availability** for Management and Signal, so you can do **zero-downtime rolling upgrades** of the control plane; a single-server deployment survives an upgrade too, just without accepting changes during the restart.
- The control plane has **no known scaling bottleneck**; grow it **vertically**, or **horizontally with PostgreSQL** under the commercial license.
- A self-hosted deployment is **single-tenant**; serve multiple isolated customers with the **Cloud MSP Portal** or a **separate licensed stack per customer**.
- Evaluate **core NetBird free** with the open source edition; for the commercial features, ask the team for an **assisted proof of concept** (30 days by default).

View File

@@ -27,6 +27,10 @@ MFA enforcement applies only to local users authenticated through the embedded I
Once enabled, all local users will be prompted to set up TOTP on their next login.
<Note>
Server administrators can also manage local MFA from the command line with `admin mfa status`, `admin mfa enable`, and `admin mfa disable`. See the [self-hosted admin CLI](/selfhosted/maintenance/admin-cli#manage-local-mfa) reference.
</Note>
## User experience
### First login after MFA is enabled

View File

@@ -233,6 +233,10 @@ This feature is available when:
Users authenticated through SSO/OIDC providers will not see this option.
<Note>
If a local user cannot sign in, a server administrator can reset the password from the server with `admin user change-password`. See the [self-hosted admin CLI](/selfhosted/maintenance/admin-cli#change-a-local-users-password) reference.
</Note>
How to Use:
1. Click your avatar in the top-right corner of the dashboard

View File

@@ -0,0 +1,213 @@
import {Note, Warning} from "@/components/mdx";
export const description =
"Use the NetBird self-hosted admin CLI to manage local users, local MFA, proxy access tokens, and reverse proxy state.";
# Self-hosted admin CLI
The self-hosted admin CLI provides maintenance commands that run directly against the NetBird Management data store and, for local user operations, the embedded IdP store. Use it when you need to repair or administer a self-hosted control plane from the server rather than through the Dashboard or public API.
You can use the admin CLI to:
- Change passwords for local embedded IdP users
- Reset a local user's MFA enrollment
- Enable, disable, or check local MFA
- Create, list, and revoke reverse proxy access tokens
- Force-mark reverse proxy instances as disconnected after an unclean shutdown
<Warning>
Back up your NetBird configuration and data stores before running commands that change state. Run the command from the same NetBird version as the server. After an upgrade, start the server once before using the admin CLI so database migrations and embedded IdP clients are initialized.
</Warning>
## Prerequisites
- Access to the host or container that runs the Management service
- The Management configuration file path
- For local user and MFA commands, a self-hosted deployment using the [embedded IdP](/selfhosted/identity-providers/local)
- For local MFA commands, a single-account embedded IdP deployment
<Note>
The admin CLI is for self-hosted deployments. It does not manage users in external identity providers such as Google Workspace, Microsoft Entra ID, Okta, or Keycloak.
</Note>
## Choose the command prefix
Use the prefix that matches your deployment type, then append one of the admin subcommands shown below.
### Combined container
New installations created with `getting-started.sh` use the combined `netbird-server` container and `config.yaml`.
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin <command>
```
If you run the binary directly on the host, use the host path to `config.yaml` instead:
```bash
netbird-server --config ./config.yaml admin <command>
```
### Older multi-container setup
Older deployments use a separate `netbird-mgmt` binary and `management.json`.
```bash
docker exec -it netbird-management \
/go/bin/netbird-mgmt admin --config /etc/netbird/management.json <command>
```
If your SQLite `store.db` or default `idp.db` is in a non-default data directory, add `--datadir`:
```bash
docker exec -it netbird-management \
/go/bin/netbird-mgmt admin --config /etc/netbird/management.json --datadir /var/lib/netbird <command>
```
## Change a local user's password
Use `admin user change-password` with either `--email` or `--user-id`.
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin user change-password \
--email user@example.com \
--password-file /run/secrets/new-netbird-password
```
To avoid storing the password in shell history, read it from standard input:
```bash
printf '%s\n' 'NewPass1!' | docker exec -i netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin user change-password \
--email user@example.com \
--password-file -
```
The new password must be at least eight characters and include at least one digit, one uppercase letter, and one special character.
<Note>
The command also removes the user's active local authentication session, so the user must sign in again with the new password.
</Note>
## Reset a local user's MFA enrollment
Use `admin user reset-mfa` when a local user loses access to their authenticator app or needs to enroll again.
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin user reset-mfa \
--email user@example.com
```
You can also select the user by ID:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin user reset-mfa \
--user-id <user-id>
```
The command clears the user's stored TOTP and WebAuthn enrollment for the local connector and removes the active local authentication session. If local MFA remains enabled, the user is prompted to enroll again at the next login.
## Manage local MFA
Local MFA applies to all users who authenticate with the embedded IdP local username/password connector. It does not affect users who sign in through external IdPs.
Check the current state:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin mfa status
```
Enable local MFA:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin mfa enable
```
Disable local MFA:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin mfa disable
```
<Warning>
Disabling local MFA removes the login requirement but does not delete users' stored MFA enrollments. If you enable MFA again, previously enrolled users are asked for the same authenticator unless you reset their enrollment with `admin user reset-mfa`.
</Warning>
## Manage proxy access tokens
Reverse proxy instances authenticate to Management with proxy access tokens. The plain token is shown only once when created.
Create a token:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token create \
--name "proxy-server-1" \
--expires-in 365d
```
`--expires-in` accepts Go duration values such as `24h` or `1h30m`, plus day values such as `30d` or `365d`. Leave it empty for no expiration.
List tokens:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token list
```
Revoke a token by ID:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token revoke <token-id>
```
<Note>
Older `netbird-server token ...` and `netbird-mgmt token ...` commands are deprecated. Use `admin token ...` for new automation.
</Note>
## Repair reverse proxy connection state
Use `admin proxy disconnect-all` to force-mark every registered reverse proxy instance as disconnected. This is useful after an unclean Management server shutdown when stale proxy sessions remain stored as connected.
Preview the change first:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin proxy disconnect-all --dry-run
```
Apply the repair with an interactive confirmation prompt:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin proxy disconnect-all
```
For non-interactive maintenance automation, use `--force`:
```bash
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin proxy disconnect-all --force
```
<Warning>
Run this command during a maintenance window. It changes stored reverse proxy state for every non-disconnected proxy instance. Live proxies may stay hidden until their next heartbeat or reconnect.
</Warning>
## Troubleshooting
| Error or warning | What to do |
|------------------|------------|
| `admin commands require the embedded IdP to be enabled` | Local user and MFA commands only work with the embedded IdP. Manage external IdP users in that provider. |
| `no local users exist in the embedded IdP storage` | Check that the server has started at least once and that `--config` or `--datadir` points to the correct data directory. |
| `embedded IdP client not found` | Start the Management server once with this configuration, then run the command again. |
| `expected exactly one account` | The local MFA CLI supports only single-account embedded IdP deployments. |
| `Warning: audit events will not be recorded` | The command can still run, but the activity event store could not be opened. Check the data store encryption key and activity store configuration. |

View File

@@ -93,36 +93,40 @@ Generate a token for each instance. The command differs depending on whether you
**Combined container** (`netbirdio/netbird-server`):
```bash
docker exec -it netbird-server /go/bin/netbird-server token create \
--name "proxy-server-1" --config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token create \
--name "proxy-server-1"
docker exec -it netbird-server /go/bin/netbird-server token create \
--name "proxy-server-2" --config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token create \
--name "proxy-server-2"
```
**Multi-container** (separate `netbirdio/management` image):
```bash
docker exec -it netbird-management /go/bin/netbird-mgmt token create --name "proxy-server-1"
docker exec -it netbird-management /go/bin/netbird-mgmt token create --name "proxy-server-2"
docker exec -it netbird-management \
/go/bin/netbird-mgmt admin token create --name "proxy-server-1"
docker exec -it netbird-management \
/go/bin/netbird-mgmt admin token create --name "proxy-server-2"
```
Use a descriptive `--name` for each token so you can identify which instance it belongs to when listing or revoking tokens:
```bash
# List all tokens (combined container)
docker exec -it netbird-server /go/bin/netbird-server token list \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token list
# List all tokens (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token list
docker exec -it netbird-management /go/bin/netbird-mgmt admin token list
# Revoke a specific instance's token (combined container)
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token revoke <token-id>
# Revoke a specific instance's token (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>
docker exec -it netbird-management /go/bin/netbird-mgmt admin token revoke <token-id>
```
<Note>
@@ -321,14 +325,14 @@ To gracefully remove a proxy instance from the cluster:
docker compose down
# Revoke its token on the management server (combined container)
docker exec -it netbird-server /go/bin/netbird-server token list \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token list
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token revoke <token-id>
# Revoke its token on the management server (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token list
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>
docker exec -it netbird-management /go/bin/netbird-mgmt admin token list
docker exec -it netbird-management /go/bin/netbird-mgmt admin token revoke <token-id>
```
After stopping the instance, update your DNS records to remove the server's IP address so clients are no longer directed to it.

View File

@@ -70,14 +70,16 @@ The proxy authenticates with the management server using an access token. Genera
**Combined container** (`netbirdio/netbird-server`):
```bash
docker exec -it netbird-server /go/bin/netbird-server token create \
--name "my-proxy" --config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token create \
--name "my-proxy"
```
**Multi-container** (separate `netbirdio/management` image):
```bash
docker exec -it netbird-management /go/bin/netbird-mgmt token create --name "my-proxy"
docker exec -it netbird-management \
/go/bin/netbird-mgmt admin token create --name "my-proxy"
```
This outputs a token in the format `nbx_...` (40 characters). **Save the token immediately** - it is only displayed once. The management server stores only a SHA-256 hash.
@@ -86,18 +88,18 @@ You can manage tokens later with:
```bash
# List all tokens (combined container)
docker exec -it netbird-server /go/bin/netbird-server token list \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token list
# List all tokens (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token list
docker exec -it netbird-management /go/bin/netbird-mgmt admin token list
# Revoke a token by ID (combined container)
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token revoke <token-id>
# Revoke a token by ID (multi-container)
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>
docker exec -it netbird-management /go/bin/netbird-mgmt admin token revoke <token-id>
```
### Step 3: Add the proxy service to docker-compose.yml
@@ -578,7 +580,7 @@ The proxy is configured entirely through environment variables (each one maps to
| Variable | Required | Description | Default |
|----------|----------|-------------|---------|
| `NB_PROXY_TOKEN` | Yes | Access token generated via `netbird-server token create` (combined) or `netbird-mgmt token create` (multi-container). The proxy refuses to start without it. | - |
| `NB_PROXY_TOKEN` | Yes | Access token generated via `netbird-server admin token create` (combined) or `netbird-mgmt admin token create` (multi-container). The proxy refuses to start without it. | - |
| `NB_PROXY_DOMAIN` | Yes | Base domain for this proxy instance (e.g., `proxy.example.com` or `netbird.example.com`). Determines the domain available for services. | - |
| `NB_PROXY_MANAGEMENT_ADDRESS` | No | URL of your NetBird management server. The proxy connects via gRPC to register itself. | `https://api.netbird.io:443` |
| `NB_PROXY_ADDRESS` | No | Address the proxy listens on. | `:8443` (Docker), `:443` (binary) |
@@ -709,14 +711,14 @@ You can also revoke the proxy token to prevent the proxy from reconnecting:
```bash
# Combined container
docker exec -it netbird-server /go/bin/netbird-server token list \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
--config <netbird-data-dir>/config.yaml
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token list
docker exec -it netbird-server \
/go/bin/netbird-server --config /etc/netbird/config.yaml admin token revoke <token-id>
# Multi-container
docker exec -it netbird-management /go/bin/netbird-mgmt token list
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>
docker exec -it netbird-management /go/bin/netbird-mgmt admin token list
docker exec -it netbird-management /go/bin/netbird-mgmt admin token revoke <token-id>
```
## Additional resources

View File

@@ -70,6 +70,8 @@ Click on Name & Description to give your policy a name and description. Then cli
### Step 4: Deploy the NetBird agent
You can deploy the NetBird agent using a daemon set or a deployment. Below is an example of a deployment configuration with 1 replica.
The example below enrolls the agent with a setup key. To also pre-populate the client config so it starts fully configured, see [Bootstrap peers via config file](/manage/peers/bootstrap-via-config-file).
```yaml
---
apiVersion: apps/v1

View File

@@ -99,6 +99,23 @@ Add a DNS server with the match domain set to `ALL`. Local DNS servers may not b
See [Manage DNS in your network](/manage/dns) for details.
## Performance Expectations
An exit node carries each device's entire internet traffic through a single WireGuard tunnel, and a single tunnel is processed largely on one CPU core of the exit node. This caps each device's throughput at single-tunnel speed: typically a few Gbps on a modern server CPU running Linux kernel WireGuard. The exact figure depends on the exit node's per-core speed, the tunnel MTU, and traffic direction (see the [benchmark assumptions](/manage/networks/sizing-routing-peers#per-peer-capacity-reference)), but it does not grow with parallel streams.
Because this is a fixed cap rather than a percentage cost, what you observe depends on your internet line rate:
- On links up to about 1 Gbps, an exit node typically adds little to no throughput loss.
- On multi-gigabit links, full-tunnel throughput tops out at the tunnel cap, so a large relative drop against a 5 or 10 Gbps line is expected behavior, not a misconfiguration.
A common mistake is scaling the wrong dimension: adding CPU cores to the exit node or running more parallel streams on a device does not raise that device's ceiling, because the tunnel still serializes on one core. Per-core CPU speed does raise it, so for fast individual clients pick the exit node hardware with the fastest single-core performance available. More cores raise the aggregate capacity across many devices; see [Sizing Routing Peers](/manage/networks/sizing-routing-peers#per-peer-capacity-reference) for measured per-size numbers.
If throughput lands well below a few Gbps, check these before resizing hardware:
- **Relayed connection.** Run `netbird status -d` on the device and confirm the connection to the exit node shows type P2P. Relayed traffic is significantly slower than a direct connection.
- **Userspace WireGuard.** Devices on Windows and macOS, and peers running in [userspace mode](/manage/networks/sizing-routing-peers#userspace-mode), reach well below kernel-mode numbers.
- **Exit node limits.** If the connection is P2P and both ends run kernel WireGuard, check the exit node's CPU load, the tunnel MTU, and the traffic direction against [Tuning for more throughput](/manage/networks/sizing-routing-peers#tuning-for-more-throughput).
## IPv6 Support
<Note>

View File

@@ -808,7 +808,6 @@ What to do:
- Check the policy and its posture checks in **Access Control → Policies**.
- Confirm the peer's group membership in the dashboard under **Peers**.
- Look at Traffic Events for explicit "blocked due to posture" or "no matching policy" entries.
Useful commands on the peer: