Skip to content

Commit 4110ffe

Browse files
shuuri-labsSunsetDrifterclaude
authored
Fix reverse proxy docs: add ProxyService gRPC route, fix container co… (#623)
* Fix reverse proxy docs: add ProxyService gRPC route, fix container commands, support both setups - add missing /management.ProxyService/ gRPC route to all reverse proxy config templates (traefik, nginx, caddy, NPM) in reverse-proxy.mdx - change default proxy -> management connection to use direct docker network instead of routing through traefik, avoiding hairpin NAT and missing gRPC route issues - add "Connecting through Traefik" section for separatevhost deployments - fix token CLI commands: use /go/bin/ prefix (not on container PATH), add --config flag for combined container - ratify instructions for enabling reverse proxy both combined (netbird-server) and multi-container (management) setups * remove unecessary proxy endpoints from reverse proxy templates other than traefik in reverse-proxy.mdx * - standardize usage of 'docker exec' as opposed to 'docker compose exec + service name' in instructions - added AuthClientID config instructions - added traefik grpc rule to configuration file explanation page - idletimeout for reverse proxy migration is now 0, matching getting-started.sh * add clarification on grpc ProxyService path for traffic - only required if the proxy service is on a different docker network to traefik * fix: correct step count in Traefik connection section from two to three Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Jack Carter <128555021+SunsetDrifter@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8936e91 commit 4110ffe

File tree

5 files changed

+179
-35
lines changed

5 files changed

+179
-35
lines changed

src/pages/selfhosted/configuration-files.mdx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ netbird-server:
143143
labels:
144144
- traefik.enable=true
145145
# gRPC router (needs h2c backend for HTTP/2 cleartext)
146-
- traefik.http.routers.netbird-grpc.rule=Host(`netbird.example.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`))
146+
# The /management.ProxyService/ path is only required if the reverse proxy
147+
# container (netbirdio/reverse-proxy) connects through Traefik — i.e., it
148+
# runs on a separate host or a different Docker network. If the proxy is on
149+
# the same Docker network as netbird-server, it connects directly and this
150+
# path prefix can be omitted.
151+
- traefik.http.routers.netbird-grpc.rule=Host(`netbird.example.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`))
147152
- traefik.http.routers.netbird-grpc.entrypoints=websecure
148153
- traefik.http.routers.netbird-grpc.tls=true
149154
- traefik.http.routers.netbird-grpc.tls.certresolver=letsencrypt
@@ -192,7 +197,7 @@ When using the built-in Traefik, the `netbird-server` service uses two routers t
192197

193198
| Router | Path Prefixes | Backend Service | Purpose |
194199
|--------|---------------|-----------------|---------|
195-
| `netbird-grpc` | `/signalexchange.SignalExchange/`, `/management.ManagementService/` | `netbird-server-h2c` (h2c scheme) | gRPC traffic for signal exchange and management API. Uses HTTP/2 cleartext (h2c) backend because gRPC requires HTTP/2. |
200+
| `netbird-grpc` | `/signalexchange.SignalExchange/`, `/management.ManagementService/`, `/management.ProxyService/` | `netbird-server-h2c` (h2c scheme) | gRPC traffic for signal exchange, management API, and the [Reverse Proxy feature](/manage/reverse-proxy). Uses HTTP/2 cleartext (h2c) backend because gRPC requires HTTP/2. The `/management.ProxyService/` path is only needed if the reverse proxy container connects through Traefik (see comment in the snippet above). |
196201
| `netbird-backend` | `/relay`, `/ws-proxy/`, `/api`, `/oauth2` | `netbird-server` (http scheme) | HTTP traffic for relay connections, WebSocket proxying, REST API, and OAuth2/OIDC endpoints. |
197202

198203
The dashboard router has `priority=1` (lowest), so it acts as a catch-all for requests that don't match the more specific server routes.

src/pages/selfhosted/migration/enable-reverse-proxy.mdx

Lines changed: 164 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,27 @@ If your current deployment uses a reverse proxy other than Traefik, you'll need
2929

3030
### What stays the same
3131

32-
- Your existing `netbird-server`, `dashboard`, `signal`, and `relay` services are unchanged
33-
- Your `management.json` and other configuration files require no modifications **unless** you use an external identity provider (not the embedded IdP). See [Configure SSO for external identity providers](#configure-sso-for-external-identity-providers) below.
32+
- Your existing NetBird services are unchanged
33+
- Your configuration files (`config.yaml` for combined setup, `management.json` for multi-container setup) require no modifications - **unless** you use an external identity provider (not the embedded IdP). See [Configure SSO for external identity providers](#configure-sso-for-external-identity-providers) below.
3434
- Existing peers, networks, and access policies are unaffected
3535

3636
## Prerequisites
3737

3838
Before starting, ensure you have:
3939

4040
- **Traefik** as your reverse proxy (see [Why Traefik is required](#why-traefik-is-required) above)
41-
- **Latest `netbird-server` image** - pull the latest version to ensure the management CLI supports token creation
41+
- **Latest NetBird images** - pull the latest version to ensure the management server and CLI support the reverse proxy feature and token creation
4242
- **A domain for the proxy** - e.g., `proxy.example.com`. Service subdomains will be created under this domain (e.g., `myapp.proxy.example.com`)
4343
- **Wildcard DNS capability** - ability to create a `*.proxy.example.com` DNS record pointing to your server
44+
- **Port 443 accessible** - the proxy needs this for ACME TLS-ALPN-01 challenges (certificate provisioning)
45+
46+
<Note>
47+
This guide covers both the **combined container** setup (`netbirdio/netbird-server`, the default for new deployments) and the **multi-container** setup (separate `management`, `signal`, and `relay` images). Where commands or configuration differ between the two setups, both variants are shown.
48+
</Note>
4449

4550
<Warning>
4651
The proxy domain **must not** be a subdomain of your NetBird management domain. For example, if your management server is at `netbird.example.com`, do not use `proxy.netbird.example.com`. Use a separate subdomain like `proxy.example.com` instead. Using a subdomain of the management domain causes TLS and routing conflicts between the proxy and management services.
4752
</Warning>
48-
- **Port 443 accessible** - the proxy needs this for ACME TLS-ALPN-01 challenges (certificate provisioning)
4953

5054
## Migration steps
5155

@@ -58,44 +62,60 @@ cd netbird-backup-$(date +%Y%m%d)
5862

5963
# Backup configuration files
6064
cp ../docker-compose.yml .
61-
cp ../management.json . 2>/dev/null
65+
cp ../config.yaml . 2>/dev/null # Combined container setup
66+
cp ../management.json . 2>/dev/null # Multi-container setup
6267
cp ../*.env . 2>/dev/null || echo "No .env files found"
6368
```
6469

6570
### Step 2: Generate a proxy access token
6671

67-
The proxy authenticates with the management server using an access token. Generate one using the management CLI:
72+
The proxy authenticates with the management server using an access token. Generate one using the server CLI.
73+
74+
**Combined container** (`netbirdio/netbird-server`):
75+
76+
```bash
77+
docker exec -it netbird-server /go/bin/netbird-server token create \
78+
--name "my-proxy" --config <netbird-data-dir>/config.yaml
79+
```
80+
81+
**Multi-container** (separate `netbirdio/management` image):
6882

6983
```bash
70-
docker exec -it netbird-server netbird-mgmt token create --name "my-proxy"
84+
docker exec -it netbird-management /go/bin/netbird-mgmt token create --name "my-proxy"
7185
```
7286

7387
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.
7488

7589
You can manage tokens later with:
7690

7791
```bash
78-
# List all tokens
79-
docker exec -it netbird-server netbird-mgmt token list
92+
# List all tokens (combined container)
93+
docker exec -it netbird-server /go/bin/netbird-server token list \
94+
--config <netbird-data-dir>/config.yaml
95+
96+
# List all tokens (multi-container)
97+
docker exec -it netbird-management /go/bin/netbird-mgmt token list
98+
99+
# Revoke a token by ID (combined container)
100+
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
101+
--config <netbird-data-dir>/config.yaml
80102

81-
# Revoke a token by ID
82-
docker exec -it netbird-server netbird-mgmt token revoke <token-id>
103+
# Revoke a token by ID (multi-container)
104+
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>
83105
```
84106

85107
### Step 3: Add the proxy service to docker-compose.yml
86108

87-
Add the following service to your `docker-compose.yml`. Replace the placeholder values with your actual domains:
109+
Add the following service to your `docker-compose.yml`. Adjust the `depends_on` value to match your management service name:
88110

89111
```yaml
90112
proxy:
91113
image: netbirdio/reverse-proxy:latest
92114
container_name: netbird-proxy
93-
extra_hosts:
94-
- "netbird.example.com:172.30.0.10"
95115
restart: unless-stopped
96116
networks: [netbird]
97117
depends_on:
98-
- netbird-server
118+
- netbird-server # Use "management" for multi-container setup
99119
env_file:
100120
- ./proxy.env
101121
volumes:
@@ -123,21 +143,41 @@ volumes:
123143
netbird_proxy_certs:
124144
```
125145

126-
Replace `netbird.example.com` in the `extra_hosts` entry with your actual NetBird management domain. This hairpin NAT fix ensures the proxy can reach Traefik's static IP within the Docker network.
146+
Then create a `proxy.env` file with the proxy configuration.
127147

128-
Then create a `proxy.env` file with the proxy configuration:
148+
**Combined container** (`netbirdio/netbird-server`):
129149

130150
```bash
131151
NB_PROXY_DOMAIN=proxy.example.com
132152
NB_PROXY_TOKEN=nbx_your_token_here
133-
NB_PROXY_MANAGEMENT_ADDRESS=https://netbird.example.com:443
153+
NB_PROXY_MANAGEMENT_ADDRESS=http://netbird-server:80
154+
NB_PROXY_ALLOW_INSECURE=true
155+
NB_PROXY_ADDRESS=:8443
156+
NB_PROXY_ACME_CERTIFICATES=true
157+
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
158+
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
159+
```
160+
161+
**Multi-container** (separate `netbirdio/management` image):
162+
163+
```bash
164+
NB_PROXY_DOMAIN=proxy.example.com
165+
NB_PROXY_TOKEN=nbx_your_token_here
166+
NB_PROXY_MANAGEMENT_ADDRESS=http://management:33073
167+
NB_PROXY_ALLOW_INSECURE=true
134168
NB_PROXY_ADDRESS=:8443
135169
NB_PROXY_ACME_CERTIFICATES=true
136170
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
137171
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
138172
```
139173

140-
Replace `proxy.example.com` with your proxy domain and `netbird.example.com` with your management domain.
174+
Replace `proxy.example.com` with your proxy domain. The `NB_PROXY_MANAGEMENT_ADDRESS` must match the Docker Compose service name and port of your management server - `netbird-server:80` for the combined container, or `management:33073` for the multi-container Traefik setup.
175+
176+
<Note>
177+
The proxy connects to the management server directly over the Docker network rather than through Traefik. This avoids the need to route the `/management.ProxyService/` gRPC service through your reverse proxy and eliminates hairpin NAT issues. The `NB_PROXY_ALLOW_INSECURE=true` setting is safe here because the traffic never leaves the Docker network.
178+
179+
If your proxy and management server run on **separate hosts** and cannot communicate over a shared Docker network, see [Connecting through Traefik](#connecting-through-traefik-instead-of-docker-network) below.
180+
</Note>
141181

142182
The Traefik labels configure a **TCP router** that:
143183
- Catches any request not matched by higher-priority HTTP routers via `HostSNI(*)` (wildcard)
@@ -152,7 +192,7 @@ The `HostSNI(*)` rule acts as a catch-all for any domain not matched by the exis
152192

153193
### Step 4: Set up DNS records
154194

155-
Create two DNS records pointing to the server running your NetBird stack one for the base proxy domain and one wildcard for service subdomains:
195+
Create two DNS records pointing to the server running your NetBird stack - one for the base proxy domain and one wildcard for service subdomains:
156196

157197
| Type | Name | Content |
158198
|------|------|---------|
@@ -194,7 +234,7 @@ If the domain appears, the proxy is connected and ready. You can now [create you
194234

195235
### Who this applies to
196236

197-
This section applies to deployments using a **standalone external identity provider** (Auth0, Okta, Keycloak, Zitadel, etc.) instead of the built-in embedded IdP (Dex). If you deployed using the quickstart script with default settings, you are using the embedded IdP and can skip this section.
237+
This section applies to **multi-container** deployments using a **standalone external identity provider** (Auth0, Okta, Keycloak, Zitadel, etc.) instead of the built-in embedded IdP (Dex). If you are running the combined container or deployed using the quickstart script with default settings, you are using the embedded IdP and can skip this section - the callback is registered automatically.
198238

199239
### Why this is needed
200240

@@ -206,16 +246,17 @@ If you want to keep using your current external identity provider, follow these
206246

207247
#### Step 1: Add callback URL to management.json
208248

209-
Add the `AuthCallbackURL` field to the `HttpConfig` section of your `management.json`:
249+
Add the `AuthCallbackURL` and `AuthClientID` fields to the `HttpConfig` section of your `management.json`:
210250

211251
```json
212252
"HttpConfig": {
213253
...existing fields...,
254+
"AuthClientID": "<your-auth-client-id>",
214255
"AuthCallbackURL": "https://<your-management-domain>/api/reverse-proxy/callback"
215256
}
216257
```
217258

218-
Replace `<your-management-domain>` with your NetBird management server domain (the same domain used for the dashboard).
259+
Replace `<your-management-domain>` with your NetBird management server domain (the same domain used for the dashboard). Replace `<your-auth-client-id>` with the OAuth2 client ID from your identity provider (the same client ID used for the dashboard application).
219260

220261
#### Step 2: Register callback in your IdP
221262

@@ -242,12 +283,12 @@ Where to find this setting in common providers:
242283
Restart the management service to pick up the configuration change:
243284

244285
```bash
245-
docker compose restart netbird-management
286+
docker compose restart management
246287
```
247288

248289
### Option B: Migrate to the embedded IdP (recommended)
249290

250-
The embedded IdP (Dex) handles the reverse proxy callback registration automatically no manual configuration needed. If you want a simpler setup, consider migrating to the embedded IdP.
291+
The embedded IdP (Dex) handles the reverse proxy callback registration automatically - no manual configuration needed. If you want a simpler setup, consider migrating to the embedded IdP.
251292

252293
With the embedded IdP, external identity providers can still be used as **connectors** alongside local authentication. This means your users can continue to sign in with their existing accounts (Google, Okta, Keycloak, etc.) while the embedded IdP manages the OIDC layer.
253294

@@ -268,6 +309,94 @@ After configuring SSO for your external identity provider, verify it works:
268309

269310
If the redirect fails or you see an error from your IdP, double-check that the callback URL is correctly registered in both `management.json` and your identity provider's settings.
270311

312+
## Connecting through Traefik instead of Docker network
313+
314+
If your proxy container cannot reach the management container directly - for example, if they run on **separate hosts** - you can route the proxy's management connection through Traefik instead. This requires three additional configuration steps.
315+
316+
### 1. Add the ProxyService gRPC route
317+
318+
The proxy communicates with the management server over two gRPC services: `ManagementService` and `ProxyService`. Both paths must be routed through Traefik. Find the existing gRPC router label in your `docker-compose.yml` - in a standard deployment this is `traefik.http.routers.netbird-grpc` - and add the `ProxyService` path prefix:
319+
320+
```
321+
traefik.http.routers.netbird-grpc.rule=Host(`netbird.example.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`))
322+
```
323+
324+
Without the `/management.ProxyService/` route, the proxy will fail to register with the management server.
325+
326+
### 2. Fix the Traefik container IP for hairpin NAT
327+
328+
When the proxy connects to your management domain through Traefik, the DNS resolution inside the Docker network must point to the Traefik container. This is done via an `extra_hosts` entry in the proxy service, but the IP address must match Traefik's actual IP.
329+
330+
To ensure a stable IP, assign a static IP to the Traefik container within your Docker network:
331+
332+
```yaml
333+
# In your docker-compose.yml
334+
335+
networks:
336+
netbird:
337+
driver: bridge
338+
ipam:
339+
config:
340+
- subnet: 172.30.0.0/24
341+
gateway: 172.30.0.1
342+
343+
services:
344+
traefik:
345+
# ...existing traefik config...
346+
networks:
347+
netbird:
348+
ipv4_address: 172.30.0.10
349+
```
350+
351+
Then add the `extra_hosts` entry to the proxy service referencing that IP:
352+
353+
```yaml
354+
proxy:
355+
# ...existing proxy config...
356+
extra_hosts:
357+
- "netbird.example.com:172.30.0.10"
358+
```
359+
360+
Replace `netbird.example.com` with your actual management domain.
361+
362+
### 3. Increase Traefik's idle timeout for gRPC
363+
364+
Traefik's default idle timeout (180 seconds) is too short for the long-lived gRPC streams used between the proxy and management server. Without increasing it, the proxy will report connection timeout errors and the dashboard may show the proxy agent as offline.
365+
366+
Add the following to your Traefik static configuration:
367+
368+
```yaml
369+
# In traefik.yml
370+
entryPoints:
371+
websecure:
372+
address: ":443"
373+
transport:
374+
respondingTimeouts:
375+
idleTimeout: "0"
376+
```
377+
378+
Or as a command-line argument:
379+
380+
```yaml
381+
# In docker-compose.yml
382+
services:
383+
traefik:
384+
command:
385+
# ...existing args...
386+
- "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0"
387+
```
388+
389+
Finally, update `proxy.env` to connect through Traefik and remove `NB_PROXY_ALLOW_INSECURE`:
390+
391+
```bash
392+
NB_PROXY_MANAGEMENT_ADDRESS=https://netbird.example.com:443
393+
# Do NOT set NB_PROXY_ALLOW_INSECURE when connecting over TLS through Traefik
394+
```
395+
396+
<Warning>
397+
If you do not assign a static IP to Traefik, Docker may assign a different IP on container restart, and the `extra_hosts` entry will silently point to the wrong address. Always configure a fixed subnet and static IP when using this approach.
398+
</Warning>
399+
271400
## For users not on Traefik
272401

273402
If your self-hosted deployment currently uses Nginx, Caddy, or another reverse proxy, you'll need to switch to Traefik before enabling the Reverse Proxy feature. See the [Traefik setup instructions](/selfhosted/reverse-proxy#traefik) for a step-by-step guide on configuring Traefik for your NetBird deployment.
@@ -276,7 +405,7 @@ If your self-hosted deployment currently uses Nginx, Caddy, or another reverse p
276405

277406
| Variable | Required | Description | Default |
278407
|----------|----------|-------------|---------|
279-
| `NB_PROXY_TOKEN` | Yes | Access token generated via `netbird-mgmt token create`. The proxy refuses to start without it. | - |
408+
| `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. | - |
280409
| `NB_PROXY_DOMAIN` | Yes | Base domain for this proxy instance (e.g., `proxy.example.com`). Determines the domain available for services. | - |
281410
| `NB_PROXY_MANAGEMENT_ADDRESS` | No | URL of your NetBird management server. The proxy connects via gRPC to register itself. | `https://api.netbird.io:443` |
282411
| `NB_PROXY_ADDRESS` | No | Address the proxy listens on. | `:8443` (Docker), `:443` (binary) |
@@ -342,8 +471,15 @@ docker compose up -d
342471
You can also revoke the proxy token to prevent the proxy from reconnecting:
343472

344473
```bash
345-
docker exec -it netbird-server netbird-mgmt token list
346-
docker exec -it netbird-server netbird-mgmt token revoke <token-id>
474+
# Combined container
475+
docker exec -it netbird-server /go/bin/netbird-server token list \
476+
--config <netbird-data-dir>/config.yaml
477+
docker exec -it netbird-server /go/bin/netbird-server token revoke <token-id> \
478+
--config <netbird-data-dir>/config.yaml
479+
480+
# Multi-container
481+
docker exec -it netbird-management /go/bin/netbird-mgmt token list
482+
docker exec -it netbird-management /go/bin/netbird-mgmt token revoke <token-id>
347483
```
348484

349485
## Additional resources

src/pages/selfhosted/reverse-proxy.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ All reverse proxy configurations must route the following endpoints:
4646
| `/api/*` | HTTP | management:80 | REST API |
4747
| `/ws-proxy/management*` | WebSocket | management:80 | WebSocket upgrade required |
4848
| `/management.ManagementService/*` | gRPC | management:80 | HTTP/2 (h2c) required |
49+
| `/management.ProxyService/*` | gRPC | management:80 | HTTP/2 (h2c) required. Only needed if using the [Reverse Proxy feature](/manage/reverse-proxy). |
4950
| `/oauth2/*` | HTTP | management:80 | Embedded IdP |
5051
| `/*` | HTTP | dashboard:80 | Catch-all for dashboard |
5152

@@ -184,7 +185,7 @@ services:
184185
- traefik.http.routers.netbird-mgmt-ws.service=netbird-mgmt-ws
185186
- traefik.http.services.netbird-mgmt-ws.loadbalancer.server.port=80
186187
# Management gRPC router
187-
- traefik.http.routers.netbird-mgmt-grpc.rule=Host(`netbird.example.com`) && PathPrefix(`/management.ManagementService/`)
188+
- traefik.http.routers.netbird-mgmt-grpc.rule=Host(`netbird.example.com`) && (PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`))
188189
- traefik.http.routers.netbird-mgmt-grpc.entrypoints=websecure
189190
- traefik.http.routers.netbird-mgmt-grpc.tls=true
190191
- traefik.http.routers.netbird-mgmt-grpc.tls.certresolver=letsencrypt
@@ -817,6 +818,7 @@ location /management.ManagementService/ {
817818
grpc_send_timeout 1d;
818819
grpc_socket_keepalive on;
819820
}
821+
820822
```
821823
822824
#### NPM running on host (not in Docker)
@@ -912,6 +914,7 @@ location /management.ManagementService/ {
912914
grpc_send_timeout 1d;
913915
grpc_socket_keepalive on;
914916
}
917+
915918
```
916919
917920
---

0 commit comments

Comments
 (0)