From d74dc4649bb35b812ea55fd9042cb3c82842cd7a Mon Sep 17 00:00:00 2001
From: Samuel Wilk <34423885+da-wilky@users.noreply.github.com>
Date: Tue, 17 Feb 2026 10:32:30 +0100
Subject: [PATCH 1/5] Create new guide to setup caddy with l4
---
.../migration/caddy-netbird-proxy-setup.mdx | 158 ++++++++++++++++++
1 file changed, 158 insertions(+)
create mode 100644 src/pages/selfhosted/migration/caddy-netbird-proxy-setup.mdx
diff --git a/src/pages/selfhosted/migration/caddy-netbird-proxy-setup.mdx b/src/pages/selfhosted/migration/caddy-netbird-proxy-setup.mdx
new file mode 100644
index 00000000..605d9221
--- /dev/null
+++ b/src/pages/selfhosted/migration/caddy-netbird-proxy-setup.mdx
@@ -0,0 +1,158 @@
+# Migration Guide: Setup Caddy with L4 to support the netbird proxy feature
+
+This guide walks you through adding or modifying Caddy as external reverse proxy inside docker while supporting the netbird proxy feature. By the end, your Caddy reverse proxy will correctly forward the requests to the netbird proxy without TLS termination.
+
+
+This setup is **not** production ready!
+
+It uses a caddy plugin that is still under development. Use with caution.
+
+
+## Docker Compose Setup for Caddy
+
+This guide covers the basics about the setup of caddy using docker compose with a focus on the modifications needed to run it with TLS passthrough.
+
+### Dockerfile
+
+Caddy on default does not support TLS passthrough. There is a [caddy plugin](https://github.com/mholt/caddy-l4) that makes this functionality work. We need to create our own caddy docker image with this plugin installed. For this create the following `Dockerfile`:
+
+```dockerfile
+ARG VERSION=2
+
+FROM caddy:${VERSION}-builder AS builder
+RUN xcaddy build --with github.com/mholt/caddy-l4
+
+FROM caddy:${VERSION}-alpine
+COPY --from=builder /usr/bin/caddy /usr/bin/caddy
+```
+
+This will build the docker image with the caddy plugin installed.
+
+### Docker Compose
+
+The build process of the image and the caddy management can be done using docker compose. Use the following `docker-compose.yml`:
+
+```yaml
+name: caddy
+
+services:
+ caddy:
+ image: local-caddy-l4
+ build:
+ context: .
+ dockerfile: Dockerfile
+ args:
+ VERSION: 2
+ restart: unless-stopped
+ ports:
+ - 80:80
+ - 443:443
+ volumes:
+ - ./caddy:/etc/caddy
+ - ./data:/data
+ - ./config:/config
+ networks:
+ caddy:
+ ipv4_address: 172.30.0.10 # Define your IPv4 here, that you might reference inside the netbird proxy setup, the IP needs to be inside the subnet of the caddy network
+
+networks:
+ caddy:
+ driver: bridge
+ ipam:
+ config:
+ - subnet: 172.30.0.0/24
+```
+
+Alternativly you can use `docker network create caddy --subnet 172.30.0.0/24` to create the network externally and instead use the following
+
+```yaml
+networks:
+ caddy:
+ external: true
+```
+
+This way this docker-compose doesnt need to be up for the caddy network to be created. Else other services, that depend on the caddy network wont start if it is not initialized.
+
+
+### Caddy Setup
+
+Create a folder called `caddy` and inside create the `Caddyfile`:
+
+```nginx
+# This section needs to be first inside the Caddyfile and cannot be moved!
+{
+ admin off # Optional, disables the admin web ui (recommended if not used)
+ servers {
+ listener_wrappers {
+ layer4 { # This section passes the TLS directly to the container for the specified domain (and wildcard subdomain)
+ @proxy-exact tls sni proxy.domain.com
+ route @proxy-exact {
+ proxy netbird-proxy:8443
+ }
+ @proxy-wild tls sni_regexp ^[^.]+\.proxy\.domain\.com$
+ route @proxy-wild {
+ proxy netbird-proxy:8443
+ }
+ }
+ tls
+ }
+ }
+}
+
+netbird.domain.com {
+ # ws-proxy signal
+ handle /ws-proxy/signal* {
+ reverse_proxy netbird-signal:80
+ }
+
+ # ws-proxy management
+ handle /ws-proxy/management* {
+ reverse_proxy netbird-management:33073
+ }
+
+ # SignalExchange (gRPC)
+ handle /signalexchange.SignalExchange/* {
+ reverse_proxy h2c://netbird-signal:10000
+ }
+
+ # Relay
+ handle /relay* {
+ reverse_proxy netbird-relay:33080
+ }
+
+ # API
+ handle /api* {
+ reverse_proxy netbird-management:33073
+ }
+
+ # Management gRPC
+ handle /management.ManagementService/* {
+ reverse_proxy h2c://netbird-management:33073
+ }
+
+ # Proxy gRPC -> DONT FORGET TO ADD THIS
+ handle /management.ProxyService/* {
+ reverse_proxy h2c://netbird-management:33073
+ }
+
+ # Dashboard
+ handle {
+ reverse_proxy netbird-dashboard:80
+ }
+}
+
+# ... additional hosts
+```
+
+### Start Caddy
+
+Your folder structure for caddy should look like this:
+```
+caddy-docker
+| caddy
+| | Caddyfile
+| docker-compose.yml
+| Dockerfile
+```
+
+After all these configurations navigate into the `caddy-docker` folder. Run the following command `docker compose up -d --build`. This will build the caddy docker image and start it.
\ No newline at end of file
From 4dd1e8792321b7af2504ba24d6589c29bb8c73b8 Mon Sep 17 00:00:00 2001
From: Samuel Wilk <34423885+da-wilky@users.noreply.github.com>
Date: Tue, 17 Feb 2026 10:33:10 +0100
Subject: [PATCH 2/5] Add references
---
src/pages/manage/reverse-proxy/index.mdx | 3 +++
src/pages/selfhosted/reverse-proxy.mdx | 2 ++
2 files changed, 5 insertions(+)
diff --git a/src/pages/manage/reverse-proxy/index.mdx b/src/pages/manage/reverse-proxy/index.mdx
index fab42641..7d4e57c2 100644
--- a/src/pages/manage/reverse-proxy/index.mdx
+++ b/src/pages/manage/reverse-proxy/index.mdx
@@ -13,6 +13,9 @@ NetBird Reverse Proxy lets you expose internal services running on peers or behi
**Self-hosted requirement:** Self-hosted deployments **must** use [Traefik](/selfhosted/reverse-proxy) as their reverse proxy. Traefik is the only supported reverse proxy that provides TLS passthrough, which is required for the Reverse Proxy feature to function correctly.
+
+ **Non-production workaround for caddy:** A selfhosted deployment using caddy is also possible but considered not production ready. You can find a setup-guide [here](/selfhosted/migration/caddy-netbird-proxy-setup).
+
## How it works
diff --git a/src/pages/selfhosted/reverse-proxy.mdx b/src/pages/selfhosted/reverse-proxy.mdx
index 6b918856..653e8b7c 100644
--- a/src/pages/selfhosted/reverse-proxy.mdx
+++ b/src/pages/selfhosted/reverse-proxy.mdx
@@ -677,6 +677,8 @@ netbird.example.com {
}
```
+To support the [Netbird Proxy feature](/manage/reverse-proxy) using caddy follow this [guide](/selfhosted/migration/caddy-netbird-proxy-setup). Be aware that it is **not** considered production ready.
+
#### Caddy running on host (not in Docker)
If Caddy is installed directly on the host, use localhost addresses:
From 019ae842037499b42a8ee9c18c0d22d5bba8fe87 Mon Sep 17 00:00:00 2001
From: Samuel Wilk <34423885+da-wilky@users.noreply.github.com>
Date: Tue, 17 Feb 2026 10:33:34 +0100
Subject: [PATCH 3/5] Modify the enable reverse proxy migration guide
---
.../migration/enable-reverse-proxy.mdx | 49 +++++++++++++++++--
1 file changed, 46 insertions(+), 3 deletions(-)
diff --git a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx
index 3788c9f7..c23dcf67 100644
--- a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx
+++ b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx
@@ -15,7 +15,11 @@ The NetBird proxy container manages its own TLS certificates (via Let's Encrypt
This capability is called **TLS passthrough**, and among common reverse proxies, **only Traefik supports it** via its TCP routers. Other reverse proxies (Nginx, Caddy, Nginx Proxy Manager) terminate TLS themselves and cannot forward the raw encrypted connection, which breaks the proxy's certificate management.
-If your current deployment uses a reverse proxy other than Traefik, you'll need to switch before enabling this feature. See [Switching to Traefik](/selfhosted/reverse-proxy#traefik) for instructions.
+If your current deployment uses a reverse proxy other than Traefik, you'll need to switch before enabling this feature for production environments. See [Switching to Traefik](/selfhosted/reverse-proxy#traefik) for instructions.
+
+
+This guide also covers using Caddy as reverse proxy, even tho this is not considered production ready.
+
## Overview of changes
@@ -86,6 +90,9 @@ docker exec -it netbird-server netbird-mgmt token revoke
Add the following service to your `docker-compose.yml`. Replace the placeholder values with your actual domains:
+
+
+Traefik-Setup:
```yaml
proxy:
image: netbirdio/reverse-proxy:latest
@@ -114,6 +121,39 @@ proxy:
max-size: "500m"
max-file: "2"
```
+
+
+Caddy-Setup: (not production ready)
+```yaml
+proxy:
+ image: netbirdio/reverse-proxy:latest
+ container_name: netbird-proxy
+ extra_hosts:
+ - "netbird.example.com:172.30.0.10"
+ restart: unless-stopped
+ networks: [netbird,caddy]
+ depends_on:
+ - netbird-server
+ env_file:
+ - ./proxy.env
+ volumes:
+ - netbird_proxy_certs:/certs
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "500m"
+ max-file: "2"
+
+# Also add the network
+networks:
+ # ...
+ caddy:
+ external: true
+```
+
+This setup expects the caddy network with the caddy-container already created and accessible.
+
+
Also add the `netbird_proxy_certs` volume to your `volumes:` section:
@@ -160,7 +200,10 @@ Create a wildcard DNS record pointing to the server running your NetBird stack:
This ensures that all service subdomains (e.g., `myapp.proxy.example.com`, `dashboard.proxy.example.com`) resolve to your server where Traefik forwards them to the proxy container.
-### Step 5: Apply changes
+### Step 5 (caddy only): Setup Caddy
+Follow the instructions from [Setup external Caddy for use with Netbird Reverse-Proxy feature](/selfhosted/migration/caddy-netbird-proxy-setup). Afterwards continue with step 6.
+
+### Step 6: Apply changes
```bash
# Pull the new image
@@ -178,7 +221,7 @@ docker compose logs -f proxy
You should see log messages indicating the proxy has connected to the management server and is ready to serve traffic.
-### Step 6: Verify in the dashboard
+### Step 7: Verify in the dashboard
Once the proxy connects to the management server:
From c1225bc0eb101ad52a9854a09e28048e4d0e9cde Mon Sep 17 00:00:00 2001
From: Samuel Wilk <34423885+da-wilky@users.noreply.github.com>
Date: Tue, 17 Feb 2026 10:33:39 +0100
Subject: [PATCH 4/5] Add to sidebar
---
src/components/NavigationDocs.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx
index 0dc688bd..e05564ca 100644
--- a/src/components/NavigationDocs.jsx
+++ b/src/components/NavigationDocs.jsx
@@ -389,6 +389,7 @@ export const docsNavigation = [
links: [
{ title: 'Coturn to Embedded STUN', href: '/selfhosted/migration/coturn-to-stun-migration' },
{ title: 'Enable Reverse Proxy', href: '/selfhosted/migration/enable-reverse-proxy' },
+ { title: 'Caddy Netbird Proxy Setup', href: '/selfhosted/migration/caddy-netbird-proxy-setup' },
]
},
],
From 3aca0de33df2aaa04aebfb74de264712ff0b5337 Mon Sep 17 00:00:00 2001
From: Samuel Wilk <34423885+da-wilky@users.noreply.github.com>
Date: Tue, 17 Feb 2026 11:59:01 +0100
Subject: [PATCH 5/5] Update src/components/NavigationDocs.jsx
Fix NetBird typo
Co-authored-by: Misha Bragin
---
src/components/NavigationDocs.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx
index e05564ca..c50502b3 100644
--- a/src/components/NavigationDocs.jsx
+++ b/src/components/NavigationDocs.jsx
@@ -389,7 +389,7 @@ export const docsNavigation = [
links: [
{ title: 'Coturn to Embedded STUN', href: '/selfhosted/migration/coturn-to-stun-migration' },
{ title: 'Enable Reverse Proxy', href: '/selfhosted/migration/enable-reverse-proxy' },
- { title: 'Caddy Netbird Proxy Setup', href: '/selfhosted/migration/caddy-netbird-proxy-setup' },
+ { title: 'Caddy NetBird Proxy Setup', href: '/selfhosted/migration/caddy-netbird-proxy-setup' },
]
},
],