Reverse Proxy Configuration

NetBird includes a built-in Caddy reverse proxy that handles TLS certificates automatically. However, if you already have an existing reverse proxy (Traefik, Nginx, etc.), you can configure NetBird to work with it instead.

Quick Setup

New Deployments

The getting-started.sh script supports multiple reverse proxy configurations. During initial deployment, you'll be prompted to select your reverse proxy:

Which reverse proxy will you use?
  [0] Built-in Caddy (recommended - automatic TLS)
  [1] Traefik (labels added to containers)
  [2] Nginx (generates config template)
  [3] Nginx Proxy Manager (generates config + instructions)
  [4] External Caddy (generates Caddyfile snippet)
  [5] Other/Manual (displays setup documentation)

The script will generate the appropriate configuration files and provide setup instructions for your chosen proxy.

Existing Deployments

For existing NetBird installations, use the configuration templates in the sections below to manually configure your reverse proxy.

Required Routing Endpoints

All reverse proxy configurations must route the following endpoints:

PathProtocolTargetNotes
/relay*WebSocketrelay:80WebSocket upgrade required
/ws-proxy/signal*WebSocketsignal:80WebSocket upgrade required
/signalexchange.SignalExchange/*gRPCsignal:10000HTTP/2 (h2c) required
/api/*HTTPmanagement:80REST API
/ws-proxy/management*WebSocketmanagement:80WebSocket upgrade required
/management.ManagementService/*gRPCmanagement:80HTTP/2 (h2c) required
/oauth2/*HTTPmanagement:80Embedded IdP
/*HTTPdashboard:80Catch-all for dashboard

Docker Compose for External Proxy

When using an external reverse proxy, expose the NetBird container ports to the host:

services:
  dashboard:
    image: netbirdio/dashboard:latest
    ports:
      - '127.0.0.1:8080:80'
    # ... other config

  signal:
    image: netbirdio/signal:latest
    ports:
      - '127.0.0.1:8083:80'
      - '127.0.0.1:10000:10000'
    # ... other config

  relay:
    image: netbirdio/relay:latest
    ports:
      - '127.0.0.1:8084:80'
    # ... other config

  management:
    image: netbirdio/management:latest
    ports:
      - '127.0.0.1:8081:80'
    # ... other config

Container Port Reference

ServiceHost PortContainer PortProtocol
Dashboard808080HTTP
Signal (HTTP)808380HTTP/WebSocket
Signal (gRPC)1000010000gRPC (h2c)
Management808180HTTP/gRPC
Relay808480WebSocket

Configuration Templates

Traefik

For Traefik, NetBird containers are configured with Docker labels for automatic service discovery. The getting-started.sh script will prompt for your Traefik configuration:

  • External network: The Docker network your Traefik container uses
  • HTTPS entrypoint: Your Traefik entrypoint for HTTPS traffic (commonly websecure)
  • Certificate resolver: Your Traefik certresolver for automatic TLS (e.g., letsencrypt)

Complete Docker Compose with Traefik Labels

Add the following labels to your NetBird services in docker-compose.yml. Replace:

  • netbird.example.com with your domain
  • websecure with your HTTPS entrypoint name
  • letsencrypt with your certificate resolver name (or remove the certresolver lines if handling TLS externally)
services:
  dashboard:
    image: netbirdio/dashboard:latest
    networks: [traefik-network]
    labels:
      - traefik.enable=true
      - traefik.http.routers.netbird-dashboard.rule=Host(`netbird.example.com`)
      - traefik.http.routers.netbird-dashboard.entrypoints=websecure
      - traefik.http.routers.netbird-dashboard.tls=true
      - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-dashboard.priority=1
      - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80

  signal:
    image: netbirdio/signal:latest
    networks: [traefik-network]
    labels:
      - traefik.enable=true
      # WebSocket router
      - traefik.http.routers.netbird-signal-ws.rule=Host(`netbird.example.com`) && PathPrefix(`/ws-proxy/signal`)
      - traefik.http.routers.netbird-signal-ws.entrypoints=websecure
      - traefik.http.routers.netbird-signal-ws.tls=true
      - traefik.http.routers.netbird-signal-ws.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-signal-ws.service=netbird-signal-ws
      - traefik.http.services.netbird-signal-ws.loadbalancer.server.port=80
      # gRPC router
      - traefik.http.routers.netbird-signal-grpc.rule=Host(`netbird.example.com`) && PathPrefix(`/signalexchange.SignalExchange/`)
      - traefik.http.routers.netbird-signal-grpc.entrypoints=websecure
      - traefik.http.routers.netbird-signal-grpc.tls=true
      - traefik.http.routers.netbird-signal-grpc.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-signal-grpc.service=netbird-signal-grpc
      - traefik.http.services.netbird-signal-grpc.loadbalancer.server.port=10000
      - traefik.http.services.netbird-signal-grpc.loadbalancer.server.scheme=h2c

  relay:
    image: netbirdio/relay:latest
    networks: [traefik-network]
    labels:
      - traefik.enable=true
      - traefik.http.routers.netbird-relay.rule=Host(`netbird.example.com`) && PathPrefix(`/relay`)
      - traefik.http.routers.netbird-relay.entrypoints=websecure
      - traefik.http.routers.netbird-relay.tls=true
      - traefik.http.routers.netbird-relay.tls.certresolver=letsencrypt
      - traefik.http.services.netbird-relay.loadbalancer.server.port=80

  management:
    image: netbirdio/management:latest
    networks: [traefik-network]
    labels:
      - traefik.enable=true
      # API router
      - traefik.http.routers.netbird-api.rule=Host(`netbird.example.com`) && PathPrefix(`/api`)
      - traefik.http.routers.netbird-api.entrypoints=websecure
      - traefik.http.routers.netbird-api.tls=true
      - traefik.http.routers.netbird-api.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-api.service=netbird-api
      - traefik.http.services.netbird-api.loadbalancer.server.port=80
      # Management WebSocket router
      - traefik.http.routers.netbird-mgmt-ws.rule=Host(`netbird.example.com`) && PathPrefix(`/ws-proxy/management`)
      - traefik.http.routers.netbird-mgmt-ws.entrypoints=websecure
      - traefik.http.routers.netbird-mgmt-ws.tls=true
      - traefik.http.routers.netbird-mgmt-ws.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-mgmt-ws.service=netbird-mgmt-ws
      - traefik.http.services.netbird-mgmt-ws.loadbalancer.server.port=80
      # Management gRPC router
      - traefik.http.routers.netbird-mgmt-grpc.rule=Host(`netbird.example.com`) && PathPrefix(`/management.ManagementService/`)
      - traefik.http.routers.netbird-mgmt-grpc.entrypoints=websecure
      - traefik.http.routers.netbird-mgmt-grpc.tls=true
      - traefik.http.routers.netbird-mgmt-grpc.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-mgmt-grpc.service=netbird-mgmt-grpc
      - traefik.http.services.netbird-mgmt-grpc.loadbalancer.server.port=80
      - traefik.http.services.netbird-mgmt-grpc.loadbalancer.server.scheme=h2c
      # OAuth2 router (embedded IdP)
      - traefik.http.routers.netbird-oauth2.rule=Host(`netbird.example.com`) && PathPrefix(`/oauth2`)
      - traefik.http.routers.netbird-oauth2.entrypoints=websecure
      - traefik.http.routers.netbird-oauth2.tls=true
      - traefik.http.routers.netbird-oauth2.tls.certresolver=letsencrypt
      - traefik.http.routers.netbird-oauth2.service=netbird-oauth2
      - traefik.http.services.netbird-oauth2.loadbalancer.server.port=80

networks:
  traefik-network:
    external: true

Traefik Configuration Requirements

Your Traefik instance must have the following configured:

1. HTTPS Entrypoint (listening on port 443):

# In traefik.yml or as command args
entryPoints:
  websecure:
    address: ":443"

2. Certificate Resolver (for automatic Let's Encrypt certificates):

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

3. Docker Provider with exposedByDefault disabled:

providers:
  docker:
    exposedByDefault: false

4. HTTP to HTTPS Redirect (recommended):

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https

Example Traefik Docker Compose

If you don't have Traefik set up yet, here's a complete example:

services:
  traefik:
    image: traefik:v3.2
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedByDefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.email=your-email@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"  # Traefik dashboard (optional)
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik-network

networks:
  traefik-network:
    name: traefik-network

Nginx

Nginx running in Docker

If Nginx is running in Docker, the easiest approach is to have NetBird join the same Docker network. The getting-started.sh script will ask for your Nginx network name and configure everything automatically.

# NetBird Nginx Configuration (Docker network)
# Uses container names for upstream servers

upstream netbird_dashboard {
    server netbird-dashboard:80;
    keepalive 10;
}
upstream netbird_signal {
    server netbird-signal:10000;
}
upstream netbird_signal_ws {
    server netbird-signal:80;
}
upstream netbird_management {
    server netbird-management:80;
}
upstream netbird_relay {
    server netbird-relay:80;
}

server {
    listen 80;
    server_name netbird.example.com;
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name netbird.example.com;

    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # Required for long-lived gRPC connections
    client_header_timeout 1d;
    client_body_timeout 1d;

    # Common proxy headers
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Host $host;
    grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # Relay (WebSocket)
    location /relay {
        proxy_pass http://netbird_relay;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 1d;
    }

    # Signal WebSocket
    location /ws-proxy/signal {
        proxy_pass http://netbird_signal_ws;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 1d;
    }

    # Signal gRPC
    location /signalexchange.SignalExchange/ {
        grpc_pass grpc://netbird_signal;
        grpc_read_timeout 1d;
        grpc_send_timeout 1d;
        grpc_socket_keepalive on;
    }

    # Management API
    location /api/ {
        proxy_pass http://netbird_management;
        proxy_set_header Host $host;
    }

    # Management WebSocket
    location /ws-proxy/management {
        proxy_pass http://netbird_management;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 1d;
    }

    # Management gRPC
    location /management.ManagementService/ {
        grpc_pass grpc://netbird_management;
        grpc_read_timeout 1d;
        grpc_send_timeout 1d;
        grpc_socket_keepalive on;
    }

    # Embedded IdP OAuth2
    location /oauth2/ {
        proxy_pass http://netbird_management;
        proxy_set_header Host $host;
    }

    # Dashboard (catch-all)
    location / {
        proxy_pass http://netbird_dashboard;
    }
}

Nginx running on host (not in Docker)

If Nginx is installed directly on the host, use localhost addresses:

# NetBird Nginx Configuration
# Replace netbird.example.com with your domain
# Update SSL certificate paths

upstream netbird_dashboard {
    server 127.0.0.1:8080;
    keepalive 10;
}
upstream netbird_signal {
    server 127.0.0.1:10000;
}
upstream netbird_signal_ws {
    server 127.0.0.1:8083;
}
upstream netbird_management {
    server 127.0.0.1:8081;
}
upstream netbird_relay {
    server 127.0.0.1:8084;
}

server {
    listen 80;
    server_name netbird.example.com;
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name netbird.example.com;

    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;

    # Required for long-lived gRPC connections
    client_header_timeout 1d;
    client_body_timeout 1d;

    # Common proxy headers
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Host $host;
    grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # Relay (WebSocket)
    location /relay {
        proxy_pass http://netbird_relay;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 1d;
    }

    # Signal WebSocket
    location /ws-proxy/signal {
        proxy_pass http://netbird_signal_ws;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 1d;
    }

    # Signal gRPC
    location /signalexchange.SignalExchange/ {
        grpc_pass grpc://netbird_signal;
        grpc_read_timeout 1d;
        grpc_send_timeout 1d;
        grpc_socket_keepalive on;
    }

    # Management API
    location /api/ {
        proxy_pass http://netbird_management;
        proxy_set_header Host $host;
    }

    # Management WebSocket
    location /ws-proxy/management {
        proxy_pass http://netbird_management;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 1d;
    }

    # Management gRPC
    location /management.ManagementService/ {
        grpc_pass grpc://netbird_management;
        grpc_read_timeout 1d;
        grpc_send_timeout 1d;
        grpc_socket_keepalive on;
    }

    # Embedded IdP OAuth2
    location /oauth2/ {
        proxy_pass http://netbird_management;
        proxy_set_header Host $host;
    }

    # Dashboard (catch-all)
    location / {
        proxy_pass http://netbird_dashboard;
    }
}

Installation:

Debian/Ubuntu:

sudo ln -s /path/to/netbird.conf /etc/nginx/sites-available/netbird
sudo ln -s /etc/nginx/sites-available/netbird /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

RHEL/CentOS:

sudo cp /path/to/netbird.conf /etc/nginx/conf.d/netbird.conf
sudo nginx -t && sudo systemctl reload nginx

TLS Certificate Setup for Nginx

You'll need to obtain SSL/TLS certificates and update the certificate paths in your Nginx configuration. Here are the most common options:

Option 1: Let's Encrypt with Certbot (Recommended)

Let's Encrypt provides free, automated TLS certificates with automatic renewal.

  1. Install certbot:
# Debian/Ubuntu
sudo apt install certbot python3-certbot-nginx

# RHEL/CentOS
sudo yum install certbot python3-certbot-nginx
  1. Obtain certificate:
# If Nginx is already running
sudo certbot certonly --nginx -d netbird.example.com

# If Nginx isn't running yet (standalone mode)
sudo certbot certonly --standalone -d netbird.example.com
  1. Update your Nginx config with the certificate paths:
ssl_certificate /etc/letsencrypt/live/netbird.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/netbird.example.com/privkey.pem;
  1. Set up automatic renewal:
# Certbot automatically installs a systemd timer for renewal
# Test the renewal process:
sudo certbot renew --dry-run

Option 2: Let's Encrypt with acme.sh

acme.sh is a lightweight alternative to certbot:

# Install acme.sh
curl https://get.acme.sh | sh

# Issue certificate
~/.acme.sh/acme.sh --issue -d netbird.example.com --nginx

# Update Nginx config with paths:
# ssl_certificate /root/.acme.sh/netbird.example.com/fullchain.cer;
# ssl_certificate_key /root/.acme.sh/netbird.example.com/netbird.example.com.key;

Option 3: Custom/Commercial Certificates

If you have certificates from another provider:

  1. Place your certificate files on the server:
sudo cp fullchain.pem /etc/ssl/certs/netbird.crt
sudo cp privkey.pem /etc/ssl/private/netbird.key
sudo chmod 600 /etc/ssl/private/netbird.key
  1. Update your Nginx config:
ssl_certificate /etc/ssl/certs/netbird.crt;
ssl_certificate_key /etc/ssl/private/netbird.key;

TLS Best Practices

The generated Nginx configurations include recommended TLS settings:

# Use modern TLS protocols only
ssl_protocols TLSv1.2 TLSv1.3;

# Let clients choose the best cipher
ssl_prefer_server_ciphers off;

# Strong cipher suites
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

These settings ensure:

  • Only TLS 1.2 and 1.3 are allowed (older protocols like TLS 1.0/1.1 are disabled)
  • Modern cipher suites that support forward secrecy
  • Compatibility with all modern browsers and NetBird clients

Caddy (External)

Caddy running in Docker

If Caddy is running in Docker, the easiest approach is to have NetBird join the same Docker network. The getting-started.sh script will ask for your Caddy network name and configure everything automatically.

netbird.example.com {
    # Relay (WebSocket)
    reverse_proxy /relay* netbird-relay:80

    # Signal WebSocket
    reverse_proxy /ws-proxy/signal* netbird-signal:80

    # Signal gRPC (h2c for plaintext HTTP/2)
    reverse_proxy /signalexchange.SignalExchange/* h2c://netbird-signal:10000

    # Management API
    reverse_proxy /api/* netbird-management:80

    # Management WebSocket
    reverse_proxy /ws-proxy/management* netbird-management:80

    # Management gRPC
    reverse_proxy /management.ManagementService/* h2c://netbird-management:80

    # Embedded IdP OAuth2
    reverse_proxy /oauth2/* netbird-management:80

    # Dashboard (catch-all)
    reverse_proxy /* netbird-dashboard:80
}

Caddy running on host (not in Docker)

If Caddy is installed directly on the host, use localhost addresses:

netbird.example.com {
    # Relay (WebSocket)
    reverse_proxy /relay* 127.0.0.1:8084

    # Signal WebSocket
    reverse_proxy /ws-proxy/signal* 127.0.0.1:8083

    # Signal gRPC (h2c for plaintext HTTP/2)
    reverse_proxy /signalexchange.SignalExchange/* h2c://127.0.0.1:10000

    # Management API
    reverse_proxy /api/* 127.0.0.1:8081

    # Management WebSocket
    reverse_proxy /ws-proxy/management* 127.0.0.1:8081

    # Management gRPC
    reverse_proxy /management.ManagementService/* h2c://127.0.0.1:8081

    # Embedded IdP OAuth2
    reverse_proxy /oauth2/* 127.0.0.1:8081

    # Dashboard (catch-all)
    reverse_proxy /* 127.0.0.1:8080
}

After adding the configuration, reload Caddy:

sudo systemctl reload caddy

Nginx Proxy Manager

Nginx Proxy Manager requires all NetBird routing to be configured via the "Advanced" tab. The Custom Locations feature doesn't properly handle path prefixes.

If NPM is running in Docker, the easiest approach is to have NetBird join the same Docker network. The script will ask for your NPM network name and configure everything automatically.

1. Create a Proxy Host in NPM:

  • Domain: netbird.example.com
  • Forward Hostname/IP: netbird-dashboard
  • Forward Port: 80
  • Block Common Exploits: enabled

2. SSL tab:

  • Request or select existing certificate
  • Enable "HTTP/2 Support" (required for gRPC)

3. Advanced tab - paste this configuration:

# Required for long-lived connections (gRPC and WebSocket)
client_header_timeout 1d;
client_body_timeout 1d;

# Relay WebSocket
location /relay {
    proxy_pass http://netbird-relay:80;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
}

# Signal WebSocket
location /ws-proxy/signal {
    proxy_pass http://netbird-signal:80;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
}

# Management WebSocket
location /ws-proxy/management {
    proxy_pass http://netbird-management:80;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
}

# API routes
location /api/ {
    proxy_pass http://netbird-management:80;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

# OAuth2/IdP routes
location /oauth2/ {
    proxy_pass http://netbird-management:80;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

# gRPC for Signal service
location /signalexchange.SignalExchange/ {
    grpc_pass grpc://netbird-signal:10000;
    grpc_read_timeout 1d;
    grpc_send_timeout 1d;
    grpc_socket_keepalive on;
}

# gRPC for Management service
location /management.ManagementService/ {
    grpc_pass grpc://netbird-management:80;
    grpc_read_timeout 1d;
    grpc_send_timeout 1d;
    grpc_socket_keepalive on;
}

NPM running on host (not in Docker)

If NPM is installed directly on the host, use localhost addresses:

1. Create a Proxy Host in NPM:

  • Domain: netbird.example.com
  • Forward Hostname/IP: 127.0.0.1
  • Forward Port: 8080
  • Block Common Exploits: enabled

2. SSL tab:

  • Request or select existing certificate
  • Enable "HTTP/2 Support" (required for gRPC)

3. Advanced tab - paste this configuration:

# Required for long-lived connections (gRPC and WebSocket)
client_header_timeout 1d;
client_body_timeout 1d;

# Relay WebSocket
location /relay {
    proxy_pass http://127.0.0.1:8084;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
}

# Signal WebSocket
location /ws-proxy/signal {
    proxy_pass http://127.0.0.1:8083;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
}

# Management WebSocket
location /ws-proxy/management {
    proxy_pass http://127.0.0.1:8081;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
}

# API routes
location /api/ {
    proxy_pass http://127.0.0.1:8081;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

# OAuth2/IdP routes
location /oauth2/ {
    proxy_pass http://127.0.0.1:8081;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

# gRPC for Signal service
location /signalexchange.SignalExchange/ {
    grpc_pass grpc://127.0.0.1:10000;
    grpc_read_timeout 1d;
    grpc_send_timeout 1d;
    grpc_socket_keepalive on;
}

# gRPC for Management service
location /management.ManagementService/ {
    grpc_pass grpc://127.0.0.1:8081;
    grpc_read_timeout 1d;
    grpc_send_timeout 1d;
    grpc_socket_keepalive on;
}

Troubleshooting

gRPC connections failing

  • Ensure your reverse proxy supports HTTP/2 and gRPC
  • Nginx Proxy Manager: Enable "HTTP/2 Support" in the SSL tab
  • Check that h2c (plaintext HTTP/2) is correctly configured for gRPC upstreams
  • Verify timeout settings are long enough (gRPC connections can be long-lived)

WebSocket connections failing

  • Ensure WebSocket upgrade headers are passed through
  • Check that Connection: Upgrade and Upgrade: websocket headers are preserved

SSL/TLS certificate issues

  • Verify your certificates are valid and not expired
  • Check certificate paths in your configuration
  • For Let's Encrypt, ensure ports 80 and 443 are accessible for validation

Dashboard loads but API calls fail

  • Check that /api/* routes are correctly configured
  • Verify the management container is running: docker compose ps management
  • Check management logs: docker compose logs management

Signal or Relay connections failing

  • Verify gRPC routes are using h2c:// (plaintext HTTP/2)
  • Check that WebSocket routes have proper upgrade handling
  • Ensure coturn (STUN/TURN) is accessible on UDP port 3478

For more help, see the Troubleshooting guide or reach out on Slack.