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.

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

  • proxy service (container: netbird-proxy) - a new service in your Docker Compose stack that handles TLS termination, certificate provisioning, and traffic forwarding for reverse proxy services
  • proxy.env file - 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, and relay services are unchanged
  • 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 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-server image - 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.com DNS record pointing to your server
  • 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 websecure entrypoint (port 443)
  • Passes the TLS connection through without termination (tls.passthrough=true)
  • Uses priority=1 to avoid intercepting traffic meant for the main NetBird HTTP routers on the same entrypoint
  • Forwards traffic to the proxy container on port 8443

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:

  1. Open your NetBird dashboard
  2. Navigate to Reverse Proxy > Services
  3. Click Add Service
  4. 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:

ProviderWhere to add the redirect URI
Auth0Application > Settings > Allowed Callback URLs
OktaApplication > General > Login redirect URIs
KeycloakClient > Settings > Valid redirect URIs
ZitadelApplication > Redirect Settings > Redirect URIs
Generic OIDCRefer 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

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.

Verification

After configuring SSO for your external identity provider, verify it works:

  1. Create a reverse proxy service with SSO authentication enabled
  2. Open the service URL in an incognito/private browser window
  3. Confirm you are redirected to your IdP login page
  4. 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

VariableRequiredDescriptionDefault
NB_PROXY_TOKENYesAccess token generated via netbird-mgmt token create. The proxy refuses to start without it.-
NB_PROXY_DOMAINYesBase domain for this proxy instance (e.g., proxy.example.com). Determines the domain available for services.-
NB_PROXY_MANAGEMENT_ADDRESSNoURL of your NetBird management server. The proxy connects via gRPC to register itself.https://api.netbird.io:443
NB_PROXY_ADDRESSNoAddress the proxy listens on.:8443 (Docker), :443 (binary)
NB_PROXY_ACME_CERTIFICATESNoSet to true to enable automatic TLS certificate provisioning via Let's Encrypt.false
NB_PROXY_ACME_CHALLENGE_TYPENoACME challenge type: tls-alpn-01 (port 443) or http-01 (port 80).tls-alpn-01
NB_PROXY_CERTIFICATE_FILENoTLS certificate filename within the certificate directory (for static certificate mode).tls.crt
NB_PROXY_CERTIFICATE_KEY_FILENoTLS private key filename within the certificate directory (for static certificate mode).tls.key
NB_PROXY_CERTIFICATE_DIRECTORYNoDirectory where static certificate files are stored../certs
NB_PROXY_ALLOW_INSECURENoAllow insecure (non-TLS) gRPC connection to the management server. Set to true when connecting over an internal Docker network.false
NB_PROXY_FORWARDED_PROTONoProtocol to report in the X-Forwarded-Proto header. Set to https when TLS is terminated at the proxy.-
NB_PROXY_OIDC_CLIENT_IDNoOIDC client ID for proxy SSO authentication.-
NB_PROXY_OIDC_ENDPOINTNoOIDC discovery endpoint URL for proxy SSO authentication.-
NB_PROXY_OIDC_SCOPESNoComma-separated list of OIDC scopes to request (e.g., openid,profile,email).-
NB_PROXY_DEBUG_LOGSNoEnable debug-level logging.false

Troubleshooting

Certificate provisioning failures

Symptom: Services stay in certificate_pending or move to certificate_failed status.

Checklist:

  1. Verify port 443 is accessible from the internet (required for tls-alpn-01 challenge)
  2. Ensure the wildcard DNS record resolves correctly: dig myapp.proxy.example.com
  3. Check proxy logs for ACME errors: docker compose logs proxy | grep -i acme
  4. If using http-01 challenge 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:

  1. Verify Traefik labels include tls.passthrough=true
  2. Confirm the router is configured as a TCP router (not HTTP) - labels should use traefik.tcp.routers, not traefik.http.routers
  3. Check that the HostSNI rule is set to HostSNI(*) (wildcard catch-all)
  4. Verify the TCP router has priority=1 to prevent it from intercepting traffic meant for the main NetBird HTTP routers
  5. Ensure the websecure entrypoint is configured in your Traefik configuration
  6. 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