Problem getting NetBird Relay working behind Nginx with TLS on self-hosted setup #2260

Open
opened 2025-11-20 07:06:38 -05:00 by saavagebueno · 18 comments
Owner

Originally created by @teksrq on GitHub (Sep 9, 2025).

Hi team,

I’ve been working on a self-hosted NetBird docker deployment (signal + relay + management with Zitadel self-hosted docker deployed separately on the same server) and finally have everything working reliably except the Relay server when running behind an Nginx reverse proxy with TLS. Server is Ubuntu 22.04

Peers can connect successfully to the Signal service and management API, but relay connections keep failing with errors like:

failed to dial to Relay server 'wss://netbird.example.com:33080/relay':
failed to send handshake request:
Get "https://netbird.example.com:33080/relay":
http: server gave HTTP response to HTTPS client

I spent a few days alternately trying to terminate TLS at the nginx proxy and then terminating at the containers, but each time I'd end up breaking the working setup with no progress towards solving the relay issue.

Current working set up:

  • Nginx terminates TLS and forwards to the relay container.
    Ports open in UFW:
  • 10000/tcp (signal)
  • 33080/tcp (relay over TLS)
    -3478/udp and 49152:65535/udp (STUN/media)
  • 33080/udp allowed in from anywhere
    Other services (signal, management, dashboard) are working fine through the same proxy.

What I’ve tried

  • Confirmed that peers connect to the Signal service without issue.
  • Verified that nginx forwards /relay requests to the relay container.
  • Double-checked TLS certs (valid, working for management/dashboard).
  • Reviewed logs from peer and relay containers — relay errors are consistent.

Questions

  1. Is NetBird relay expected to work over pure TLS (wss://) behind an Nginx reverse proxy, or does it require QUIC/UDP to function properly?
  2. If TLS relay should work, are there specific Nginx config requirements for forwarding WebSockets and gRPC to the relay container?
  3. If QUIC is required, should I expose 33080/udp directly (bypassing nginx), or can nginx proxy QUIC as well?

/etc/nginx/sites-available/netbird.conf

upstream dashboard {
    server 127.0.0.1:8081;
    keepalive 10;
}

upstream management {
    server 127.0.0.1:9444; 
    keepalive 10;
}

upstream signal {
    server 127.0.0.1:10000; 
    keepalive 10;
}

upstream relay {
    server 127.0.0.1:33080;
}

server {
    listen 80;
    server_name netbird.example.com;

    # 301 redirect to HTTPS
    location / {
            return 301 https://$host$request_uri;
    }
}
server {
    listen 443 ssl http2;
    server_name netbird.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    client_header_timeout 1d;
    client_body_timeout 1d;

    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;

    # Proxy dashboard
    location / {
        proxy_pass http://dashboard;
    }

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

    # Proxy Management http endpoint
    location /api/ {
        proxy_pass http://management;
        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 Management grpc endpoint
    location /management.ManagementService/ {
        grpc_pass grpc://management;
        #grpc_ssl_verify off;
        grpc_read_timeout 1d;
        grpc_send_timeout 1d;
        grpc_socket_keepalive on;
    }

    # Relay for headless peers
    location /relay {
        grpc_pass grpc://relay;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        grpc_set_header X-Forwarded-Proto $scheme;
        grpc_read_timeout 1d;
        grpc_send_timeout 1d;
        grpc_socket_keepalive on;
    }

}

docker-compose

x-default: &default
  restart: 'unless-stopped'
  logging:
    driver: 'json-file'
    options:
      max-size: '500m'
      max-file: '2'

services:
  # UI dashboard
  dashboard:
    <<: *default
    image: netbirdio/dashboard:latest
    ports:
      - 8081:80
      - 9443:443
    environment:
      # Endpoints
      - NETBIRD_MGMT_API_ENDPOINT=https://netbird.example.com
      - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.example.com
      # OIDC
      - AUTH_AUDIENCE=1234
      - AUTH_CLIENT_ID=1234
      - AUTH_CLIENT_SECRET=example
      - AUTH_AUTHORITY=https://zitadel.example.com
      - USE_AUTH0=false
      - AUTH_SUPPORTED_SCOPES=openid profile email offline_access api
      - AUTH_REDIRECT_URI=/auth
      - AUTH_SILENT_REDIRECT_URI=/silent-auth
      - NETBIRD_TOKEN_SOURCE=accessToken
      # SSL
      - NGINX_SSL_PORT=443
      # Letsencrypt
      - LETSENCRYPT_DOMAIN=
      - LETSENCRYPT_EMAIL=
    volumes:
      - netbird-letsencrypt:/etc/letsencrypt/

  # Signal
  signal:
    <<: *default
    image: netbirdio/signal:latest
    volumes:
      - netbird-signal:/var/lib/netbird
    ports:
      - 10000:80
 # Relay
  relay:
    <<: *default
    image: netbirdio/relay:latest
    environment:
      - NB_LOG_LEVEL=info
      - NB_LISTEN_ADDRESS=:33080
      - NB_EXPOSED_ADDRESS=rels://netbird.example.com:33080/relay
      # todo: change to a secure secret
      - NB_AUTH_SECRET=example
    ports:
      - 33080:33080

  # Management
  management:
    <<: *default
    image: netbirdio/management:latest
    depends_on:
      - dashboard
    volumes:
      - netbird-mgmt:/var/lib/netbird
      - netbird-letsencrypt:/etc/letsencrypt:ro
      - ./management.json:/etc/netbird/management.json
    ports:
      - 9444:443 #API port
  #    # command for Let's Encrypt validation without dashboard container
  #    command: ["--letsencrypt-domain", "", "--log-file", "console"]
    command: [
      "--port", "443",
      "--log-file", "console",
      "--log-level", "info",
      "--disable-anonymous-metrics=false",
      "--single-account-mode-domain=netbird.example.comm",
      "--dns-domain=netbird.selfhosted"
      ]
    environment:
      - NETBIRD_STORE_ENGINE_POSTGRES_DSN=
      - NETBIRD_STORE_ENGINE_MYSQL_DSN=

  # Coturn
  coturn:
    <<: *default
    image: coturn/coturn:latest
    volumes:
      - ./turnserver.conf:/etc/turnserver.conf:ro
    network_mode: host
    environment:
      - REALM=netbird.example.com
      - LISTEN_PORT=3478
      - TLS_LISTEN_PORT=5349
      - CERT_FILE=/etc/letsencrypt/live/example.com/fullchain.pem
      - KEY_FILE=/etc/letsencrypt/live/example.com/privkey.pem
    command:
      - -c /etc/turnserver.conf

volumes:
  netbird-mgmt:
  netbird-signal:
  netbird-letsencrypt:

management.json

{
    "Stuns": [
        {
            "Proto": "udp",
            "URI": "stun:netbird.example.com:3478",
            "Username": "",
            "Password": ""
        }
    ],
    "TURNConfig": {
        "TimeBasedCredentials": false,
        "CredentialsTTL": "12h0m0s",
        "Secret": "secret",
        "Turns": [
            {
                "Proto": "udp",
                "URI": "turn:netbird.example.com:3478",
                "Username": "self",
                "Password": "example"
            }
        ]
    },
    "Relay": {
        "Addresses": [
            "rels://netbird.example.com:33080/relay"
        ],
        "CredentialsTTL": "24h0m0s",
        "Secret": "example"
    },
    "Signal": {
        "Proto": "http",
        "URI": "netbird.example.com:10000",
        "Username": "",
        "Password": ""
    },
    "Datadir": "/var/lib/netbird/",
    "DataStoreEncryptionKey": "example=",
    "HttpConfig": {
        "LetsEncryptDomain": "",
        "CertFile": "",
        "CertKey": "",
        "AuthAudience": "1234",
        "AuthIssuer": "https://zitadel.example.com",
        "AuthUserIDClaim": "",
        "AuthKeysLocation": "https://zitadel.example.com/oauth/v2/keys",
        "OIDCConfigEndpoint": "https://zitadel.example.com/.well-known/openid-configuration",
        "IdpSignKeyRefreshEnabled": true,
        "ExtraAuthAudience": ""
    },
    "IdpManagerConfig": {
        "ManagerType": "zitadel",
        "ClientConfig": {
            "Issuer": "https://zitadel.example.com",
            "TokenEndpoint": "https://zitadel.example.com/oauth/v2/token",
            "ClientID": "netbird",
            "ClientSecret": "example",
            "GrantType": "client_credentials"
        },
        "ExtraConfig": {
            "AdminEndpoint": "https://zitadel.example.com/management/v1",
            "ManagementEndpoint": "https://zitadel.example.com/management/v1"
        },
        "Auth0ClientCredentials": null,
        "AzureClientCredentials": null,
        "KeycloakClientCredentials": null,
        "ZitadelClientCredentials": null
    },
    "DeviceAuthorizationFlow": {
        "Provider": "hosted",
        "ProviderConfig": {
            "ClientID": "1234",
            "ClientSecret": "",
            "Domain": "zitadel.example.com",
            "Audience": "1234",
            "TokenEndpoint": "https://zitadel.example.com/oauth/v2/token",
            "DeviceAuthEndpoint": "https://zitadel.example.com/oauth/v2/device_authorization",
            "AuthorizationEndpoint": "",
            "Scope": "openid profile email offline_access",
            "UseIDToken": false,
            "RedirectURLs": null,
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "PKCEAuthorizationFlow": {
        "ProviderConfig": {
            "ClientID": "1234",
            "ClientSecret": "example",
            "Domain": "",
            "Audience": "335978769598709762",
            "TokenEndpoint": "https://zitadel.example.com/oauth/v2/token",
            "DeviceAuthEndpoint": "",
            "AuthorizationEndpoint": "https://zitadel.example.com/oauth/v2/authorize",
            "Scope": "openid profile email offline_access api",
            "UseIDToken": false,
            "RedirectURLs": [
                "http://localhost:53000"
            ],
            "DisablePromptLogin": false,
            "LoginFlag": 0
                }
    },
    "StoreConfig": {
        "Engine": "sqlite"
    },
    "ReverseProxy": {
        "TrustedHTTPProxies": [],
        "TrustedHTTPProxiesCount": 0,
        "TrustedPeers": [
            "0.0.0.0/0"
        ]
    },
    "DisableDefaultPolicy": true
}
Originally created by @teksrq on GitHub (Sep 9, 2025). Hi team, I’ve been working on a self-hosted NetBird docker deployment (signal + relay + management with Zitadel self-hosted docker deployed separately on the same server) and finally have everything working reliably except the Relay server when running behind an Nginx reverse proxy with TLS. Server is Ubuntu 22.04 Peers can connect successfully to the Signal service and management API, but relay connections keep failing with errors like: > failed to dial to Relay server 'wss://netbird.example.com:33080/relay': failed to send handshake request: Get "https://netbird.example.com:33080/relay": http: server gave HTTP response to HTTPS client I spent a few days alternately trying to terminate TLS at the nginx proxy and then terminating at the containers, but each time I'd end up breaking the working setup with no progress towards solving the relay issue. Current working set up: - Nginx terminates TLS and forwards to the relay container. Ports open in UFW: - 10000/tcp (signal) - 33080/tcp (relay over TLS) -3478/udp and 49152:65535/udp (STUN/media) - 33080/udp allowed in from anywhere Other services (signal, management, dashboard) are working fine through the same proxy. What I’ve tried - Confirmed that peers connect to the Signal service without issue. - Verified that nginx forwards /relay requests to the relay container. - Double-checked TLS certs (valid, working for management/dashboard). - Reviewed logs from peer and relay containers — relay errors are consistent. Questions 1. Is NetBird relay expected to work over pure TLS (wss://) behind an Nginx reverse proxy, or does it require QUIC/UDP to function properly? 2. If TLS relay should work, are there specific Nginx config requirements for forwarding WebSockets and gRPC to the relay container? 3. If QUIC is required, should I expose 33080/udp directly (bypassing nginx), or can nginx proxy QUIC as well? /etc/nginx/sites-available/netbird.conf ``` upstream dashboard { server 127.0.0.1:8081; keepalive 10; } upstream management { server 127.0.0.1:9444; keepalive 10; } upstream signal { server 127.0.0.1:10000; keepalive 10; } upstream relay { server 127.0.0.1:33080; } server { listen 80; server_name netbird.example.com; # 301 redirect to HTTPS location / { return 301 https://$host$request_uri; } } server { listen 443 ssl http2; server_name netbird.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; client_header_timeout 1d; client_body_timeout 1d; 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; # Proxy dashboard location / { proxy_pass http://dashboard; } # Proxy Signal location /signalexchange.SignalExchange/ { grpc_pass grpc://signal; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } # Proxy Management http endpoint location /api/ { proxy_pass http://management; 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 Management grpc endpoint location /management.ManagementService/ { grpc_pass grpc://management; #grpc_ssl_verify off; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } # Relay for headless peers location /relay { grpc_pass grpc://relay; grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; grpc_set_header X-Forwarded-Proto $scheme; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } } ``` docker-compose ``` x-default: &default restart: 'unless-stopped' logging: driver: 'json-file' options: max-size: '500m' max-file: '2' services: # UI dashboard dashboard: <<: *default image: netbirdio/dashboard:latest ports: - 8081:80 - 9443:443 environment: # Endpoints - NETBIRD_MGMT_API_ENDPOINT=https://netbird.example.com - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.example.com # OIDC - AUTH_AUDIENCE=1234 - AUTH_CLIENT_ID=1234 - AUTH_CLIENT_SECRET=example - AUTH_AUTHORITY=https://zitadel.example.com - USE_AUTH0=false - AUTH_SUPPORTED_SCOPES=openid profile email offline_access api - AUTH_REDIRECT_URI=/auth - AUTH_SILENT_REDIRECT_URI=/silent-auth - NETBIRD_TOKEN_SOURCE=accessToken # SSL - NGINX_SSL_PORT=443 # Letsencrypt - LETSENCRYPT_DOMAIN= - LETSENCRYPT_EMAIL= volumes: - netbird-letsencrypt:/etc/letsencrypt/ # Signal signal: <<: *default image: netbirdio/signal:latest volumes: - netbird-signal:/var/lib/netbird ports: - 10000:80 # Relay relay: <<: *default image: netbirdio/relay:latest environment: - NB_LOG_LEVEL=info - NB_LISTEN_ADDRESS=:33080 - NB_EXPOSED_ADDRESS=rels://netbird.example.com:33080/relay # todo: change to a secure secret - NB_AUTH_SECRET=example ports: - 33080:33080 # Management management: <<: *default image: netbirdio/management:latest depends_on: - dashboard volumes: - netbird-mgmt:/var/lib/netbird - netbird-letsencrypt:/etc/letsencrypt:ro - ./management.json:/etc/netbird/management.json ports: - 9444:443 #API port # # command for Let's Encrypt validation without dashboard container # command: ["--letsencrypt-domain", "", "--log-file", "console"] command: [ "--port", "443", "--log-file", "console", "--log-level", "info", "--disable-anonymous-metrics=false", "--single-account-mode-domain=netbird.example.comm", "--dns-domain=netbird.selfhosted" ] environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN= - NETBIRD_STORE_ENGINE_MYSQL_DSN= # Coturn coturn: <<: *default image: coturn/coturn:latest volumes: - ./turnserver.conf:/etc/turnserver.conf:ro network_mode: host environment: - REALM=netbird.example.com - LISTEN_PORT=3478 - TLS_LISTEN_PORT=5349 - CERT_FILE=/etc/letsencrypt/live/example.com/fullchain.pem - KEY_FILE=/etc/letsencrypt/live/example.com/privkey.pem command: - -c /etc/turnserver.conf volumes: netbird-mgmt: netbird-signal: netbird-letsencrypt: ``` management.json ``` { "Stuns": [ { "Proto": "udp", "URI": "stun:netbird.example.com:3478", "Username": "", "Password": "" } ], "TURNConfig": { "TimeBasedCredentials": false, "CredentialsTTL": "12h0m0s", "Secret": "secret", "Turns": [ { "Proto": "udp", "URI": "turn:netbird.example.com:3478", "Username": "self", "Password": "example" } ] }, "Relay": { "Addresses": [ "rels://netbird.example.com:33080/relay" ], "CredentialsTTL": "24h0m0s", "Secret": "example" }, "Signal": { "Proto": "http", "URI": "netbird.example.com:10000", "Username": "", "Password": "" }, "Datadir": "/var/lib/netbird/", "DataStoreEncryptionKey": "example=", "HttpConfig": { "LetsEncryptDomain": "", "CertFile": "", "CertKey": "", "AuthAudience": "1234", "AuthIssuer": "https://zitadel.example.com", "AuthUserIDClaim": "", "AuthKeysLocation": "https://zitadel.example.com/oauth/v2/keys", "OIDCConfigEndpoint": "https://zitadel.example.com/.well-known/openid-configuration", "IdpSignKeyRefreshEnabled": true, "ExtraAuthAudience": "" }, "IdpManagerConfig": { "ManagerType": "zitadel", "ClientConfig": { "Issuer": "https://zitadel.example.com", "TokenEndpoint": "https://zitadel.example.com/oauth/v2/token", "ClientID": "netbird", "ClientSecret": "example", "GrantType": "client_credentials" }, "ExtraConfig": { "AdminEndpoint": "https://zitadel.example.com/management/v1", "ManagementEndpoint": "https://zitadel.example.com/management/v1" }, "Auth0ClientCredentials": null, "AzureClientCredentials": null, "KeycloakClientCredentials": null, "ZitadelClientCredentials": null }, "DeviceAuthorizationFlow": { "Provider": "hosted", "ProviderConfig": { "ClientID": "1234", "ClientSecret": "", "Domain": "zitadel.example.com", "Audience": "1234", "TokenEndpoint": "https://zitadel.example.com/oauth/v2/token", "DeviceAuthEndpoint": "https://zitadel.example.com/oauth/v2/device_authorization", "AuthorizationEndpoint": "", "Scope": "openid profile email offline_access", "UseIDToken": false, "RedirectURLs": null, "DisablePromptLogin": false, "LoginFlag": 0 } }, "PKCEAuthorizationFlow": { "ProviderConfig": { "ClientID": "1234", "ClientSecret": "example", "Domain": "", "Audience": "335978769598709762", "TokenEndpoint": "https://zitadel.example.com/oauth/v2/token", "DeviceAuthEndpoint": "", "AuthorizationEndpoint": "https://zitadel.example.com/oauth/v2/authorize", "Scope": "openid profile email offline_access api", "UseIDToken": false, "RedirectURLs": [ "http://localhost:53000" ], "DisablePromptLogin": false, "LoginFlag": 0 } }, "StoreConfig": { "Engine": "sqlite" }, "ReverseProxy": { "TrustedHTTPProxies": [], "TrustedHTTPProxiesCount": 0, "TrustedPeers": [ "0.0.0.0/0" ] }, "DisableDefaultPolicy": true } ```
saavagebueno added the triage-needed label 2025-11-20 07:06:38 -05:00
Author
Owner

@wooksta commented on GitHub (Sep 11, 2025):

I'm having a similar issue where the webui works, but I can't get android or linux to connect.

I did notice the following in your setup:
your management is running on port 443 (and I didn't see anything to indicate you turned off TLS).
Meanwhile your redirect goes to http?

@wooksta commented on GitHub (Sep 11, 2025): I'm having a similar issue where the webui works, but I can't get android or linux to connect. I did notice the following in your setup: your management is running on port 443 (and I didn't see anything to indicate you turned off TLS). Meanwhile your redirect goes to http?
Author
Owner

@teksrq commented on GitHub (Sep 12, 2025):

Yes, TLS is terminated at the proxy, couldn't get it to work passing through the TLS to the containers. I believe this is the root cause of my relay issues.

@teksrq commented on GitHub (Sep 12, 2025): Yes, TLS is terminated at the proxy, couldn't get it to work passing through the TLS to the containers. I believe this is the root cause of my relay issues.
Author
Owner

@wooksta commented on GitHub (Sep 12, 2025):

Yes, TLS is terminated at the proxy, couldn't get it to work passing through the TLS to the containers. I believe this is the root cause of my relay issues

So i solved my issue and it turned out to be a docker problem. Docker set up my firewalls and didn't forward packets correctly to the servers. Maybe that's your issue too?

@wooksta commented on GitHub (Sep 12, 2025): > Yes, TLS is terminated at the proxy, couldn't get it to work passing through the TLS to the containers. I believe this is the root cause of my relay issues So i solved my issue and it turned out to be a docker problem. Docker set up my firewalls and didn't forward packets correctly to the servers. Maybe that's your issue too?
Author
Owner

@faizansirajuddin commented on GitHub (Sep 14, 2025):

Is that working for you?

I'm trying similar setup with Netbird, Ngingx Proxy and Authentik and still could not able to make it working

@faizansirajuddin commented on GitHub (Sep 14, 2025): Is that working for you? I'm trying similar setup with Netbird, Ngingx Proxy and Authentik and still could not able to make it working
Author
Owner

@wooksta commented on GitHub (Sep 14, 2025):

Is that working for you?

I'm trying similar setup with Netbird, Ngingx Proxy and Authentik and still could not able to make it working

So I got it working in the end. I figured it out when I did netbird up on the host Maschine with localhost as the target. When that worked but via nginx didn't, I figured out the problem was on the nginx end. I can post my nginx config if you want.

@wooksta commented on GitHub (Sep 14, 2025): > Is that working for you? > > I'm trying similar setup with Netbird, Ngingx Proxy and Authentik and still could not able to make it working So I got it working in the end. I figured it out when I did netbird up on the host Maschine with localhost as the target. When that worked but via nginx didn't, I figured out the problem was on the nginx end. I can post my nginx config if you want.
Author
Owner

@faizansirajuddin commented on GitHub (Sep 14, 2025):

Sure. Please

So for me I have a NPM and Authentik installed in different machine than netbird. and I'm trying to get netbird work with reverse proxy.

thanks

@faizansirajuddin commented on GitHub (Sep 14, 2025): Sure. Please So for me I have a NPM and Authentik installed in different machine than netbird. and I'm trying to get netbird work with reverse proxy. thanks
Author
Owner

@wooksta commented on GitHub (Sep 14, 2025):

So I have NPM (network mode: host) and netbird (own network) running on one machine in docker, and I have my keycloak running on docker on another server.

You have npm and your idp on host (B) and netbird on host (A).

`# ------------------------------------------------------------

netbird..net

------------------------------------------------------------

log_format upstreamlog '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream: $upstream_addr, response_time: $upstream_response_time, '
'status: $upstream_status';

access_log /var/log/nginx/upstream_access.log upstreamlog;

upstream dashboard {
server 127.0.0.1:10080;
keepalive 10;
}

upstream management {
server 127.0.0.1:33073;
keepalive 10;
}

upstream signal {
server 127.0.0.1:10001;
keepalive 10;
}

upstream relay {
server 127.0.0.1:33080;
}

server {
listen 80;
server_name netbird..net;

# 301 redirect to HTTPS
location / {
        return 301 https://$host$request_uri;
}

}
server {
listen 443 ssl http2;
server_name netbird..net;

# Let's Encrypt SSL
ssl_certificate /etc/letsencrypt/live/npm-4/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/npm-4/privkey.pem;

access_log /data/logs/proxy-host-netbird_access.log proxy;
error_log /data/logs/proxy-host-netbird_error.log warn;

client_header_timeout 1d;
client_body_timeout 1d;

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;

# Proxy dashboard
location / {
    proxy_pass http://dashboard;
}

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

# Proxy Management http endpoint
location /api {
    proxy_pass http://management;
    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;    }

location /api {

grpc_pass grpc://127.0.0.1:33073;

error_page 502 = /errorgrpc_management;

}

# Proxy Management grpc endpoint
location /management.ManagementService/ {
    grpc_pass grpc://management;
    #grpc_ssl_verify off;
    grpc_read_timeout 1d;
    grpc_send_timeout 1d;
    grpc_socket_keepalive on;
}

# Relay for headless peers
location /relay {
    grpc_pass grpc://relay;
    grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    grpc_set_header X-Forwarded-Proto $scheme;
    grpc_read_timeout 1d;
    grpc_send_timeout 1d;
    grpc_socket_keepalive on;
}

}`

This seems to work fine. As mentioned, try connecting to netbird through something that doesn't have to go through npm to see if npm is the problem or your config of netbird is wrong.

Where I have localhost, you would have to setup the connection to the machine with netbird (A) on it.
If that machine (A) is exposed to the internet, you might consider installing an npm on that machine too. Because I have npm and netbird on the same machine, I can just pass http from npm to netbird. If you are using anything outside a private network (like at your home) I would use https to connect from npm to netbird.
If you put npm on that host (A), you can just pass ssl from (B) to (A) and have http internal.

@wooksta commented on GitHub (Sep 14, 2025): So I have NPM (network mode: host) and netbird (own network) running on one machine in docker, and I have my keycloak running on docker on another server. You have npm and your idp on host (B) and netbird on host (A). `# ------------------------------------------------------------ # netbird.<redacted>.net # ------------------------------------------------------------ log_format upstreamlog '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' 'upstream: $upstream_addr, response_time: $upstream_response_time, ' 'status: $upstream_status'; access_log /var/log/nginx/upstream_access.log upstreamlog; upstream dashboard { server 127.0.0.1:10080; keepalive 10; } upstream management { server 127.0.0.1:33073; keepalive 10; } upstream signal { server 127.0.0.1:10001; keepalive 10; } upstream relay { server 127.0.0.1:33080; } server { listen 80; server_name netbird.<redacted>.net; # 301 redirect to HTTPS location / { return 301 https://$host$request_uri; } } server { listen 443 ssl http2; server_name netbird.<redacted>.net; # Let's Encrypt SSL ssl_certificate /etc/letsencrypt/live/npm-4/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/npm-4/privkey.pem; access_log /data/logs/proxy-host-netbird_access.log proxy; error_log /data/logs/proxy-host-netbird_error.log warn; client_header_timeout 1d; client_body_timeout 1d; 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; # Proxy dashboard location / { proxy_pass http://dashboard; } # Proxy Signal location /signalexchange.SignalExchange/ { grpc_pass grpc://signal; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } # Proxy Management http endpoint location /api { proxy_pass http://management; 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; } # location /api { # grpc_pass grpc://127.0.0.1:33073; # error_page 502 = /errorgrpc_management; # } # Proxy Management grpc endpoint location /management.ManagementService/ { grpc_pass grpc://management; #grpc_ssl_verify off; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } # Relay for headless peers location /relay { grpc_pass grpc://relay; grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; grpc_set_header X-Forwarded-Proto $scheme; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } }` This seems to work fine. As mentioned, try connecting to netbird through something that doesn't have to go through npm to see if npm is the problem or your config of netbird is wrong. Where I have localhost, you would have to setup the connection to the machine with netbird (A) on it. If that machine (A) is exposed to the internet, you might consider installing an npm on that machine too. Because I have npm and netbird on the same machine, I can just pass http from npm to netbird. If you are using anything outside a private network (like at your home) I would use https to connect from npm to netbird. If you put npm on that host (A), you can just pass ssl from (B) to (A) and have http internal.
Author
Owner

@faizansirajuddin commented on GitHub (Sep 14, 2025):

Hi Thanks you.

Ealier I tried using direct setup without proxy with zitadel auth provider and it was working fine without any issues. but now with authntik and NMP is creating problems.

I'm pasting my configureation see if you can help me out.

upon visiting netbird.doamin.com I can able to authnticate with auththentik and it is redirecting to netbird peer page

and there it is being stucked and keep load. I have notice one error on netbird side also see

------------------------------------------------------------

netbird.example.com

------------------------------------------------------------

map $scheme $hsts_header {
https "max-age=63072000; preload";
}

server {
set $server "192.168.1.10"; # <-- NetBird host IP

listen 80;
listen [::]:80;

listen 443 ssl;
listen [::]:443 ssl;
http2 on;

server_name netbird.example.com;

ssl_certificate /etc/letsencrypt/live/npm-3/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/npm-3/privkey.pem;

include conf.d/include/letsencrypt-acme-challenge.conf;
include conf.d/include/ssl-cache.conf;
include conf.d/include/ssl-ciphers.conf;
include conf.d/include/block-exploits.conf;
include conf.d/include/force-ssl.conf;

access_log /data/logs/proxy-host-11_access.log proxy;
error_log /data/logs/proxy-host-11_error.log warn;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;

Dashboard

location / {
proxy_pass http://$server: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;
}

Management API (HTTP)

location /api {
proxy_pass http://$server:33073;
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;
}

Management gRPC

location /management.ManagementService/ {
grpc_pass grpc://$server:33073;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
}

Signal gRPC

location /signalexchange.SignalExchange/ {
grpc_pass grpc://$server:10000;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
}

Relay

location /relay/ {
proxy_pass http://$server:33080;
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;
}

Custom

include /data/nginx/custom/server_proxy[.]conf;
}

This error I'm getting on netbird side when starting docker compose

management-1 | 2025-09-14T11:53:05Z WARN management/server/account.go:250: failed warming up cache due to error: 403 Forbidden

@faizansirajuddin commented on GitHub (Sep 14, 2025): Hi Thanks you. Ealier I tried using direct setup without proxy with zitadel auth provider and it was working fine without any issues. but now with authntik and NMP is creating problems. I'm pasting my configureation see if you can help me out. upon visiting netbird.doamin.com I can able to authnticate with auththentik and it is redirecting to netbird peer page and there it is being stucked and keep load. I have notice one error on netbird side also see # ------------------------------------------------------------ # netbird.example.com # ------------------------------------------------------------ map $scheme $hsts_header { https "max-age=63072000; preload"; } server { set $server "192.168.1.10"; # <-- NetBird host IP listen 80; listen [::]:80; listen 443 ssl; listen [::]:443 ssl; http2 on; server_name netbird.example.com; ssl_certificate /etc/letsencrypt/live/npm-3/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/npm-3/privkey.pem; include conf.d/include/letsencrypt-acme-challenge.conf; include conf.d/include/ssl-cache.conf; include conf.d/include/ssl-ciphers.conf; include conf.d/include/block-exploits.conf; include conf.d/include/force-ssl.conf; access_log /data/logs/proxy-host-11_access.log proxy; error_log /data/logs/proxy-host-11_error.log warn; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_http_version 1.1; # Dashboard location / { proxy_pass http://$server: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; } # Management API (HTTP) location /api { proxy_pass http://$server:33073; 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; } # Management gRPC location /management.ManagementService/ { grpc_pass grpc://$server:33073; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } # Signal gRPC location /signalexchange.SignalExchange/ { grpc_pass grpc://$server:10000; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } # Relay location /relay/ { proxy_pass http://$server:33080; 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; } # Custom include /data/nginx/custom/server_proxy[.]conf; } #### This error I'm getting on netbird side when starting docker compose management-1 | 2025-09-14T11:53:05Z WARN management/server/account.go:250: failed warming up cache due to error: 403 Forbidden
Author
Owner

@wooksta commented on GitHub (Sep 14, 2025):

So, all I saw was that I set grpc for the relay location, and you use http, that might be it.

I think the cache might be unrelated, butsomething you should look up.

@wooksta commented on GitHub (Sep 14, 2025): So, all I saw was that I set grpc for the relay location, and you use http, that might be it. I think the cache might be unrelated, butsomething you should look up.
Author
Owner

@faizansirajuddin commented on GitHub (Sep 14, 2025):

So after inspecting http request I got to know that I'm getting SSL PROTOCOL ERROR while connecting to management port 33073

8786-6cab84d1f1050c71.js:1 GET https://netbird.example.com:33073/api/users/current net::ERR_SSL_PROTOCOL_ERROR

So in my setup requests to the port 80,443 is availble through NPM and rest port request can bypass proxy and can be directly rechable.

Here are my containers

NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
artifacts-coturn-1 coturn/coturn:latest "docker-entrypoint.s…" coturn 3 minutes ago Up 3 minutes
artifacts-dashboard-1 netbirdio/dashboard:latest "/usr/bin/supervisor…" dashboard 3 minutes ago Up 3 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp
artifacts-management-1 netbirdio/management:latest "/go/bin/netbird-mgm…" management 3 minutes ago Up 3 minutes 0.0.0.0:33073->443/tcp, [::]:33073->443/tcp
artifacts-relay-1 netbirdio/relay:latest "/go/bin/netbird-rel…" relay 3 minutes ago Up 3 minutes 0.0.0.0:33080->33080/tcp, [::]:33080->33080/tcp
artifacts-signal-1 netbirdio/signal:latest "/go/bin/netbird-sig…" signal 3 minutes ago Up 3 minutes 0.0.0.0:10000->80/tcp, [::]:10000->80/tcp
[root@netbird artifacts]#

@faizansirajuddin commented on GitHub (Sep 14, 2025): So after inspecting http request I got to know that I'm getting SSL PROTOCOL ERROR while connecting to management port 33073 8786-6cab84d1f1050c71.js:1 GET https://netbird.example.com:33073/api/users/current net::ERR_SSL_PROTOCOL_ERROR So in my setup requests to the port 80,443 is availble through NPM and rest port request can bypass proxy and can be directly rechable. Here are my containers NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS artifacts-coturn-1 coturn/coturn:latest "docker-entrypoint.s…" coturn 3 minutes ago Up 3 minutes artifacts-dashboard-1 netbirdio/dashboard:latest "/usr/bin/supervisor…" dashboard 3 minutes ago Up 3 minutes 0.0.0.0:80->80/tcp, [::]:80->80/tcp, 0.0.0.0:443->443/tcp, [::]:443->443/tcp artifacts-management-1 netbirdio/management:latest "/go/bin/netbird-mgm…" management 3 minutes ago Up 3 minutes 0.0.0.0:33073->443/tcp, [::]:33073->443/tcp artifacts-relay-1 netbirdio/relay:latest "/go/bin/netbird-rel…" relay 3 minutes ago Up 3 minutes 0.0.0.0:33080->33080/tcp, [::]:33080->33080/tcp artifacts-signal-1 netbirdio/signal:latest "/go/bin/netbird-sig…" signal 3 minutes ago Up 3 minutes 0.0.0.0:10000->80/tcp, [::]:10000->80/tcp [root@netbird artifacts]#
Author
Owner

@wooksta commented on GitHub (Sep 15, 2025):

it sounds like your backend is expecting SSL, which it shouldn't according to your config. I think you might have to try to work on that.

@wooksta commented on GitHub (Sep 15, 2025): it sounds like your backend is expecting SSL, which it shouldn't according to your config. I think you might have to try to work on that.
Author
Owner

@faizansirajuddin commented on GitHub (Sep 15, 2025):

Thanks man @wooksta for your support. now it is working for me as well.

@faizansirajuddin commented on GitHub (Sep 15, 2025): Thanks man @wooksta for your support. now it is working for me as well.
Author
Owner

@teksrq commented on GitHub (Sep 15, 2025):

Glad you both got it working, but just want to clarify that my issue is a bit different from what wooksta fixed with container name resolution.

In my setup:

Management and Signal are running fine behind Nginx with TLS.

Relay is also behind Nginx (terminating TLS), and peers are correctly receiving the advertised relay address (rels://netbird.example.com:33080/relay).

Peers can reach the relay, but the connection fails with errors like:

http: server gave HTTP response to HTTPS client
tls: first record does not look like a TLS handshake

Relevant UFW Firewall rules:

  • TCP 33080 open for relay.

  • UDP 3478 open for STUN/TURN.

  • UDP 49152–65535 open for WebRTC media

This looks like a protocol mismatch — the peer expects TLS/WebSocket, but the relay behind Nginx isn’t handling the handshake correctly.

So unlike wooksta’s case (container DNS/hostname discovery), my relay is being discovered, but TLS/WebSocket negotiation fails.

Question:
Does NetBird officially support running the relay behind an Nginx reverse proxy with TLS termination? If so, an example Nginx config that correctly handles rels:// WebSocket traffic would be greatly appreciated!

@teksrq commented on GitHub (Sep 15, 2025): Glad you both got it working, but just want to clarify that my issue is a bit different from what wooksta fixed with container name resolution. In my setup: Management and Signal are running fine behind Nginx with TLS. Relay is also behind Nginx (terminating TLS), and peers are correctly receiving the advertised relay address (rels://netbird.example.com:33080/relay). Peers can reach the relay, but the connection fails with errors like: http: server gave HTTP response to HTTPS client tls: first record does not look like a TLS handshake Relevant UFW Firewall rules: - TCP 33080 open for relay. - UDP 3478 open for STUN/TURN. - UDP 49152–65535 open for WebRTC media This looks like a protocol mismatch — the peer expects TLS/WebSocket, but the relay behind Nginx isn’t handling the handshake correctly. So unlike wooksta’s case (container DNS/hostname discovery), my relay is being discovered, but TLS/WebSocket negotiation fails. Question: Does NetBird officially support running the relay behind an Nginx reverse proxy with TLS termination? If so, an example Nginx config that correctly handles rels:// WebSocket traffic would be greatly appreciated!
Author
Owner

@faizansirajuddin commented on GitHub (Sep 15, 2025):

Yes Netbird is supported behind the reverse proxy. but you need to terminal all you connection except stun to the proxy.

then proxy will forward it to servers.

what you need to do is while configuring environment file set following things.

Set NETBIRD_DOMAIN to your domain, e.g. demo.netbird.io
Set NETBIRD_DISABLE_LETSENCRYPT=true
Add NETBIRD_MGMT_API_PORT to your reverse-proxy TLS-port (default: 443) ## I have set to 443
Add NETBIRD_SIGNAL_PORT to your reverse-proxy TLS-port ### I have set to 443
Optional:

Add TURN_MIN_PORT and TURN_MAX_PORT to configure the port-range used by the Turn-server (min 10000 max 20000)

when you run configure script it will generate management URLs pointing to the proxy. https://netbird.example.com

at this stag if you run docker compose then it will break as there are conflicting ports so have to change outside ports to non conflicting ports. do not change inside ports.

then configure proxy accordingly. here config from my Nginx proxy manager it is based fully on nginx so configuration will be same.

------------------------------------------------------------

netbird.example.com

------------------------------------------------------------

map $scheme $hsts_header {
https "max-age=63072000; preload";
}

server {
set $forward_scheme http;
set $server "192.168.1.10";
set $port 80;

listen 80;
listen [::]:80;

listen 443 ssl;
listen [::]:443 ssl;

server_name netbird.example.com;

http2 on;

Let's Encrypt SSL

include conf.d/include/letsencrypt-acme-challenge.conf;
include conf.d/include/ssl-cache.conf;
include conf.d/include/ssl-ciphers.conf;
ssl_certificate /etc/letsencrypt/live/npm-3/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/npm-3/privkey.pem;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;

access_log /data/logs/proxy-host-11_access.log proxy;
error_log /data/logs/proxy-host-11_error.log warn;

location / {

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto  $scheme;
proxy_set_header X-Forwarded-For    $remote_addr;
proxy_set_header X-Real-IP          $remote_addr;

proxy_pass       http://192.168.1.10:80;












proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;

}

location /api {

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto  $scheme;
proxy_set_header X-Forwarded-For    $remote_addr;
proxy_set_header X-Real-IP          $remote_addr;

proxy_pass       http://192.168.1.10:443;












proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;

}

location /signalexchange.SignalExchange/ {
location /signalexchange.SignalExchange/ {
grpc_pass grpc://192.168.1.10:9443;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
}

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto  $scheme;
proxy_set_header X-Forwarded-For    $remote_addr;
proxy_set_header X-Real-IP          $remote_addr;

proxy_pass       http://192.168.1.10:9443;












proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;

}

location /management.ManagementService/ {
location /management.ManagementService/ {
grpc_pass grpc://192.168.1.10:443;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
}

proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto  $scheme;
proxy_set_header X-Forwarded-For    $remote_addr;
proxy_set_header X-Real-IP          $remote_addr;

proxy_pass       http://192.168.1.10:443;












proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_http_version 1.1;

}

Custom

include /data/nginx/custom/server_proxy[.]conf;
}

and here is docker compose includes on ports and env setting linking to proxy config

services:

UI dashboard

dashboard:
<<: *default
image: netbirdio/dashboard:latest
ports:
- 80:80
- 8443:443

environment:
  # Endpoints
  - NETBIRD_MGMT_API_ENDPOINT=https://netbird.example.com:443
  - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.example.com:443

Signal

signal:
<<: *default
image: netbirdio/signal:latest
volumes:
- netbird-signal:/var/lib/netbird
ports:
- 9443:80

Relay

relay:
<<: *default
image: netbirdio/relay:latest
environment:
- NB_LOG_LEVEL=info
- NB_LISTEN_ADDRESS=:33080
- NB_EXPOSED_ADDRESS=rels://netbird.example.com:33080/relay

Management

management:
<<: *default
image: netbirdio/management:latest
depends_on:
- dashboard
volumes:
- netbird-mgmt:/var/lib/netbird
- netbird-letsencrypt:/etc/letsencrypt:ro
- ./management.json:/etc/netbird/management.json
ports:
- 443:443 #API port

@faizansirajuddin commented on GitHub (Sep 15, 2025): Yes Netbird is supported behind the reverse proxy. but you need to terminal all you connection except stun to the proxy. then proxy will forward it to servers. what you need to do is while configuring environment file set following things. Set NETBIRD_DOMAIN to your domain, e.g. demo.netbird.io Set NETBIRD_DISABLE_LETSENCRYPT=true Add NETBIRD_MGMT_API_PORT to your reverse-proxy TLS-port (default: 443) ## I have set to 443 Add NETBIRD_SIGNAL_PORT to your reverse-proxy TLS-port ### I have set to 443 Optional: Add TURN_MIN_PORT and TURN_MAX_PORT to configure the port-range used by the Turn-server (min 10000 max 20000) when you run configure script it will generate management URLs pointing to the proxy. https://netbird.example.com at this stag if you run docker compose then it will break as there are conflicting ports so have to change outside ports to non conflicting ports. do not change inside ports. then configure proxy accordingly. here config from my Nginx proxy manager it is based fully on nginx so configuration will be same. # ------------------------------------------------------------ # netbird.example.com # ------------------------------------------------------------ map $scheme $hsts_header { https "max-age=63072000; preload"; } server { set $forward_scheme http; set $server "192.168.1.10"; set $port 80; listen 80; listen [::]:80; listen 443 ssl; listen [::]:443 ssl; server_name netbird.example.com; http2 on; # Let's Encrypt SSL include conf.d/include/letsencrypt-acme-challenge.conf; include conf.d/include/ssl-cache.conf; include conf.d/include/ssl-ciphers.conf; ssl_certificate /etc/letsencrypt/live/npm-3/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/npm-3/privkey.pem; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_http_version 1.1; access_log /data/logs/proxy-host-11_access.log proxy; error_log /data/logs/proxy-host-11_error.log warn; location / { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://192.168.1.10:80; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_http_version 1.1; } location /api { proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://192.168.1.10:443; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_http_version 1.1; } location /signalexchange.SignalExchange/ { location /signalexchange.SignalExchange/ { grpc_pass grpc://192.168.1.10:9443; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://192.168.1.10:9443; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_http_version 1.1; } location /management.ManagementService/ { location /management.ManagementService/ { grpc_pass grpc://192.168.1.10:443; grpc_read_timeout 1d; grpc_send_timeout 1d; grpc_socket_keepalive on; } proxy_set_header Host $host; proxy_set_header X-Forwarded-Scheme $scheme; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://192.168.1.10:443; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_http_version 1.1; } # Custom include /data/nginx/custom/server_proxy[.]conf; } # and here is docker compose includes on ports and env setting linking to proxy config services: # UI dashboard dashboard: <<: *default image: netbirdio/dashboard:latest ports: - 80:80 - 8443:443 environment: # Endpoints - NETBIRD_MGMT_API_ENDPOINT=https://netbird.example.com:443 - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.example.com:443 # Signal signal: <<: *default image: netbirdio/signal:latest volumes: - netbird-signal:/var/lib/netbird ports: - 9443:80 # Relay relay: <<: *default image: netbirdio/relay:latest environment: - NB_LOG_LEVEL=info - NB_LISTEN_ADDRESS=:33080 - NB_EXPOSED_ADDRESS=rels://netbird.example.com:33080/relay # Management management: <<: *default image: netbirdio/management:latest depends_on: - dashboard volumes: - netbird-mgmt:/var/lib/netbird - netbird-letsencrypt:/etc/letsencrypt:ro - ./management.json:/etc/netbird/management.json ports: - 443:443 #API port
Author
Owner

@wooksta commented on GitHub (Sep 19, 2025):

@faizansirajuddin hey am I seeing that right? your nginx doesn't proxy the relay, you have an open port for that?

also how do you deal with SSL? can you configure ssl just for relay?

@wooksta commented on GitHub (Sep 19, 2025): @faizansirajuddin hey am I seeing that right? your nginx doesn't proxy the relay, you have an open port for that? also how do you deal with SSL? can you configure ssl just for relay?
Author
Owner

@faizansirajuddin commented on GitHub (Sep 19, 2025):

Yes, I did not proxied the relay as netbird document does not mention it and opened port for that. as far as I know relay is being protected by wiregurd tunnel so your don't need SSL on relay.

In relay case your tunnel is hopping through your netbird server.

@faizansirajuddin commented on GitHub (Sep 19, 2025): Yes, I did not proxied the relay as netbird document does not mention it and opened port for that. as far as I know relay is being protected by wiregurd tunnel so your don't need SSL on relay. In relay case your tunnel is hopping through your netbird server.
Author
Owner

@CustomIcon commented on GitHub (Sep 23, 2025):

Im doing the same for now. relay is not UDP as far as i know. rest is working without port forwarding and only using nginx ports

@CustomIcon commented on GitHub (Sep 23, 2025): Im doing the same for now. relay is not UDP as far as i know. rest is working without port forwarding and only using nginx ports
Author
Owner

@ykorzikowski commented on GitHub (Nov 19, 2025):

Documentation is kind of slim here. I checked the source code:

You need to set the following Headers:

        proxy_set_header X-Real-Ip $remote_addr;
        proxy_set_header X-Real-Port $remote_port;

See https://github.com/netbirdio/netbird/blob/v0.60.0/relay/server/listener/ws/listener.go#L112

Now I see the correct IP adresses (not the local docker one) in my relay logs

@ykorzikowski commented on GitHub (Nov 19, 2025): Documentation is kind of slim here. I checked the source code: You need to set the following Headers: ``` proxy_set_header X-Real-Ip $remote_addr; proxy_set_header X-Real-Port $remote_port; ``` See https://github.com/netbirdio/netbird/blob/v0.60.0/relay/server/listener/ws/listener.go#L112 Now I see the correct IP adresses (not the local docker one) in my relay logs
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: SVI/netbird#2260