Migration Guide: Enable Reverse Proxy Feature
This guide walks you through adding the NetBird Reverse Proxy to an existing self-hosted deployment. By the end, you'll have a netbird-proxy container running alongside your existing services, ready to expose internal applications to the public internet.
Who is this guide for? This migration guide is for existing self-hosted users who:
- Already have a working NetBird deployment (management server, dashboard, signal, relay)
- Want to enable the Reverse Proxy feature to expose internal services publicly
- Are running their services via Docker Compose
Why Traefik is required
The NetBird proxy container manages its own TLS certificates (via Let's Encrypt or static files). This means the reverse proxy sitting in front of it must not terminate TLS - it needs to pass raw TLS connections through to the proxy container untouched.
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 for instructions.
Overview of changes
What you're adding
proxyservice (container:netbird-proxy) - a new service in your Docker Compose stack that handles TLS termination, certificate provisioning, and traffic forwarding for reverse proxy servicesproxy.envfile - environment variables for the proxy container, including domain, token, and ACME configuration- Traefik TCP labels - routing rules that tell Traefik to pass TLS connections through to the proxy container
- Wildcard DNS record - so that all service subdomains (e.g.,
myapp.proxy.example.com) resolve to your server - Proxy access token - generated via the management CLI, used by the proxy to authenticate with the management server
What stays the same
- Your existing
netbird-server,dashboard,signal, andrelayservices are unchanged - Your
management.jsonand other configuration files require no modifications — unless you use an external identity provider (not the embedded IdP). See Configure SSO for external identity providers below. - Existing peers, networks, and access policies are unaffected
Prerequisites
Before starting, ensure you have:
- Traefik as your reverse proxy (see Why Traefik is required above)
- Latest
netbird-serverimage - pull the latest version to ensure the management CLI supports token creation - A domain for the proxy - e.g.,
proxy.example.com. Service subdomains will be created under this domain (e.g.,myapp.proxy.example.com) - Wildcard DNS capability - ability to create a
*.proxy.example.comDNS record pointing to your server
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.
- Port 443 accessible - the proxy needs this for ACME TLS-ALPN-01 challenges (certificate provisioning)
Migration steps
Step 1: Backup current configuration
# Create a backup directory
mkdir -p netbird-backup-$(date +%Y%m%d)
cd netbird-backup-$(date +%Y%m%d)
# Backup configuration files
cp ../docker-compose.yml .
cp ../management.json . 2>/dev/null
cp ../*.env . 2>/dev/null || echo "No .env files found"
Step 2: Generate a proxy access token
The proxy authenticates with the management server using an access token. Generate one using the management CLI:
docker exec -it netbird-server netbird-mgmt token create --name "my-proxy"
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.
You can manage tokens later with:
# List all tokens
docker exec -it netbird-server netbird-mgmt token list
# Revoke a token by ID
docker exec -it netbird-server netbird-mgmt token revoke <token-id>
Step 3: Add the proxy service to docker-compose.yml
Add the following service to your docker-compose.yml. Replace the placeholder values with your actual domains:
proxy:
image: netbirdio/reverse-proxy:latest
container_name: netbird-proxy
extra_hosts:
- "netbird.example.com:172.30.0.10"
restart: unless-stopped
networks: [netbird]
depends_on:
- netbird-server
env_file:
- ./proxy.env
volumes:
- netbird_proxy_certs:/certs
labels:
- traefik.enable=true
- traefik.tcp.routers.proxy-passthrough.entrypoints=websecure
- traefik.tcp.routers.proxy-passthrough.rule=HostSNI(`*`)
- traefik.tcp.routers.proxy-passthrough.tls.passthrough=true
- traefik.tcp.routers.proxy-passthrough.service=proxy-tls
- traefik.tcp.routers.proxy-passthrough.priority=1
- traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
Also add the netbird_proxy_certs volume to your volumes: section:
volumes:
# ...existing volumes...
netbird_proxy_certs:
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.
Then create a proxy.env file with the proxy configuration:
NB_PROXY_DOMAIN=proxy.example.com
NB_PROXY_TOKEN=nbx_your_token_here
NB_PROXY_MANAGEMENT_ADDRESS=https://netbird.example.com:443
NB_PROXY_ADDRESS=:8443
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
Replace proxy.example.com with your proxy domain and netbird.example.com with your management domain.
The Traefik labels configure a TCP router that:
- Catches any request not matched by higher-priority HTTP routers via
HostSNI(*)(wildcard) - Uses the
websecureentrypoint (port 443) - Passes the TLS connection through without termination (
tls.passthrough=true) - Uses
priority=1to avoid intercepting traffic meant for the main NetBird HTTP routers on the same entrypoint - Forwards traffic to the proxy container on port 8443
The HostSNI(*) rule acts as a catch-all for any domain not matched by the existing NetBird HTTP routers. The priority=1 ensures this TCP router only handles traffic that no other router claims. Any domain pointing to your server that isn't netbird.example.com will be forwarded to the proxy.
Step 4: Set up wildcard DNS
Create a wildcard DNS record pointing to the server running your NetBird stack:
*.proxy.example.com →
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
# Pull the new image
docker compose pull proxy
# Start the proxy alongside existing services
docker compose up -d
# Verify all services are running
docker compose ps
# Check proxy logs
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
Once the proxy connects to the management server:
- Open your NetBird dashboard
- Navigate to Reverse Proxy > Services
- Click Add Service
- In the domain selector, you should see your proxy domain (e.g.,
proxy.example.com) with a Cluster badge
If the domain appears, the proxy is connected and ready. You can now create your first service.
Configure SSO for external identity providers
Who this applies to
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.
Why this is needed
The reverse proxy SSO feature authenticates users through an OAuth2/OIDC flow that redirects through a callback endpoint on the management server (/api/reverse-proxy/callback). The embedded IdP registers this callback automatically, but external IdPs need it configured manually. Without this configuration, SSO authentication on reverse proxy services will silently fail.
Option A: Quick fix (keep your external IdP)
If you want to keep using your current external identity provider, follow these three steps:
Step 1: Add callback URL to management.json
Add the AuthCallbackURL field to the HttpConfig section of your management.json:
"HttpConfig": {
...existing fields...,
"AuthCallbackURL": "https://<your-management-domain>/api/reverse-proxy/callback"
}
Replace <your-management-domain> with your NetBird management server domain (the same domain used for the dashboard).
Step 2: Register callback in your IdP
In your identity provider's application settings, add the following URL as an allowed redirect URI / callback URL:
https:///api/reverse-proxy/callback
This is in addition to any existing redirect URIs (like /auth or /silent-auth).
Where to find this setting in common providers:
| Provider | Where to add the redirect URI |
|---|---|
| Auth0 | Application > Settings > Allowed Callback URLs |
| Okta | Application > General > Login redirect URIs |
| Keycloak | Client > Settings > Valid redirect URIs |
| Zitadel | Application > Redirect Settings > Redirect URIs |
| Generic OIDC | Refer to your provider's documentation |
Step 3: Restart management server
Restart the management service to pick up the configuration change:
docker compose restart netbird-management
Option B: Migrate to the embedded IdP (recommended)
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.
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.
See the Identity Providers page for instructions on adding external IdPs as connectors.
Migrating from a standalone external IdP to the embedded IdP with your IdP as a connector requires user ID migration. See the Migration Guide or contact support@netbird.io for assistance.
Verification
After configuring SSO for your external identity provider, verify it works:
- Create a reverse proxy service with SSO authentication enabled
- Open the service URL in an incognito/private browser window
- Confirm you are redirected to your IdP login page
- After authenticating, confirm you are redirected back to the service and can access it
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.
For users not on Traefik
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 for a step-by-step guide on configuring Traefik for your NetBird deployment.
Environment variable reference
| Variable | Required | Description | Default |
|---|---|---|---|
NB_PROXY_TOKEN | Yes | Access token generated via netbird-mgmt token create. The proxy refuses to start without it. | - |
NB_PROXY_DOMAIN | Yes | Base domain for this proxy instance (e.g., proxy.example.com). Determines the domain available for services. | - |
NB_PROXY_MANAGEMENT_ADDRESS | No | URL of your NetBird management server. The proxy connects via gRPC to register itself. | https://api.netbird.io:443 |
NB_PROXY_ADDRESS | No | Address the proxy listens on. | :8443 (Docker), :443 (binary) |
NB_PROXY_ACME_CERTIFICATES | No | Set to true to enable automatic TLS certificate provisioning via Let's Encrypt. | false |
NB_PROXY_ACME_CHALLENGE_TYPE | No | ACME challenge type: tls-alpn-01 (port 443) or http-01 (port 80). | tls-alpn-01 |
NB_PROXY_CERTIFICATE_FILE | No | TLS certificate filename within the certificate directory (for static certificate mode). | tls.crt |
NB_PROXY_CERTIFICATE_KEY_FILE | No | TLS private key filename within the certificate directory (for static certificate mode). | tls.key |
NB_PROXY_CERTIFICATE_DIRECTORY | No | Directory where static certificate files are stored. | ./certs |
NB_PROXY_ALLOW_INSECURE | No | Allow insecure (non-TLS) gRPC connection to the management server. Set to true when connecting over an internal Docker network. | false |
NB_PROXY_FORWARDED_PROTO | No | Protocol to report in the X-Forwarded-Proto header. Set to https when TLS is terminated at the proxy. | - |
NB_PROXY_OIDC_CLIENT_ID | No | OIDC client ID for proxy SSO authentication. | - |
NB_PROXY_OIDC_ENDPOINT | No | OIDC discovery endpoint URL for proxy SSO authentication. | - |
NB_PROXY_OIDC_SCOPES | No | Comma-separated list of OIDC scopes to request (e.g., openid,profile,email). | - |
NB_PROXY_DEBUG_LOGS | No | Enable debug-level logging. | false |
Troubleshooting
Certificate provisioning failures
Symptom: Services stay in certificate_pending or move to certificate_failed status.
Checklist:
- Verify port 443 is accessible from the internet (required for
tls-alpn-01challenge) - Ensure the wildcard DNS record resolves correctly:
dig myapp.proxy.example.com - Check proxy logs for ACME errors:
docker compose logs proxy | grep -i acme - If using
http-01challenge type, ensure port 80 is also accessible
TLS passthrough not working
Symptom: The proxy starts but services return TLS errors or Traefik's default certificate.
Checklist:
- Verify Traefik labels include
tls.passthrough=true - Confirm the router is configured as a TCP router (not HTTP) - labels should use
traefik.tcp.routers, nottraefik.http.routers - Check that the
HostSNIrule is set toHostSNI(*)(wildcard catch-all) - Verify the TCP router has
priority=1to prevent it from intercepting traffic meant for the main NetBird HTTP routers - Ensure the
websecureentrypoint is configured in your Traefik configuration - Restart Traefik after adding the proxy container:
docker compose restart traefik
Port conflicts
Symptom: The proxy container fails to start with an address-in-use error.
Solution: The proxy listens on port 8443 inside the container. If another service uses port 8443 on the same Docker network, change NB_PROXY_ADDRESS to a different port and update the Traefik label loadbalancer.server.port to match.
Rollback procedure
If you need to remove the proxy and revert to your previous configuration:
# Stop all services
docker compose down
# Restore your backup
cd netbird-backup-YYYYMMDD
cp docker-compose.yml ../
# Restart without the proxy
cd ..
docker compose up -d
You can also revoke the proxy token to prevent the proxy from reconnecting:
docker exec -it netbird-server netbird-mgmt token list
docker exec -it netbird-server netbird-mgmt token revoke <token-id>
Additional resources
- Reverse Proxy feature documentation - full overview of services, targets, domains, and authentication
- Custom Domains - use your own domain names for reverse proxy services
- Reverse Proxy configuration reference - all proxy environment variables and options
- Self-Hosting Quickstart - getting started with self-hosted NetBird

