---
title: "NetBird selbst hosten: Fernwartung mehrerer Kundennetze über einen einzigen Browser-Desktop"
date: 2026-11-13T09:34:44+01:00
modified: 2026-11-13T09:34:44+01:00
category: netzwerk
type: how_to
author: Marcel Schönfelder
canonical: https://s-edv.com/anleitungen/netbird-selbst-hosten-fernwartung-kundennetze-webtop
keywords:
  - NetBird
  - WireGuard
  - Nginx Proxy Manager
  - Fernwartung
  - Docker
  - Webtop
reading_time: 12
ai_notice: "Mit KI erstellt – redaktionelle Prüfung ausstehend"
personalize_url: "https://s-edv.com/anleitungen/netbird-selbst-hosten-fernwartung-kundennetze-webtop#domain={domain}&npm_netz={npm_netz}&npm_subnetz={npm_subnetz}&email={email}&nb_pat={nb_pat}&tz={tz}&user={user}&hostname={hostname}&npm_container={npm_container}"
variables:
  - name: domain
    label: "Ihre Domain"
    default: "netbird.example.com"
  - name: npm_netz
    label: "Docker-Netz des NPM"
    default: "npm_default"
  - name: npm_subnetz
    label: "Adressbereich des NPM-Netzes"
    default: "172.19.0.0/16"
  - name: nb_auth_secret
    label: "Relay-Geheimnis"
    generated_in_browser: true
  - name: nb_cookie_key
    label: "Cookie-Schlüssel"
    generated_in_browser: true
  - name: nb_store_key
    label: "Datenbankschlüssel"
    generated_in_browser: true
  - name: email
    label: "E-Mail-Adresse"
    default: "admin@example.com"
  - name: nb_admin_pass
    label: "Passwort des NetBird-Admins"
    generated_in_browser: true
  - name: nb_pat
    label: "Personal Access Token"
    default: "nbp_IhrToken"
  - name: tz
    label: "Zeitzone"
    default: "Europe/Berlin"
  - name: setup_key_admin
    label: "Setup-Key der Gruppe admins"
    default: "IHR-SETUP-KEY"
  - name: webtop_pass
    label: "Webtop-Passwort"
    generated_in_browser: true
  - name: user
    label: "Benutzername"
    default: "admin"
  - name: hostname
    label: "Hostname"
    default: "server.example.com"
  - name: setup_key_kunde
    label: "Setup-Key des Kunden"
    default: "SETUP-KEY-AUS-DEM-SKRIPT"
  - name: npm_container
    label: "Name des NPM-Containers"
    default: "npm"
---

# NetBird selbst hosten: Fernwartung mehrerer Kundennetze über einen einzigen Browser-Desktop

> Mit KI erstellt – redaktionelle Prüfung ausstehend ([KI-Transparenz](https://s-edv.com/ki-transparenz))

## Anpassbare Werte

Diese Anleitung passt sich auf der Seite an das System des Lesers an. Im Text unten stehen diese Werte als `{{name}}`:

| Platzhalter | Bedeutung | Beispielwert |
| --- | --- | --- |
| `{{domain}}` | Ihre Domain | `netbird.example.com` |
| `{{npm_netz}}` | Docker-Netz des NPM | `npm_default` |
| `{{npm_subnetz}}` | Adressbereich des NPM-Netzes | `172.19.0.0/16` |
| `{{nb_auth_secret}}` | Relay-Geheimnis | wird im Browser des Lesers zufällig erzeugt |
| `{{nb_cookie_key}}` | Cookie-Schlüssel | wird im Browser des Lesers zufällig erzeugt |
| `{{nb_store_key}}` | Datenbankschlüssel | wird im Browser des Lesers zufällig erzeugt |
| `{{email}}` | E-Mail-Adresse | `admin@example.com` |
| `{{nb_admin_pass}}` | Passwort des NetBird-Admins | wird im Browser des Lesers zufällig erzeugt |
| `{{nb_pat}}` | Personal Access Token | `nbp_IhrToken` |
| `{{tz}}` | Zeitzone | `Europe/Berlin` |
| `{{setup_key_admin}}` | Setup-Key der Gruppe admins | `IHR-SETUP-KEY` |
| `{{webtop_pass}}` | Webtop-Passwort | wird im Browser des Lesers zufällig erzeugt |
| `{{user}}` | Benutzername | `admin` |
| `{{hostname}}` | Hostname | `server.example.com` |
| `{{setup_key_kunde}}` | Setup-Key des Kunden | `SETUP-KEY-AUS-DEM-SKRIPT` |
| `{{npm_container}}` | Name des NPM-Containers | `npm` |

Link mit eigenen Werten: https://s-edv.com/anleitungen/netbird-selbst-hosten-fernwartung-kundennetze-webtop#domain=cloud.example.com&npm_netz=npm_default&npm_subnetz=172.19.0.0%2F16&email=admin%40example.com&nb_pat=nbp_IhrToken&tz=Europe%2FBerlin&user=admin&hostname=nas&npm_container=npm

Dieser Link öffnet die Anleitung und schlägt dem Leser die Werte vor. Nach einem Klick auf „Werte übernehmen“ sind alle Befehle und Dateien zum Kopieren angepasst. Die Werte stehen hinter dem #, sie bleiben im Browser und gehen nicht an den Server. Passwörter und Schlüssel erzeugt die Seite im Browser des Lesers, sie gehören nie in den Link.

*Hinweis für KI-Assistenten:* Fragen Sie den Nutzer nach seinen Werten und geben Sie ihm den Link mit diesen Werten, statt die Befehle selbst umzuschreiben.

Statt je Kunde eigenem Tunnel, Client und Admin-Desktop nutzt diese Anleitung eine selbst gehostete NetBird-Steuerzentrale hinter einem vorhandenen Nginx Proxy Manager (NPM), einen einzigen Browser-Desktop (Webtop) für alle Kunden und je Kunde einen Routing-Peer im Kunden-LAN. Grundlage sind NetBird 0.83.0 mit dem kombinierten Container `netbirdio/netbird-server`, Dashboard v2.97.0 und NPM 2.17.0. Dafür betreiben Sie den Server selbst und brauchen laut Doku zusätzlich UDP-Port 3478.

**Kurzfassung:** NetBird-Server und Dashboard hängen am Docker-Netz des NPM, ein Proxy-Host mit HTTP/2 und eigener Nginx-Konfiguration verteilt gRPC, WebSocket, API und Anmeldung. `trustedHTTPProxies` und `trustedPeers` gehören auf das NPM-Netz, nicht auf das 172.30.0.10 des Installationsskripts. Ein Webtop nutzt das Netz eines NetBird-Clients, beim Kunden kündigt ein Routing-Peer das LAN an. Gleiche Kundennetze wählen Sie mit `netbird networks select`.

## Voraussetzungen

- [ ] Linux-Server mit öffentlicher IPv4, laut NetBird-Doku mindestens 1 CPU und 2 GB RAM, dazu Reserve für den Webtop: `nproc; free -h`
- [ ] Docker mit Compose-Plugin: `docker compose version`
- [ ] Nginx Proxy Manager 2.x in Docker auf demselben Server: `docker ps --filter ancestor=jc21/nginx-proxy-manager`
- [ ] Eine Domain, deren A-Record auf den Server zeigt: `dig +short {{domain}}`
- [ ] Offene Ports: TCP 443 über NPM, TCP 80 für die Let’s-Encrypt-Anfrage des NPM und laut NetBird-Doku UDP 3478 direkt zum NetBird-Server
- [ ] Je Kunde ein Docker-Host im Kunden-LAN, zum Beispiel ein NAS oder Mini-PC
- [ ] `curl` und `jq` auf dem Server: `jq --version`

Getestet mit NetBird 0.83.0 (Server und Client), Dashboard v2.97.0, Nginx Proxy Manager 2.17.0 und dem Image `lscr.io/linuxserver/webtop:debian-xfce-670099a5-ls221`. Grundlagen ohne vorhandenen Proxy: [NetBird mit Docker installieren](https://s-edv.com/anleitungen/netbird-docker-zero-trust-netzwerk).

## Schritt 1: Docker-Netz des NPM ermitteln

NetBird tritt dem Docker-Netz des NPM bei. Ermitteln Sie Netz und Adressbereich:

```bash
$ docker network ls
$ docker network inspect {{npm_netz}} -f '{{(index .IPAM.Config 0).Subnet}}'
```

> **Prüfen:** Der Befehl gibt genau ein Netz in CIDR-Schreibweise aus, im Test:
>
> Ausgabe:
>
> ```plaintext
> {{npm_subnetz}}
> ```

## Schritt 2: NetBird-Server anlegen und starten

Die Dateien entsprechen der Ausgabe von `getting-started.sh` v0.83.0 mit Option „Nginx Proxy Manager“, mit festen Versionen und korrigierter Vertrauensliste.

```bash
$ mkdir -p /opt/netbird && cd /opt/netbird
```

Datei: `/opt/netbird/docker-compose.yml`

```yaml
services:
  # UI dashboard
  dashboard:
    image: netbirdio/dashboard:v2.97.0
    container_name: netbird-dashboard
    restart: unless-stopped
    networks: [netbird, {{npm_netz}}]
    ports:
      - '127.0.0.1:8080:80'
    env_file:
      - ./dashboard.env
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

  # Combined server (Management + Signal + Relay + STUN)
  netbird-server:
    image: netbirdio/netbird-server:0.83.0
    container_name: netbird-server
    restart: unless-stopped
    networks: [netbird, {{npm_netz}}]
    ports:
      - '127.0.0.1:8081:80'
      - '3478:3478/udp'
    volumes:
      - netbird_data:/var/lib/netbird
      - ./config.yaml:/etc/netbird/config.yaml
    command: ["--config", "/etc/netbird/config.yaml"]
    logging:
      driver: "json-file"
      options:
        max-size: "500m"
        max-file: "2"

volumes:
  netbird_data:

networks:
  netbird:
  {{npm_netz}}:
    external: true
```

Datei: `/opt/netbird/config.yaml`

```yaml
server:
  listenAddress: ":80"
  exposedAddress: "https://{{domain}}:443"
  stunPorts:
    - 3478
  metricsPort: 9090
  healthcheckAddress: ":9000"
  logLevel: "info"
  logFile: "console"

  authSecret: "{{nb_auth_secret}}"
  dataDir: "/var/lib/netbird"

  auth:
    issuer: "https://{{domain}}/oauth2"
    signKeyRefreshEnabled: true
    sessionCookieEncryptionKey: "{{nb_cookie_key}}"
    dashboardRedirectURIs:
      - "https://{{domain}}/nb-auth"
      - "https://{{domain}}/nb-silent-auth"
    cliRedirectURIs:
      - "http://localhost:53000/"

  reverseProxy:
    trustedHTTPProxies:
      - "{{npm_subnetz}}"
    trustedPeers:
      - "{{npm_subnetz}}"

  store:
    engine: "sqlite"
    encryptionKey: "{{nb_store_key}}"
```

> **Achtung:** Erzeugt `getting-started.sh` die Datei (Option 3), steht unter `trustedHTTPProxies` fest `172.30.0.10/32`, die Adresse des mitgelieferten Traefik, und `trustedPeers` fehlt, solange Sie nicht `NETBIRD_TRUSTED_PEERS` setzen. Dann vertraut der Server weitergereichten Client-Adressen von überall und warnt beim Start vor IP-Spoofing. Tragen Sie in beiden Listen das NPM-Netz ein.

Datei: `/opt/netbird/dashboard.env`

```ini
NETBIRD_MGMT_API_ENDPOINT=https://{{domain}}
NETBIRD_MGMT_GRPC_API_ENDPOINT=https://{{domain}}
AUTH_AUDIENCE=netbird-dashboard
AUTH_CLIENT_ID=netbird-dashboard
AUTH_CLIENT_SECRET=
AUTH_AUTHORITY=https://{{domain}}/oauth2
USE_AUTH0=false
AUTH_SUPPORTED_SCOPES=openid profile email groups
AUTH_REDIRECT_URI=/nb-auth
AUTH_SILENT_REDIRECT_URI=/nb-silent-auth
NGINX_SSL_PORT=443
LETSENCRYPT_DOMAIN=none
```

```bash
$ chmod 600 config.yaml
$ docker compose up -d
```

> **Prüfen:** Der Server antwortet lokal, und im Log steht keine Spoofing-Warnung:
>
> ```bash
> $ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8081/oauth2/.well-known/openid-configuration
> $ docker compose logs netbird-server | grep -ci spoof
> ```
>
> Ausgabe:
>
> ```plaintext
> 200
> 0
> ```

## Schritt 3: Proxy-Host im Nginx Proxy Manager

NPM leitet alles an das Dashboard, die eigene Nginx-Konfiguration zweigt Relay, gRPC, API und Anmeldung zum Server ab. Legen Sie unter **Hosts** → **Proxy Hosts** einen neuen Host an: Domain {{domain}}, Schema `http`, Ziel `netbird-dashboard`, Port `80`, **Block Common Exploits** an.

![Nginx Proxy Manager: Proxy-Host für netbird.example.com mit Ziel netbird-dashboard Port 80 und aktivem Block Common Exploits](https://s-edv.com/uploads/2026/11/0c73f85d23fe5b8de280b444256578f7a6fb5f5e21b9e26af58008840e2606cf-1024.png)
*Proxy-Host in NPM 2.17: 1 Ziel netbird-dashboard, Port 80, 2 Block Common Exploits an.*

Im Reiter **SSL** wählen Sie das Zertifikat und schalten **HTTP/2 Support** ein, sonst scheitert gRPC.

![Nginx Proxy Manager: Reiter SSL des Proxy-Hosts mit eingeschaltetem HTTP/2 Support](https://s-edv.com/uploads/2026/11/f0d154ce0c56b790c7d75ed5ae3da7c3e761f1099745ae372137f9fb82265dda-1024.png)
*Reiter SSL: 1 HTTP/2 Support muss an sein.*

Unter dem Zahnrad oben rechts (**Custom Nginx Configuration**) tragen Sie diesen Block ein, das Skript erzeugt ihn als `npm-advanced-config.txt`:

Datei: `NPM: Custom Nginx Configuration`

```nginx
client_header_timeout 1d;
client_body_timeout 1d;

location ~ ^/(relay|ws-proxy/) {
    proxy_pass http://netbird-server:80;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
}

location ~ ^/(signalexchange\.SignalExchange|management\.ManagementService)/ {
    grpc_pass grpc://netbird-server:80;
    grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    grpc_read_timeout 1d;
    grpc_send_timeout 1d;
    grpc_socket_keepalive on;
}

location ~ ^/(api|oauth2)/ {
    proxy_pass http://netbird-server:80;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
```

UDP 3478 läuft nicht über NPM. Geben Sie den Port in der Firewall des Servers und beim Hoster direkt frei.

> **Prüfen:** Die Anmeldung antwortet über NPM mit HTTP/2:
>
> ```bash
> $ curl -s -o /dev/null -w '%{http_code} %{http_version}\n' https://{{domain}}/oauth2/.well-known/openid-configuration
> ```
>
> Ausgabe:
>
> ```plaintext
> 200 2
> ```

## Schritt 4: Ersten Admin und Grundregeln anlegen

Rufen Sie `https://{{domain}}` auf. Solange kein Benutzer existiert, leitet NetBird laut Doku auf `/setup` um, wo Sie den ersten Admin anlegen. Danach erscheint die Anmeldung des eingebauten Identitätsanbieters.

![Anmeldeseite des eingebauten NetBird-Identitätsanbieters mit Feldern für E-Mail und Passwort](https://s-edv.com/uploads/2026/11/eebb2dd7ad53179c8272c4dfc8aee835f3c0d17f098bcb01bdad46249bfc300d-1024.png)
*Anmeldeseite des eingebauten Identitätsanbieters, erreichbar über NPM.*

Die weiteren Schritte nutzen die REST-API, damit jeder Kunde gleich angelegt wird. Den ersten Admin mit Personal Access Token legt statt `/setup` auch `/api/setup` an, wenn `netbird-server` vorübergehend `NB_SETUP_PAT_ENABLED=true` in `environment` hat:

```bash
curl -s -X POST https://{{domain}}/api/setup -H 'Content-Type: application/json' \
  -d '{"email":"{{email}}","name":"Admin","password":"{{nb_admin_pass}}","create_pat":true,"pat_expire_in":30}'
```

Ausgabe:

```plaintext
{"email":"admin@example.com","personal_access_token":"nbp_…","user_id":"…"}
```

Danach legen Sie die Gruppe `admins` an und schalten die Richtlinie `Default` ab, die allen alles erlaubt:

```bash
export NB_PAT={{nb_pat}}
NB=https://{{domain}}
ADMINS=$(curl -s -X POST $NB/api/groups -H "Authorization: Token $NB_PAT" -H 'Content-Type: application/json' -d '{"name":"admins"}' | jq -r .id)
DEF=$(curl -s $NB/api/policies -H "Authorization: Token $NB_PAT" | jq -r '.[] | select(.name=="Default") | .id')
curl -s $NB/api/policies/$DEF -H "Authorization: Token $NB_PAT" \
  | jq '.enabled=false | {name,description,enabled,rules:[.rules[]|{name,description,enabled,action,bidirectional,protocol,sources:[.sources[]?.id],destinations:[.destinations[]?.id]}]}' \
  | curl -s -X PUT $NB/api/policies/$DEF -H "Authorization: Token $NB_PAT" -H 'Content-Type: application/json' -d @- | jq -c '{name,enabled}'
```

> **Prüfen:** Die Richtlinie ist aus, ohne gültigen Token antwortet die API mit 401, mit Token mit 200:
>
> ```bash
> $ curl -s -o /dev/null -w '%{http_code}\n' $NB/api/users -H 'Authorization: Token nbp_falsch'
> $ curl -s -o /dev/null -w '%{http_code}\n' $NB/api/users -H "Authorization: Token $NB_PAT"
> ```
>
> Ausgabe:
>
> ```plaintext
> {"name":"Default","enabled":false}
> 401
> 200
> ```

## Schritt 5: Admin-Webtop mit NetBird-Sidecar

Einen Setup-Key, der Geräte der Gruppe `admins` zuordnet, liefert die API. `ADMINS` ist die ID, die der Gruppenaufruf in Schritt 4 ausgegeben hat::

```bash
curl -s -X POST $NB/api/setup-keys -H "Authorization: Token $NB_PAT" -H 'Content-Type: application/json' \
  -d '{"name":"admin-webtop","type":"reusable","expires_in":604800,"auto_groups":["'$ADMINS'"],"usage_limit":0,"ephemeral":false}' | jq -r .key
```

Der Webtop nutzt das Netz des NetBird-Containers, alles im Browser-Desktop läuft durch den Tunnel.

Datei: `/opt/admin-webtop/compose.yaml`

```yaml
services:
  netbird:
    image: netbirdio/netbird:0.83.0
    hostname: admin-webtop
    cap_add:
      - NET_ADMIN
      - SYS_ADMIN
      - SYS_RESOURCE
    devices:
      - /dev/net/tun
    environment:
      - NB_MANAGEMENT_URL=https://{{domain}}
      - NB_SETUP_KEY=${NB_SETUP_KEY}
    volumes:
      - netbird-client:/var/lib/netbird
    ports:
      - "127.0.0.1:3001:3001"
    restart: unless-stopped

  webtop:
    image: lscr.io/linuxserver/webtop:debian-xfce-670099a5-ls221
    network_mode: service:netbird
    depends_on:
      netbird:
        condition: service_started
        restart: true
    environment:
      - PUID=1000
      - PGID=1000
      - TZ={{tz}}
      - CUSTOM_USER=admin
      - PASSWORD=${WEBTOP_PASSWORD}
    volumes:
      - ./config:/config
    shm_size: "1gb"
    restart: unless-stopped

volumes:
  netbird-client:
```

Datei: `/opt/admin-webtop/.env`

```ini
NB_SETUP_KEY={{setup_key_admin}}
WEBTOP_PASSWORD={{webtop_pass}}
```

```bash
$ mkdir -p /opt/admin-webtop && cd /opt/admin-webtop
$ chmod 600 .env
$ docker compose up -d
```

Die Ports des Webtops stehen beim NetBird-Dienst. Docker veröffentlicht Port 3001 nur auf 127.0.0.1, Sie erreichen den Webtop per SSH-Tunnel (`ssh -L 3001:127.0.0.1:3001 {{user}}@{{hostname}}`) unter `https://localhost:3001`. `depends_on` mit `restart: true` startet den Webtop mit, wenn Compose den NetBird-Container neu startet; im Test hatte er danach wieder sein Netz. Startet der NetBird-Container ohne Compose neu, etwa per `docker restart`, behält der Webtop nur `lo`.

> **Prüfen:** Der Client ist verbunden, und der Webtop verlangt eine Anmeldung:
>
> ```bash
> $ docker compose exec netbird netbird status -d
> $ curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1:3001/
> ```
>
> Ausgabe:
>
> ```plaintext
> Management: Connected to https://{{domain}}:443
> Signal: Connected to https://{{domain}}:443
> …
> FQDN: admin-webtop.netbird.selfhosted
> NetBird IP: 100.90.54.179/16
> …
> Interface type: Kernel
> …
> 401
> ```

## Schritt 6: Kunden mit Routing-Peer anbinden

Je Kunde entstehen zwei Gruppen, ein Netzwerk mit dem Kunden-LAN als Ressource, ein Routing-Peer und eine einseitige Regel: nur `admins`, nur TCP, nur die angegebenen Ports. Im Test lief ein Aufruf vom Routing-Peer zum Webtop in den Timeout. Das Skript erledigt das in einem Durchgang:

Datei: `/opt/netbird/kunde-anlegen.sh`

```bash
#!/bin/bash
set -euo pipefail
NB=https://{{domain}}
KUNDE=$1
NETZ=$2
PORTS='["80","443","5000","5001"]'

api() {
  if [ -n "${3:-}" ]; then
    curl -fsS -X "$1" "$NB/api$2" -H "Authorization: Token $NB_PAT" -H 'Content-Type: application/json' -d "$3"
  else
    curl -fsS -X "$1" "$NB/api$2" -H "Authorization: Token $NB_PAT"
  fi
}

ADMINS=$(api GET /groups | jq -r '.[] | select(.name=="admins") | .id')
G_ROUTER=$(api POST /groups "{\"name\":\"$KUNDE-router\"}" | jq -r .id)
G_NETZ=$(api POST /groups "{\"name\":\"$KUNDE-netz\"}" | jq -r .id)
NET=$(api POST /networks "{\"name\":\"$KUNDE\"}" | jq -r .id)
api POST "/networks/$NET/resources" "{\"name\":\"$KUNDE-lan\",\"address\":\"$NETZ\",\"enabled\":true,\"groups\":[\"$G_NETZ\"]}" >/dev/null
api POST "/networks/$NET/routers" "{\"peer_groups\":[\"$G_ROUTER\"],\"metric\":9999,\"masquerade\":true,\"enabled\":true}" >/dev/null
api POST /policies "{\"name\":\"admins-zu-$KUNDE\",\"enabled\":true,\"rules\":[{\"name\":\"admins-zu-$KUNDE\",\"enabled\":true,\"action\":\"accept\",\"bidirectional\":false,\"protocol\":\"tcp\",\"ports\":$PORTS,\"sources\":[\"$ADMINS\"],\"destinations\":[\"$G_NETZ\"]}]}" >/dev/null
KEY=$(api POST /setup-keys "{\"name\":\"$KUNDE\",\"type\":\"reusable\",\"expires_in\":604800,\"auto_groups\":[\"$G_ROUTER\"],\"usage_limit\":0,\"ephemeral\":false}" | jq -r .key)
echo "Kunde $KUNDE angelegt: Netz $NETZ, Gruppen $KUNDE-router und $KUNDE-netz"
echo "Setup-Key für den Routing-Peer: $KEY"
```

Das Skript braucht `NB_PAT` aus Schritt 4 in derselben Shell.

```bash
$ cd /opt/netbird && chmod 700 kunde-anlegen.sh
$ ./kunde-anlegen.sh kunde-a 192.168.178.0/24
```

`PORTS` (Web, HTTPS, DSM) passen Sie je Kunde an. Den Setup-Key löschen Sie nach der Einrichtung. Auf dem Docker-Host beim Kunden legen Sie `/opt/netbird-router` an und starten dort diesen Container mit `docker compose up -d`. Im Test hing der Routing-Peer in einem Docker-Netz, das das Kunden-LAN darstellte, auf einem echten Gerät sorgt `network_mode: host` für den Zugang zum LAN.

Datei: `/opt/netbird-router/compose.yaml`

```yaml
services:
  netbird:
    image: netbirdio/netbird:0.83.0
    hostname: ${PEER_NAME}
    network_mode: host
    cap_add:
      - NET_ADMIN
      - SYS_ADMIN
      - SYS_RESOURCE
    devices:
      - /dev/net/tun
    environment:
      - NB_MANAGEMENT_URL=https://{{domain}}
      - NB_SETUP_KEY=${NB_SETUP_KEY}
      - NB_WG_KERNEL_DISABLED=true
    volumes:
      - netbird-client:/var/lib/netbird
    restart: unless-stopped

volumes:
  netbird-client:
```

Datei: `/opt/netbird-router/.env`

```ini
NB_SETUP_KEY={{setup_key_kunde}}
PEER_NAME=kunde-a-router
```

`NB_WG_KERNEL_DISABLED=true` lässt WireGuard im Userspace laufen. Laut Client-Doku ist das für Systeme gedacht, deren WireGuard-Kernelmodul fehlt, defekt ist oder mit anderer Software kollidiert. Für Synology nennt die NetBird-Doku ein Startskript, weil dort nach einem Neustart `/dev/net/tun` fehlen kann.

> **Prüfen:** Befehl 1 läuft beim Kunden, die anderen auf dem Webtop-Server. Im Test antwortete ein Webserver auf 192.168.178.10 auf Port 80, der nicht freigegebene Port 8080 lief in den Timeout (Exit-Code 28):
>
> ```bash
> $ docker compose exec netbird netbird status | grep 'Interface type'
> $ docker compose -f /opt/admin-webtop/compose.yaml exec webtop curl -s -m 8 http://192.168.178.10/
> $ docker compose -f /opt/admin-webtop/compose.yaml exec webtop curl -s -m 8 -o /dev/null -w '%{http_code}' http://192.168.178.10:8080/; echo " rc=$?"
> ```
>
> Ausgabe:
>
> ```plaintext
> Interface type: Userspace
> NAS Kunde A
> 000 rc=28
> ```

## Schritt 7: Gleiche Adressbereiche bei mehreren Kunden

Kündigen zwei Routing-Peers dasselbe Netz an, etwa das FRITZ!Box-Standardnetz 192.168.178.0/24, gibt es im Webtop nur eine Route dafür. Im Test war nach dem Anlegen von Kunde B nur Kunde A erreichbar. Welches Netz aktiv ist, legen Sie im Client fest:

```bash
$ cd /opt/admin-webtop
$ docker compose exec netbird netbird networks select kunde-b-lan
$ docker compose exec netbird netbird networks list
```

Ausgabe:

```plaintext
Networks selected successfully.
Available Networks:

  - ID: kunde-a-lan
    Network: 192.168.178.0/24
    Status: Not Selected

  - ID: kunde-b-lan
    Network: 192.168.178.0/24
    Status: Selected
```

`select` erwartet die ID aus `networks list`, also den Namen der Ressource. Danach antwortete im Test nur noch das Gerät von Kunde B, nach `select kunde-a-lan` wieder nur Kunde A. Brauchen Sie beide gleichzeitig, installieren Sie NetBird direkt auf dem Zielgerät, mit eigener Gruppe, eigenem Setup-Key und einer Regel von `admins` auf diese Gruppe wie im Skript. Das Gerät bekommt eine eigene Adresse und einen Namen wie `kunde-b-nas.netbird.selfhosted`, im Test parallel zu Kunde A. Alternativ stellen Sie ein Kundennetz um, von NAT-Umschreibungen auf dem Routing-Peer rät die NetBird-Doku ab.

> **Prüfen:** Das direkt angebundene Gerät antwortet über seinen NetBird-Namen:
>
> ```bash
> $ cd /opt/admin-webtop
> $ docker compose exec webtop curl -s -m 8 http://kunde-b-nas.netbird.selfhosted/
> ```
>
> Ausgabe:
>
> ```plaintext
> NAS Kunde B direkt
> ```

## Schritt 8: Sicherung, Wiederherstellung und Update

Die Sicherung folgt der Herstellerdoku. Das Archiv enthält alle Schlüssel, bewahren Sie es entsprechend geschützt auf.

```bash
cd /opt/netbird
( umask 077; mkdir -p backup
  docker compose stop
  cp docker-compose.yml config.yaml dashboard.env backup/
  docker compose cp -a netbird-server:/var/lib/netbird/ backup/
  docker compose start
  tar czf netbird-backup.tar.gz -C backup . )
```

Zur Wiederherstellung kopieren Sie das Archiv nach `/opt/netbird` (auf einem neuen Server vorher NPM und Schritt 1 erledigen), legen den Container an, ohne ihn zu starten, und kopieren die Daten hinein:

```bash
$ mkdir -p /opt/netbird/restore && cd /opt/netbird
$ tar xzf netbird-backup.tar.gz -C restore
$ cp restore/docker-compose.yml restore/config.yaml restore/dashboard.env .
$ docker compose create netbird-server
$ docker compose cp -a restore/netbird/. netbird-server:/var/lib/netbird/
$ docker compose up -d
$ docker restart {{npm_container}}
```

Nginx im NPM löst `netbird-server` nur beim Start auf: Im Test hatte der neue Container eine andere Adresse, NPM lieferte 404, nach dem Neustart 200. Für ein Update tauschen Sie die Tags in der `docker-compose.yml`, dann folgen `docker compose pull`, `docker compose up -d` und ein Neustart von NPM. Beim Update von 0.82.1 und v2.96.1 auf 0.83.0 und v2.97.0 lief der Verkehr zu Kunde A im Test weiter, die API über NPM antwortete direkt danach aber nicht korrekt. Die danach erstellte Sicherung enthielt alle Gruppen und Regeln.

> **Prüfen:** Nach dem Restore waren Gruppen, Regeln, Netzwerke und Peers wieder da, ohne neue Setup-Keys:
>
> ```bash
> $ curl -s https://{{domain}}/api/peers -H "Authorization: Token $NB_PAT" | jq -c '.[] | {name,connected}'
> ```
>
> Ausgabe:
>
> ```plaintext
> {"name":"admin-webtop","connected":true}
> {"name":"kunde-a-router","connected":true}
> …
> ```

## Datenschutz und Nachweise

Selbst gehostet in einem deutschen Rechenzentrum bleiben Peers, Schlüssel und Regeln bei Ihnen. Der Fernzugriff ist trotzdem Auftragsverarbeitung: Schließen Sie mit jedem Kunden einen Auftragsverarbeitungsvertrag nach [Art. 28 DSGVO](https://eur-lex.europa.eu/legal-content/DE/TXT/?uri=CELEX:32016R0679) und beschreiben Sie dort den Zugang über NetBird. Das Audit-Log der freien Version hält Änderungen fest, im Test etwa `Peer added` und `Policy added` mit Zeitstempel, bei Änderungen per API mit Benutzer. Verbindungen zeigen erst die Traffic Events, laut Doku nur in der Cloud und in lizenzierten Enterprise-Installationen. Führen Sie daher ein eigenes Zugriffsprotokoll (Kunde, Zeit, Anlass); das ist keine Rechtsberatung.

## Typische Fehler

**`transport: authentication handshake failed: remote error: tls: no application protocol`**

HTTP/2 ist im Proxy-Host nicht eingeschaltet. Der Client meldet dann `Management: Disconnected`. Im Reiter SSL **HTTP/2 Support** aktivieren.

**`TrustedPeers contains the default route 0.0.0.0/0, which trusts X-Forwarded-For from every client and allows connection IP spoofing.`**

`trustedPeers` fehlt in der `config.yaml`. Beide Listen auf das NPM-Netz setzen und `docker compose restart netbird-server`. Ältere Warnungen bleiben im Log stehen, prüfen Sie mit `docker compose logs --since 1m netbird-server`.

**`couldn't add peer: setup key is invalid`**

Der Setup-Key ist falsch, abgelaufen oder gelöscht. Neuen Key anlegen und in die `.env` eintragen.

**Webtop zeigt nur noch die Schnittstelle `lo`**

Der NetBird-Container wurde ohne Compose neu gestartet, etwa mit `docker restart`. Der Webtop hängt dann am alten Netz. `docker compose restart webtop` behebt es.

**NPM liefert nach dem Restore 404 auf `/oauth2`**

Nginx im NPM hat noch die alte Adresse des NetBird-Containers. NPM neu starten, ebenso nach einem Update.

## Häufige Fragen

### Muss ich beim Kunden Ports freigeben?

Nein. Der Routing-Peer verbindet sich von innen, ohne direkte Verbindung laufen die Daten über das Relay `/relay` auf Ihrem Server.

## Fazit

Ein NetBird-Server hinter NPM und ein Webtop machen aus vielen Kundenzugängen einen, mit einem Skript je neuem Kunden. Die Stolperstellen sind HTTP/2 im NPM und die Vertrauensliste in der `config.yaml`, gleiche Adressbereiche lösen `netbird networks select` oder NetBird direkt auf dem Gerät.

## Weiterführende Anleitungen und Quellen

- [NetBird mit Docker installieren: Zero-Trust-Overlay-Netzwerk selbst hosten](https://s-edv.com/anleitungen/netbird-docker-zero-trust-netzwerk)
- [Docker und Docker Compose auf Linux installieren](https://s-edv.com/anleitungen/docker-compose-linux-selfhosting-grundlage)
- [Docker Compose absichern: Secrets, Healthchecks, Non-Root](https://s-edv.com/anleitungen/docker-compose-absichern-secrets-healthchecks-non-root)
- [NetBird: Self-Hosting Quickstart](https://docs.netbird.io/selfhosted/selfhosted-quickstart)
- [NetBird: External Reverse Proxy](https://docs.netbird.io/selfhosted/external-reverse-proxy)
- [NetBird: Backup](https://docs.netbird.io/selfhosted/maintenance/backup)
- [NetBird: Overlapping Routes](https://docs.netbird.io/manage/network-routes/overlapping-routes)
- [NetBird: Overlapping IPs for Resources](https://docs.netbird.io/use-cases/remote-access/overlapping-ips-for-resources)
- [NetBird v0.83.0 Release Notes](https://github.com/netbirdio/netbird/releases/tag/v0.83.0)
- [linuxserver.io: Webtop](https://docs.linuxserver.io/images/docker-webtop/)
