From c8fef443f34b787a378c83e87d50aa7b70a6ec82 Mon Sep 17 00:00:00 2001 From: Brandon Hopkins Date: Mon, 4 May 2026 21:42:44 -0700 Subject: [PATCH 1/2] Prepare the management server for cross-host proxies --- .../scaling/multiple-proxy-instances.mdx | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx b/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx index 5f40956e..449734e8 100644 --- a/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx +++ b/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx @@ -40,6 +40,37 @@ Before deploying additional proxy instances, make sure you have: - The ability to update DNS records to point to multiple servers - Access to the management server CLI to generate proxy tokens +### Prepare the management server for cross-host proxies + +Additional proxy instances run on different hosts than the management server, so they cannot reach the management container over the Docker network. Each remote proxy connects to management via gRPC over public TLS (`https://netbird.example.com:443`), which means the management server's Traefik must be configured to route the gRPC streams used by the proxy. + +If you have not already done so as part of [Enable Reverse Proxy](/selfhosted/migration/enable-reverse-proxy#connecting-through-traefik-instead-of-docker-network), update the management server now: + +1. **Add the `ProxyService` gRPC path** to the existing gRPC router rule on the management host. In a standard deployment this is the `traefik.http.routers.netbird-grpc` label: + + ``` + traefik.http.routers.netbird-grpc.rule=Host(`netbird.example.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`)) + ``` + + Without `/management.ProxyService/` in this rule, Traefik falls back to the dashboard router and returns the dashboard HTML. The proxy logs this as `code = Unimplemented ... 404 (Not Found); transport: received unexpected content-type "text/html"`. + +2. **Disable the Traefik idle timeout** on the `websecure` entrypoint so long-lived gRPC streams between the proxy and management server are not cut off: + + ```yaml + # docker-compose.yml on the management host + services: + traefik: + command: + # ...existing args... + - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=0" + ``` + +3. Restart Traefik on the management host: `docker compose up -d traefik`. + + +Both changes are made on the **management server**, not on the proxy hosts. Skipping them is the most common cause of `management connection failed ... 404 (Not Found)` errors when adding a remote proxy instance. + + ## Token management Each proxy instance authenticates with the management server using an access token. You can either generate a unique token per instance or share a single token across instances. @@ -114,7 +145,7 @@ Create a `docker-compose.yml` on the new server: ```yaml services: traefik: - image: traefik:v3.4 + image: traefik:v3.6 container_name: traefik restart: unless-stopped command: From 7579d6475462888ace5ce59f83c35969f8c75d2c Mon Sep 17 00:00:00 2001 From: TechHutTV Date: Fri, 8 May 2026 08:46:12 -0700 Subject: [PATCH 2/2] Add warning and minor mdx fix --- src/components/mdx.jsx | 6 +++--- .../maintenance/scaling/multiple-proxy-instances.mdx | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/mdx.jsx b/src/components/mdx.jsx index 3728c9ba..4172ce6d 100644 --- a/src/components/mdx.jsx +++ b/src/components/mdx.jsx @@ -83,7 +83,7 @@ export function Note({ children }) { return (
-
+
{children}
@@ -94,7 +94,7 @@ export function Warning({ children }) { return (
-
+
{children}
@@ -105,7 +105,7 @@ export function Success({ children }) { return (
-
+
{children}
diff --git a/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx b/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx index 449734e8..7ebbec47 100644 --- a/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx +++ b/src/pages/selfhosted/maintenance/scaling/multiple-proxy-instances.mdx @@ -52,6 +52,17 @@ If you have not already done so as part of [Enable Reverse Proxy](/selfhosted/mi traefik.http.routers.netbird-grpc.rule=Host(`netbird.example.com`) && (PathPrefix(`/signalexchange.SignalExchange/`) || PathPrefix(`/management.ManagementService/`) || PathPrefix(`/management.ProxyService/`)) ``` + + Replace the entire rule — don't just append the new `ProxyService` clause to the end of your existing line. The original rule closes its path group with `))`, so pasting `|| PathPrefix(...)` after it pushes the new path *outside* the OR group: + + ``` + ...ManagementService/`)) || PathPrefix(`/management.ProxyService/`)) + ^ this `)` ends the OR group too early + ``` + + The symptom is the same `transport: received unexpected content-type "text/html"` error, which makes it easy to think the fix didn't work. The `ProxyService` path must sit inside the same parentheses as the other two paths. + + Without `/management.ProxyService/` in this rule, Traefik falls back to the dashboard router and returns the dashboard HTML. The proxy logs this as `code = Unimplemented ... 404 (Not Found); transport: received unexpected content-type "text/html"`. 2. **Disable the Traefik idle timeout** on the `websecure` entrypoint so long-lived gRPC streams between the proxy and management server are not cut off: