diff --git a/public/docs-static/files/io.netbird.client.plist b/public/docs-static/files/io.netbird.client.plist
new file mode 100644
index 000000000..8c2313977
--- /dev/null
+++ b/public/docs-static/files/io.netbird.client.plist
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+ managementURL
+ https://api.netbird.io:443
+
+
+
+
+
+
+ allowServerSSH
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/docs-static/files/netbird-macos.mobileconfig b/public/docs-static/files/netbird-macos.mobileconfig
new file mode 100644
index 000000000..f1fcd3c34
--- /dev/null
+++ b/public/docs-static/files/netbird-macos.mobileconfig
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+ PayloadType
+ Configuration
+ PayloadVersion
+ 1
+ PayloadIdentifier
+ io.netbird.client.mdm
+ PayloadUUID
+ 11111111-1111-1111-1111-111111111111
+ PayloadDisplayName
+ NetBird MDM Policy
+ PayloadDescription
+ Enforces NetBird client configuration. Values written here override any local user / CLI / on-disk setting and are re-applied at every daemon boot and on every 1-minute MDM reload tick.
+ PayloadOrganization
+ NetBird
+ PayloadScope
+ System
+ PayloadRemovalDisallowed
+
+
+ PayloadContent
+
+
+
+ PayloadType
+ com.apple.ManagedClient.preferences
+ PayloadVersion
+ 1
+ PayloadIdentifier
+ io.netbird.client.mdm.preferences
+ PayloadUUID
+ 22222222-2222-2222-2222-222222222222
+ PayloadDisplayName
+ NetBird Managed Preferences
+ PayloadEnabled
+
+
+ PayloadContent
+
+ io.netbird.client
+
+ Forced
+
+
+ mcx_preference_settings
+
+
+
+ managementURL
+ https://api.netbird.io:443
+
+
+
+
+
+
+ allowServerSSH
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/docs-static/files/netbird-macos.sh b/public/docs-static/files/netbird-macos.sh
new file mode 100644
index 000000000..48933ef1b
--- /dev/null
+++ b/public/docs-static/files/netbird-macos.sh
@@ -0,0 +1,189 @@
+#!/bin/bash
+#
+# SYNOPSIS
+# Push the NetBird MDM policy to a macOS device via JumpCloud Commands.
+#
+# DESCRIPTION
+# This is the macOS counterpart of netbird-policy.reg.ps1.
+# It writes the values declared in the "POLICY VALUES" block below to
+# the managed-preferences plist that the NetBird daemon's
+# client/mdm/policy_darwin.go loader reads on every 1-minute MDM
+# reload tick:
+#
+# /Library/Managed Preferences/io.netbird.client.plist
+#
+# Once the plist lands, the daemon picks up the new values without
+# restart (the ticker calls Config.apply() → applyMDMPolicy() and
+# restarts the engine on diff).
+#
+# DEPLOYMENT (JumpCloud)
+# 1. Admin Console -> Device Management -> Commands -> +.
+# 2. Type: Mac, Shell, Run as: root.
+# 3. Paste this file verbatim into the command body.
+# 4. Bind to the target system group, save, run.
+#
+# IMPORTANT: PERSISTENCE
+# macOS wipes /Library/Managed Preferences/ at every boot on devices
+# that are NOT MDM-enrolled. For a persistent fleet rollout, push the
+# companion netbird-macos.mobileconfig as a Custom Configuration
+# Profile (Admin Console -> MDM -> Mac Custom Configuration Profiles)
+# instead of this script. Use this script when:
+# - the device is MDM-enrolled (file survives reboots), or
+# - you need a one-shot test push before reboot, or
+# - you orchestrate via JumpCloud Commands and want the same
+# variable-driven workflow as the Windows .ps1 sibling.
+#
+# IDEMPOTENCY: re-running with the same values is a no-op from the
+# daemon's point of view (the 1-minute reload ticker diff returns empty).
+#
+# SECURITY: PreSharedKey is redacted in this script's log output.
+
+set -euo pipefail
+
+### POLICY VALUES — EDIT THIS BLOCK ###########################################
+#
+# Set each variable below to the desired value. Set to empty string ""
+# or to NULL to omit a key entirely (the daemon treats an absent key
+# as "no enforcement" for that field). Booleans use "true"/"false"
+# (lowercase). Integers as decimal.
+#
+# Reference for key names + accepted values:
+# client/mdm/policy.go (Key* constants)
+# netbird-macos.mobileconfig (sample profile)
+# netbird.admx + netbird.adml (Windows ADMX schema)
+#
+NULL='__UNSET__'
+managementURL='https://api.netbird.io:443'
+preSharedKey="$NULL" # secret; redacted in log
+allowServerSSH='true'
+blockInbound="$NULL"
+disableAutoConnect="$NULL"
+disableClientRoutes="$NULL"
+disableServerRoutes="$NULL"
+disableMetricsCollection="$NULL"
+disableUpdateSettings="$NULL"
+disableProfiles="$NULL"
+disableNetworks="$NULL"
+rosenpassEnabled="$NULL"
+rosenpassPermissive="$NULL"
+wireguardPort='51820'
+splitTunnelMode="$NULL" # "allow" or "disallow", Android-only at the daemon level
+splitTunnelApps="$NULL" # comma-separated app IDs, Android-only
+##############################################################################
+
+readonly PLIST_DIR='/Library/Managed Preferences'
+readonly PLIST_PATH="$PLIST_DIR/io.netbird.client.plist"
+readonly LOG_TAG='netbird-mdm'
+
+# log sends a message to the system logger using the configured tag and echoes the message to stdout prefixed by an ISO 8601 UTC timestamp and the tag.
+log() {
+ /usr/bin/logger -t "$LOG_TAG" "$*"
+ printf '%s [%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$LOG_TAG" "$*"
+}
+
+# is_set returns success if the provided value is non-empty and is not equal to the special NULL marker.
+is_set() {
+ local value="$1"
+ [[ -n "$value" && "$value" != "$NULL" ]]
+}
+
+# start_plist creates the temporary plist file at "$PLIST_PATH.tmp" containing the XML plist header and opening `` for the policy plist.
+start_plist() {
+ cat > "$PLIST_PATH.tmp" <<'EOF'
+
+
+
+
+EOF
+}
+
+# end_plist appends the closing `` and `` tags to the temporary plist file.
+end_plist() {
+ cat >> "$PLIST_PATH.tmp" <<'EOF'
+
+
+EOF
+}
+
+# emit_string appends a plist ``/`` entry for the given key and value to "$PLIST_PATH.tmp", XML-escaping `&`, `<`, and `>`, and logs the assignment (masking the logged value as `********** (secret)` when the key is `preSharedKey`).
+emit_string() {
+ local key="$1" value="$2" log_value="$2"
+ # Escape XML entities in the value
+ local escaped
+ escaped="$(printf '%s' "$value" | sed -e 's/&/\&/g' -e 's/\</g' -e 's/>/\>/g')"
+ printf ' %s\n %s\n' "$key" "$escaped" >> "$PLIST_PATH.tmp"
+ if [[ "$key" == "preSharedKey" ]]; then
+ log_value='********** (secret)'
+ fi
+ log "set $key = $log_value"
+}
+
+# emit_bool writes a boolean plist entry for a given key into the temporary plist file.
+# emit_bool writes a boolean plist entry for a key when the provided value matches an accepted boolean token; logs an error and skips the key on invalid input.
+emit_bool() {
+ local key="$1" value="$2"
+ local xml_bool
+ case "$value" in
+ true|True|TRUE|1|yes) xml_bool='' ; value='true' ;;
+ false|False|FALSE|0|no) xml_bool='' ; value='false' ;;
+ *) log "invalid boolean for $key: $value (must be true/false); skipping"; return ;;
+ esac
+ printf ' %s\n %s\n' "$key" "$xml_bool" >> "$PLIST_PATH.tmp"
+ log "set $key = $value"
+}
+
+# emit_int validates that VALUE contains only decimal digits and, if valid, appends an `` plist entry for KEY to the temporary plist (`$PLIST_PATH.tmp`) and logs the assignment; on invalid input it logs a skip and does not emit the key.
+emit_int() {
+ local key="$1" value="$2"
+ if ! [[ "$value" =~ ^[0-9]+$ ]]; then
+ log "invalid integer for $key: $value (must be decimal); skipping"
+ return
+ fi
+ printf ' %s\n %s\n' "$key" "$value" >> "$PLIST_PATH.tmp"
+ log "set $key = $value"
+}
+
+# main builds the NetBird MDM plist from configured policy variables, validates and installs it to /Library/Managed Preferences/io.netbird.client.plist (root:wheel, 644) and optionally triggers the NetBird daemon to reload.
+main() {
+ log "applying NetBird MDM policy to $PLIST_PATH"
+ /bin/mkdir -p "$PLIST_DIR"
+ start_plist
+
+ is_set "$managementURL" && emit_string managementURL "$managementURL"
+ is_set "$preSharedKey" && emit_string preSharedKey "$preSharedKey"
+ is_set "$allowServerSSH" && emit_bool allowServerSSH "$allowServerSSH"
+ is_set "$blockInbound" && emit_bool blockInbound "$blockInbound"
+ is_set "$disableAutoConnect" && emit_bool disableAutoConnect "$disableAutoConnect"
+ is_set "$disableClientRoutes" && emit_bool disableClientRoutes "$disableClientRoutes"
+ is_set "$disableServerRoutes" && emit_bool disableServerRoutes "$disableServerRoutes"
+ is_set "$disableMetricsCollection" && emit_bool disableMetricsCollection "$disableMetricsCollection"
+ is_set "$disableUpdateSettings" && emit_bool disableUpdateSettings "$disableUpdateSettings"
+ is_set "$disableProfiles" && emit_bool disableProfiles "$disableProfiles"
+ is_set "$disableNetworks" && emit_bool disableNetworks "$disableNetworks"
+ is_set "$rosenpassEnabled" && emit_bool rosenpassEnabled "$rosenpassEnabled"
+ is_set "$rosenpassPermissive" && emit_bool rosenpassPermissive "$rosenpassPermissive"
+ is_set "$wireguardPort" && emit_int wireguardPort "$wireguardPort"
+ is_set "$splitTunnelMode" && emit_string splitTunnelMode "$splitTunnelMode"
+ is_set "$splitTunnelApps" && emit_string splitTunnelApps "$splitTunnelApps"
+
+ end_plist
+
+ if ! /usr/bin/plutil -lint "$PLIST_PATH.tmp" >/dev/null 2>&1; then
+ log "ERROR: generated plist failed plutil lint; not installing"
+ /usr/bin/plutil -lint "$PLIST_PATH.tmp" >&2 || true
+ /bin/rm -f "$PLIST_PATH.tmp"
+ exit 1
+ fi
+
+ /bin/mv -f "$PLIST_PATH.tmp" "$PLIST_PATH"
+ /usr/sbin/chown root:wheel "$PLIST_PATH"
+ /bin/chmod 644 "$PLIST_PATH"
+
+ log "policy installed; NetBird daemon will pick it up within the next 1-minute reload tick"
+
+ # Optional: kick the daemon for an immediate apply. Safe — does
+ # nothing on a host where NetBird is not yet installed.
+ /bin/launchctl kickstart -k system/io.netbird.client 2>/dev/null || true
+}
+
+main "$@"
diff --git a/public/docs-static/files/netbird-policy.reg b/public/docs-static/files/netbird-policy.reg
new file mode 100644
index 000000000..ba4402e50
Binary files /dev/null and b/public/docs-static/files/netbird-policy.reg differ
diff --git a/public/docs-static/files/netbird-policy.reg.ps1 b/public/docs-static/files/netbird-policy.reg.ps1
new file mode 100644
index 000000000..1d6d68260
--- /dev/null
+++ b/public/docs-static/files/netbird-policy.reg.ps1
@@ -0,0 +1,94 @@
+#requires -Version 5.1
+<#
+.SYNOPSIS
+ Push the NetBird MDM policy to a Windows device via JumpCloud Commands
+ by importing a sidecar netbird-policy.reg file.
+
+.DESCRIPTION
+ Windows counterpart of netbird-macos.sh. Outcome:
+ HKLM\Software\Policies\NetBird populated from the attached
+ netbird-policy.reg file, daemon picks up the change via the
+ 1-minute MDM reload ticker.
+
+ Deployment:
+ 1. Admin Console -> Device Management -> Commands -> +.
+ 2. Type: Windows PowerShell. Run as: SYSTEM.
+ 3. Paste this file verbatim into the command body.
+ 4. In the same command, attach `netbird-policy.reg` as a file.
+ JumpCloud copies attached files into the command's working
+ directory before invoking the script, so `$PSScriptRoot` or
+ Get-Location resolves to where the .reg lives.
+ 5. Bind to the target system group, save, run.
+
+ Producing the .reg file:
+ On a reference machine, after configuring the policy values either
+ via gpedit (GPO) or manual `reg add`, export with:
+
+ reg export "HKLM\Software\Policies\NetBird" netbird-policy.reg /y
+
+ Then attach the resulting file to the JumpCloud command.
+
+ Semantics:
+ - The script nukes the existing HKLM\Software\Policies\NetBird key
+ before importing the .reg, so the .reg is the SINGLE SOURCE OF
+ TRUTH. Any value present in the registry but absent from the .reg
+ is removed. This is what an MDM admin almost always wants.
+ - Setting the .reg to an empty (header-only) file effectively unsets
+ the policy.
+
+ Idempotency: re-running the script with the same .reg is a no-op from
+ the daemon's perspective (values identical → 1-min ticker sees no
+ diff → engine not restarted).
+
+ Exit codes: 0 = success; 1 = .reg missing or reg.exe error.
+#>
+
+$ErrorActionPreference = "Stop"
+
+$RegFileName = "netbird-policy.reg"
+$RegKey = "HKLM\Software\Policies\NetBird"
+
+# Resolve the attached .reg file: JumpCloud copies command attachments
+# into C:\Windows\Temp\ before invoking the script. Cwd / $PSScriptRoot
+# fallbacks cover the local-dev case where you might dot-source this
+# from elsewhere.
+$candidates = @(
+ (Join-Path "$env:WINDIR\Temp" $RegFileName)
+ (Join-Path (Get-Location) $RegFileName)
+ (Join-Path $PSScriptRoot $RegFileName)
+) | Where-Object { Test-Path $_ }
+
+if ($candidates.Count -eq 0) {
+ Write-Error "[netbird-mdm] $RegFileName not found in working directory or `$PSScriptRoot. Attach the file to the JumpCloud command."
+ exit 1
+}
+$regFile = $candidates[0]
+Write-Host "[netbird-mdm] using $regFile"
+
+# Wipe the existing policy key so the .reg is authoritative.
+$existed = Test-Path "Registry::HKEY_LOCAL_MACHINE\Software\Policies\NetBird"
+if ($existed) {
+ & reg.exe delete $RegKey /f | Out-Null
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "[netbird-mdm] failed to clear $RegKey before import (exit $LASTEXITCODE)"
+ exit 1
+ }
+ Write-Host "[netbird-mdm] cleared previous values under $RegKey"
+}
+
+# Import. reg.exe writes both data and (re-)creates the key if needed.
+& reg.exe import $regFile
+if ($LASTEXITCODE -ne 0) {
+ Write-Error "[netbird-mdm] reg import failed (exit $LASTEXITCODE)"
+ exit 1
+}
+
+# Audit dump so the JumpCloud per-execution log captures the applied state.
+Write-Host "[netbird-mdm] final policy state under $RegKey :"
+& reg.exe query $RegKey /s
+
+# Daemon's 1-min reload ticker picks up the change automatically.
+# Uncomment to force immediate convergence (skips the ticker wait):
+# Restart-Service netbird -Force -ErrorAction SilentlyContinue
+
+exit 0
diff --git a/public/docs-static/files/netbird.adml b/public/docs-static/files/netbird.adml
new file mode 100644
index 000000000..d49b05022
--- /dev/null
+++ b/public/docs-static/files/netbird.adml
@@ -0,0 +1,95 @@
+
+
+ NetBird Client Policies
+ Group Policy template for NetBird client MDM-managed settings. Values are written under HKLM\Software\Policies\NetBird and consumed by the netbird daemon at startup and every 1-minute reload tick.
+
+
+
+
+ NetBird
+ NetBird Client 0.40+
+
+
+ Management URL
+ URL of the NetBird management server. Format: https://host[:port]. When set, users cannot override this value via UI or CLI.
+
+ Pre-shared key
+ WireGuard pre-shared key used as an additional symmetric secret on every peer-to-peer tunnel. Secret value.
+
+
+ Disable auto-connect
+ When enabled, the NetBird tunnel does not auto-connect at daemon startup. Equivalent to --disable-auto-connect.
+
+ Disable client routes
+ When enabled, this client will not consume routes advertised by routing peers. Equivalent to --disable-client-routes.
+
+ Disable server routes
+ When enabled, this client will not act as a routing peer for other clients. Equivalent to --disable-server-routes.
+
+ Block inbound
+ When enabled, the client firewall blocks all inbound peer traffic on the WireGuard interface. Equivalent to --block-inbound.
+
+ Allow server SSH
+ When enabled, this client accepts incoming SSH sessions via NetBird SSH. Equivalent to --allow-server-ssh.
+
+ Enable Rosenpass
+ Enables Rosenpass post-quantum key exchange on WireGuard tunnels. Both peers must support it.
+
+ Rosenpass permissive
+ When enabled, the client falls back to plain WireGuard if a peer does not support Rosenpass; otherwise it refuses the connection.
+
+ WireGuard port
+ UDP port used by the local WireGuard interface. Allowed range: 1-65535.
+
+ Split tunnel
+ Restrict the NetBird tunnel to or from a chosen list of application package names. Choose either the allow mode (only the listed apps route through NetBird) or the disallow mode (the listed apps bypass NetBird; everything else routes through). The mode is mutually exclusive — only one can be active at a time. Android-only at the daemon level; Windows/macOS/iOS clients ignore this policy.
+ Allow only listed apps (everything else bypasses)
+ Disallow listed apps (everything else routes)
+
+
+ Disable update settings
+ When enabled, blocks every configuration change from the client UI and from the CLI (netbird up / login / setconfig). The Settings view stays viewable but read-only. Equivalent to --disable-update-settings.
+
+ Disable profiles
+ When enabled, the client UI/CLI cannot list, create, switch or remove NetBird connection profiles. Equivalent to --disable-profiles.
+
+ Disable networks
+ When enabled, the client UI/CLI cannot list, select or deselect NetBird networks (the corresponding daemon RPCs return Unavailable). Equivalent to --disable-networks.
+
+ Disable metrics collection
+ When enabled, the client does not collect or report local usage metrics.
+
+
+
+
+
+
+
+ https://api.netbird.io:443
+
+
+
+
+
+
+
+
+
+
+ WireGuard UDP port:
+
+
+
+ Mode:
+
+
+
+
+
+
+
+
diff --git a/public/docs-static/files/netbird.admx b/public/docs-static/files/netbird.admx
new file mode 100644
index 000000000..51aeccf6b
--- /dev/null
+++ b/public/docs-static/files/netbird.admx
@@ -0,0 +1,223 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ allow
+ disallow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx
index 094a7db68..3d8edbf97 100644
--- a/src/components/NavigationDocs.jsx
+++ b/src/components/NavigationDocs.jsx
@@ -746,6 +746,7 @@ export const docsNavigation = [
links: [
{ title: 'Profiles', href: '/client/profiles' },
{ title: 'Environment Variables', href: '/client/environment-variables' },
+ { title: 'MDM Integration', href: '/client/mdm-integration' },
{
title: 'Settings',
isOpen: false,
diff --git a/src/pages/client/mdm-integration.mdx b/src/pages/client/mdm-integration.mdx
new file mode 100644
index 000000000..7d1976c4d
--- /dev/null
+++ b/src/pages/client/mdm-integration.mdx
@@ -0,0 +1,387 @@
+import { Note, Warning } from "@/components/mdx";
+
+export const description =
+ "Enforce NetBird client configuration through your MDM channel (Intune, Jamf, Kandji, Mosyle, Workspace ONE, JumpCloud, Group Policy). Push management URL, kill switches, and other settings to a fleet of Windows and macOS devices.";
+
+# MDM Integration
+
+NetBird's client honors policies pushed by your Mobile Device Management
+(MDM) channel, so an administrator can enforce configuration across a
+fleet of devices instead of touching each machine. On every supported
+platform the daemon reads from the **OS-native managed-configuration
+store** that your MDM already writes to. No agent of ours sits between
+you and the MDM provider; whatever you can push to that store (manually,
+via Group Policy, via a Configuration Profile, via your MDM console)
+becomes effective NetBird policy.
+
+This page covers Windows and macOS. iOS and Android support is on the
+roadmap.
+
+## At a glance
+
+| Platform | Where NetBird reads policy | How an admin writes it |
+| --- | --- | --- |
+| Windows | `HKLM\Software\Policies\NetBird` (registry) | Group Policy (ADMX) · Intune ADMX ingestion or OMA-URI · `reg import` · MDM-vendor scripts |
+| macOS | `/Library/Managed Preferences/io.netbird.client.plist` | Configuration Profile (`.mobileconfig`) pushed by your MDM, targeting bundle id `io.netbird.client` |
+
+Both backends are the de-facto convention for desktop apps (the same
+shape Chrome, Edge, Firefox, Zoom, Tailscale, Citrix Workspace, and
+others use). Any MDM that supports the platform also supports NetBird —
+there is no NetBird-specific integration to build.
+
+## How enforcement works
+
+When NetBird starts, and every minute while it runs, the daemon:
+
+1. Reads the platform-native managed-configuration store.
+2. Merges the values **on top of every other configuration layer**
+ (defaults → on-disk profile → environment variables → CLI/UI input →
+ **MDM**). MDM always wins.
+3. Locks any field that came from the MDM source. Attempts to change
+ that field from the GUI, the CLI (`netbird up --flag=...`, `netbird
+ login --flag=...`) or via direct gRPC are rejected with a clear
+ error listing the locked fields. The client UI greys these fields
+ out and tags them with **(MDM)** so the user knows they cannot
+ change them.
+4. If the MDM payload changes (admin pushes new values), the change
+ takes effect within ~1 minute on the device — no client restart
+ needed.
+
+
+MDM is **authoritative**. When a key is present in the MDM payload, the
+MDM value wins regardless of whether it is `true` or `false`. An admin
+pushing `disableNetworks=false` via MDM re-enables the feature even on a
+host that was installed with `--disable-networks`. The MDM payload is
+the source of truth as long as the policy is in place. MDM-supplied
+values are also never written back to the on-disk profile, so removing
+the policy at the MDM side takes effect on the next reload with no
+stale residue on the device.
+
+
+## Policy keys reference
+
+The same 16 keys apply on every platform. Names are camelCase in the
+managed-configuration payload; the Windows ADMX template renders the
+PascalCase variant in the Group Policy Editor — both are recognized.
+
+| Key | Type | Description |
+| --- | --- | --- |
+| `managementURL` | string | Override the management server URL (e.g. `https://api.netbird.io:443` or a self-hosted URL). |
+| `preSharedKey` | string | WireGuard pre-shared key. Treated as secret and redacted in logs. |
+| `wireguardPort` | integer | UDP port the local WireGuard interface binds to. Range `1–65535`. |
+| `allowServerSSH` | boolean | Allow the embedded NetBird SSH server on this peer. |
+| `disableAutoConnect` | boolean | Skip auto-connecting on startup; require an explicit `netbird up`. |
+| `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. |
+| `disableClientRoutes` | boolean | This peer does not route traffic to other peers. |
+| `disableServerRoutes` | boolean | This peer is not a router for others. |
+| `disableMetricsCollection` | boolean | Disable anonymous usage telemetry. |
+| `disableUpdateSettings` | boolean | Block every configuration change from UI or CLI on this device (read-only mode). |
+| `disableProfiles` | boolean | Hide the profile menu in the GUI and reject profile CRUD via CLI. |
+| `disableNetworks` | boolean | Hide the Networks / Exit Node menus in the GUI and reject the related RPCs. |
+| `splitTunnelMode` | string | `allow` or `disallow` — split-tunnel policy mode (Android only at the client level; harmless on desktop). |
+| `splitTunnelApps` | string | Comma-separated list of package names that the split-tunnel mode applies to (Android only). |
+
+### Notes on a few keys
+
+- `disableUpdateSettings` and `disableProfiles` overlap with the
+ service-install CLI flags `--disable-update-settings` and
+ `--disable-profiles`. Either source can disable the feature; the MDM
+ value wins when present.
+- `disableUpdateSettings` keeps the Settings view in the GUI visible
+ (so users can inspect current values) but rejects every attempt to
+ save changes. Use it for read-only fleets.
+- `splitTunnelMode` and `splitTunnelApps` are wired into Android's
+ `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.
+- The `disableMetricsCollection` key is reserved for an upcoming
+ metrics integration; the client recognizes it today but no metrics
+ pipeline is shipped yet.
+
+## Windows
+
+The NetBird daemon reads policies from
+`HKLM\Software\Policies\NetBird`. Anything that ends up under that
+registry key — through whichever delivery channel — becomes policy.
+The shapes are:
+
+| Value name | Registry type | Example |
+| --- | --- | --- |
+| `ManagementURL`, `PreSharedKey`, `SplitTunnelMode`, `SplitTunnelApps` | `REG_SZ` | `"https://api.netbird.io:443"` |
+| All `Disable*` flags, `AllowServerSSH`, `RosenpassEnabled`, `RosenpassPermissive` | `REG_DWORD` (0 / 1) | `0x00000001` |
+| `WireguardPort` | `REG_DWORD` | `0x0000ca6c` (51820 decimal) |
+
+Choose one of the delivery channels below. All four converge on the
+same registry key.
+
+### Group Policy (on-prem AD / local gpedit)
+
+1. Copy the ADMX/ADML files into the system Policy Definitions store:
+ - Place `netbird.admx` in `C:\Windows\PolicyDefinitions\`.
+ - Place `netbird.adml` in `C:\Windows\PolicyDefinitions\en-US\`.
+2. Open `gpedit.msc` (or the AD Group Policy Management Editor).
+3. Navigate to **Computer Configuration → Administrative Templates →
+ NetBird**.
+4. Edit any policy (e.g. **Management URL**), set it to **Enabled**
+ with the desired value, and click **OK**.
+5. Run `gpupdate /force` on each target device (or wait for the
+ periodic refresh).
+6. Verify with `reg query HKLM\Software\Policies\NetBird` — the values
+ you set should appear there.
+
+Download the templates: netbird.admx / netbird.adml.
+
+### Microsoft Intune (ADMX ingestion)
+
+Recommended for cloud-managed Windows fleets.
+
+1. In the Intune admin center, go to **Devices → Configuration → Import
+ ADMX**, upload `netbird.admx` together with `netbird.adml`. Wait for
+ the **Available** status.
+2. Create a new **Configuration Profile → Templates → Imported
+ Administrative templates → NetBird**.
+3. Configure the policies you want to enforce.
+4. Assign the profile to your device group(s) and save.
+
+Devices pick up the policy on the next Intune sync (typically within
+8 hours, sooner if you trigger a manual sync from the device). The
+values end up in `HKLM\Software\Policies\NetBird`.
+
+### Microsoft Intune (custom OMA-URI)
+
+If you cannot ingest the ADMX template, you can push individual values
+via OMA-URI under
+`./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/...` or via
+the Registry CSP at
+`./Device/Vendor/MSFT/Registry/HKEY_LOCAL_MACHINE/Software/Policies/NetBird/`.
+ADMX ingestion is simpler and gives admins the same UI as on-prem GPO,
+so prefer that.
+
+### `.reg` import (single source of truth)
+
+For fleets without an MDM, or as a quick-test path, you can carry the
+whole policy in a single `.reg` file:
+
+1. Configure the policy values on a reference machine (via `gpedit` or
+ `reg add`).
+2. Export the key:
+ ```
+ reg export "HKLM\Software\Policies\NetBird" netbird-policy.reg /y
+ ```
+3. Distribute the resulting file and apply with:
+ ```
+ reg import netbird-policy.reg
+ ```
+
+Download a sample: netbird-policy.reg.
+
+### JumpCloud
+
+NetBird ships a JumpCloud companion script: netbird-policy.reg.ps1. To use it:
+
+1. In the JumpCloud admin console, go to **Device Management →
+ Commands → +**.
+2. Type: **Windows PowerShell**. Run as: **SYSTEM**.
+3. Paste `netbird-policy.reg.ps1` verbatim into the command body.
+4. In the same command, attach the `netbird-policy.reg` file you
+ produced above. JumpCloud copies attached files into the command's
+ working directory before invoking the script.
+5. Bind the command to the target system group and run it.
+
+The script wipes the existing `HKLM\Software\Policies\NetBird` key
+before importing the `.reg`, so the `.reg` is the **single source of
+truth** for that device. To unset all policy, attach an empty (header-
+only) `.reg`; the daemon will pick up the absence on the next reload.
+
+## macOS
+
+The NetBird daemon reads policy from
+`/Library/Managed Preferences/io.netbird.client.plist`. macOS writes
+that file when an MDM provider pushes a Configuration Profile whose
+`com.apple.ManagedClient.preferences` payload targets the bundle id
+`io.netbird.client`.
+
+
+macOS wipes the contents of `/Library/Managed Preferences/` on every
+boot if the device is not MDM-enrolled. Manual `defaults write` works
+for a quick test but does not survive a reboot on an un-enrolled Mac.
+Use a real MDM channel for production rollouts.
+
+
+### Custom Configuration Profile (recommended)
+
+This is the canonical macOS path and works with every MDM
+(Jamf, Kandji, Mosyle, Microsoft Intune for Mac, Workspace ONE,
+JumpCloud, Apple Configurator 2, etc.).
+
+1. Start from the template netbird-macos.mobileconfig. Open it in your editor (or in
+ [iMazing Profile Editor](https://imazing.com/profile-editor) /
+ [ProfileCreator](https://github.com/ProfileCreator/ProfileCreator)).
+2. Inside the `mcx_preference_settings` dictionary, set the keys you
+ want to enforce. Keep the bundle id `io.netbird.client` as the
+ preference domain.
+3. Replace the placeholder `PayloadUUID` values with freshly generated
+ UUIDs (`uuidgen` on macOS) so each deployment has unique ids.
+4. (Optional, recommended for production) sign the profile with your
+ organization's Developer ID Installer certificate using
+ `productsign` — unsigned profiles on Sonoma/Sequoia/Tahoe require
+ an extra user confirmation on install.
+5. Upload the resulting `.mobileconfig` to your MDM as a **Custom
+ Configuration Profile** and scope it to the target device group.
+
+Verify on a target device with:
+
+```bash
+sudo defaults read "/Library/Managed Preferences/io.netbird.client"
+```
+
+The output should match the keys you set in the profile.
+
+### MDM-specific notes
+
+- **Jamf Pro**: upload as **Computers → Configuration Profiles → New →
+ Application & Custom Settings → External Applications → Upload File
+ (Plist file)** for the preference domain `io.netbird.client`.
+- **Kandji**: use the **Custom Profile** assignment library item.
+- **Mosyle**: **Profiles → Add new profile → Custom Settings** with
+ domain `io.netbird.client`.
+- **Microsoft Intune (for Mac)**: **Devices → Configuration → Create
+ profile → macOS → Templates → Custom**, upload the `.mobileconfig`.
+- **Apple Configurator 2** (no MDM, ideal for testing on a tethered
+ device): drag the `.mobileconfig` onto the device in Configurator and
+ push.
+
+### JumpCloud
+
+JumpCloud supports two delivery channels for the NetBird policy on
+macOS. Pick whichever fits how your fleet is enrolled.
+
+#### MDM Custom Configuration Profile (recommended for MDM-enrolled fleets)
+
+If your Macs are MDM-enrolled with JumpCloud, push the policy as a
+managed-preferences plist:
+
+1. In the JumpCloud admin console, open **Policy Management →
+ Policies → +** and choose the **Mac** platform.
+2. Pick the **MDM Custom Configuration Profile** policy template.
+3. Upload io.netbird.client.plist
+ as the plist payload. Edit the file before upload to enable just
+ the keys you want to enforce — leave the rest commented out.
+4. Bind the policy to the target Device Group and save.
+
+Notes:
+
+- JumpCloud's **MDM Custom Configuration Profile** accepts a bare
+ managed-preferences plist (the inner Apple managed-prefs dictionary)
+ — **not** a full `.mobileconfig` envelope. Uploading
+ `netbird-macos.mobileconfig` will be rejected. Use the bare
+ `io.netbird.client.plist` for this code path; reserve
+ `netbird-macos.mobileconfig` for other MDMs that expect the full
+ Configuration Profile shape.
+- Keep the filename as `io.netbird.client.plist`. The Apple
+ convention for managed-preferences plists is
+ `.plist` (this is how macOS materializes the file at
+ `/Library/Managed Preferences/.plist`), and JumpCloud's
+ policy form does not currently expose a separate bundle-identifier
+ field — keeping the canonical filename is the safest path. If your
+ JumpCloud console version surfaces a bundle-id / preference-domain
+ field elsewhere in the policy wizard, set it to `io.netbird.client`
+ too.
+
+JumpCloud wraps the plist into an Apple Configuration Profile and
+pushes it via the MDM channel. The OS materializes the file at
+`/Library/Managed Preferences/io.netbird.client.plist`, where the
+NetBird daemon picks it up within the next 1-minute reload tick.
+Removing the policy from JumpCloud removes the file on the next sync,
+which un-locks the corresponding fields on the client.
+
+#### Shell Command (no MDM enrollment required)
+
+If your fleet is JumpCloud-managed but not MDM-enrolled, NetBird ships
+a companion script: netbird-macos.sh. It is the macOS
+counterpart of the Windows `.reg.ps1` script — same fleet, different
+backend:
+
+1. Edit the `### POLICY VALUES ###` block at the top of the script;
+ set the variables for the keys you want to enforce and leave the
+ rest at `$NULL`.
+2. In the JumpCloud admin console, go to **Device Management →
+ Commands → +**. Type: **Mac, Shell**. Run as: **root**.
+3. Paste the edited script verbatim into the command body.
+4. Bind to the target system group and run.
+
+The script writes
+`/Library/Managed Preferences/io.netbird.client.plist`, sets ownership
+to `root:wheel` with mode `644`, and kicks the NetBird daemon so the
+change applies immediately. On MDM-enrolled devices the file survives
+reboots; on un-enrolled devices the file is wiped at the next reboot
+(macOS-imposed). Prefer the Custom Mac Application Settings policy
+above when the fleet is enrolled.
+
+## Verifying enforcement
+
+On any platform, the cleanest verification is the daemon's own debug
+dump:
+
+```bash
+netbird debug config
+```
+
+The response includes a `mDMManagedFields` array that lists every key
+the daemon is currently honoring from the MDM source. If a key you
+expected to be locked is missing from that array, the MDM payload did
+not reach the device (or used a value name the daemon does not
+recognize).
+
+The client UI mirrors the same state: any submenu item, settings field,
+or kill switch driven by MDM appears greyed out with a **(MDM)** tag
+next to its label.
+
+Daemon logs (`/var/log/netbird/client.log` on Linux/macOS,
+`%ProgramData%\Netbird\` on Windows) contain a one-line
+`MDM enrolled with N managed key(s): [...]` entry on every reload, plus
+one `MDM override = ` line per applied key. Secrets are
+redacted.
+
+## Troubleshooting
+
+**The policy did not apply at all.**
+Check that the daemon can see the source.
+
+- Windows: `reg query HKLM\Software\Policies\NetBird` — if empty, the
+ delivery channel did not write the values. Check `gpresult /h` for
+ GPO failures or the Intune sync status in **Settings → Accounts →
+ Access work or school → Info → Sync**.
+- macOS:
+ `sudo defaults read /Library/Managed\ Preferences/io.netbird.client`
+ — if the file is missing, the MDM payload was not pushed or the
+ bundle id in the profile does not match `io.netbird.client`.
+
+**The key shows up in the registry / plist but not in `netbird debug
+config` `mDMManagedFields`.**
+The value name is misspelled. Names are case-insensitive but must match
+one of the keys in the reference table above. The daemon log emits an
+`MDM ignoring unknown ` warning when this happens.
+
+**The user can still change the field from the GUI / CLI.**
+The change is being rejected by the daemon but the UI may not have
+caught up yet. The UI refreshes within a couple of seconds after a
+config change; try closing and reopening the Settings window. If the
+change actually sticks, double-check that the MDM payload is still
+present on the device — it may have been removed by another policy.
+
+**On macOS, the file disappears after a reboot.**
+The device is not MDM-enrolled. macOS protects
+`/Library/Managed Preferences/` by wiping it at boot if no MDM
+controls the directory. Enroll the device with a real MDM provider for
+persistent rollouts.
+
+**My MDM provider is not in the list above.**
+Any MDM that can push a Configuration Profile on macOS or write a
+registry value on Windows works. The mechanism is OS-native, not
+NetBird-specific. If you hit a quirk specific to your provider, please
+open an issue at
+https://github.com/netbirdio/netbird/issues with the provider name and
+what you observed.