[GH-ISSUE #4972] Relay handshake failure: unable to validate peer signature #10447

Open
opened 2026-08-05 01:25:56 -04:00 by saavagebueno · 0 comments
Owner

Originally created by @imgios on GitHub (Dec 18, 2025).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/4972

The relay is unable to validate a peer signature with the following error:

2025-12-18T17:57:34Z INFO relay/server/listener/ws/listener.go:105: WS client connected from: 172.18.0.2:47080

2025-12-18T17:57:34Z ERRO relay/server/relay.go:130: failed to handshake: validate sha-nsQpmLr8mDqk+H4Dcuh/pvmxCum8frgy77d3C1Ok85A= (172.18.0.2:47080): invalid signature

This issue is replicated for each peer in the network. In this setup, there's a VM with the following apps deployed as containers:

  • Traefik (manages the ports 80 and 443)
  • Keycloak
  • Netbird

The VM has rules to expose the following ports: 80, 443, 3478.

Netbird 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
    environment:
      # Endpoints
      - NETBIRD_MGMT_API_ENDPOINT=https://vpn.redacted.tld:443
      - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://vpn.redacted.tld:443
      # OIDC
      - AUTH_AUDIENCE=netbird-client
      - AUTH_CLIENT_ID=netbird-client
      - AUTH_CLIENT_SECRET=<SECRET>
      - AUTH_AUTHORITY=https://auth.vps.redacted.tld/realms/netbird
      - 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=
    networks:
      - traefik_default
    volumes:
      - netbird-letsencrypt:/etc/letsencrypt/
    labels:
    - traefik.enable=true
    - traefik.http.routers.netbird-dashboard.rule=Host(`vpn.redacted.tld`)
    - traefik.http.routers.netbird-dashboard.entrypoints=websecure
    - traefik.http.routers.netbird-dashboard.tls=true
    - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt
    - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80

  # Signal
  signal:
    <<: *default
    image: netbirdio/signal:latest
    volumes:
      - netbird-signal:/var/lib/netbird
    networks:
      - traefik_default
    labels:
    - traefik.enable=true
    - traefik.http.routers.netbird-wsproxy-signal.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/ws-proxy/signal`)
    - traefik.http.routers.netbird-wsproxy-signal.entrypoints=websecure
    - traefik.http.routers.netbird-wsproxy-signal.tls=true
    - traefik.http.routers.netbird-wsproxy-signal.service=netbird-wsproxy-signal
    - traefik.http.services.netbird-wsproxy-signal.loadbalancer.server.port=80
    - traefik.http.routers.netbird-signal.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/signalexchange.SignalExchange/`)
    - traefik.http.routers.netbird-signal.entrypoints=websecure
    - traefik.http.routers.netbird-signal.tls=true
    - traefik.http.routers.netbird-signal.service=netbird-signal
    - traefik.http.services.netbird-signal.loadbalancer.server.port=10000
    - traefik.http.services.netbird-signal.loadbalancer.server.scheme=h2c

  # Relay
  relay:
    <<: *default
    image: netbirdio/relay:latest
    environment:
    - NB_LOG_LEVEL=info
    - NB_LISTEN_ADDRESS=:33080
    - NB_EXPOSED_ADDRESS=rels://vpn.redacted.tld:443/relay
    - NB_AUTH_SECRET=<SECRET>
    networks:
      - traefik_default
    labels:
    - traefik.enable=true
    - traefik.http.routers.netbird-relay.rule=Host(`vpn.redacted.tld`) && 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=33080
    - traefik.docker.network=traefik_default
    - traefik.http.routers.netbird-relay.middlewares=netbird-relay-ws
    - traefik.http.middlewares.netbird-relay-ws.headers.customrequestheaders.Connection=Upgrade
    - traefik.http.middlewares.netbird-relay-ws.headers.customrequestheaders.Upgrade=websocket

  # Management
  management:
    <<: *default
    image: netbirdio/management:latest
    depends_on:
      - dashboard
    volumes:
      - netbird-mgmt:/var/lib/netbird
      - netbird-letsencrypt:/etc/letsencrypt:ro
      - /netbird/infrastructure_files/artifacts/management.json:/etc/netbird/management.json
    command: [
      "--port", "33073",
      "--log-file", "console",
      "--log-level", "info",
      "--disable-anonymous-metrics=true",
      "--single-account-mode-domain=vpn.redacted.tld",
      "--dns-domain=netbird.redacted.vpn"
      ]
    networks:
      - traefik_default
    labels:
    - traefik.enable=true
    - traefik.http.routers.netbird-api.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/api`)
    - traefik.http.routers.netbird-api.entrypoints=websecure
    - traefik.http.routers.netbird-api.tls=true
    - traefik.http.routers.netbird-api.service=netbird-api
    - traefik.http.services.netbird-api.loadbalancer.server.port=33073
    - traefik.http.routers.netbird-wsproxy-mgmt.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/ws-proxy/management`)
    - traefik.http.routers.netbird-wsproxy-mgmt.entrypoints=websecure
    - traefik.http.routers.netbird-wsproxy-mgmt.service=netbird-wsproxy-mgmt
    - traefik.http.services.netbird-wsproxy-mgmt.loadbalancer.server.port=33073
    - traefik.http.routers.netbird-management.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/management.ManagementService/`)
    - traefik.http.routers.netbird-management.entrypoints=websecure
    - traefik.http.routers.netbird-management.tls=true
    - traefik.http.routers.netbird-management.service=netbird-management
    - traefik.http.services.netbird-management.loadbalancer.server.port=33073
    - traefik.http.services.netbird-management.loadbalancer.server.scheme=h2c
    environment:
      - NETBIRD_STORE_ENGINE_POSTGRES_DSN=
      - NETBIRD_STORE_ENGINE_MYSQL_DSN=

  # Coturn
  coturn:
    <<: *default
    image: coturn/coturn:latest
    domainname: vpn.redacted.tld
    volumes:
      - /netbird/infrastructure_files/artifacts/turnserver.conf:/etc/turnserver.conf:ro
    network_mode: host
    command:
      - -c /etc/turnserver.conf

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

networks:
  traefik_default:
    external: true
Netbird management.json
{
    "Stuns": [
        {
            "Proto": "udp",
            "URI": "stun:vpn.redacted.tld:3478",
            "Username": "",
            "Password": ""
        }
    ],
    "TURNConfig": {
        "TimeBasedCredentials": false,
        "CredentialsTTL": "12h0m0s",
        "Secret": "secret",
        "Turns": [
            {
                "Proto": "udp",
                "URI": "turn:vpn.redacted.tld:3478",
                "Username": "self",
                "Password": "PASSWORD"
            }
        ]
    },
    "Relay": {
        "Addresses": [
            "rels://vpn.redacted.tld:443/relay"
        ],
        "CredentialsTTL": "24h0m0s",
        "Secret": "SECRET"
    },
    "Signal": {
        "Proto": "https",
        "URI": "vpn.redacted.tld:443",
        "Username": "",
        "Password": ""
    },
    "Datadir": "/var/lib/netbird/",
    "DataStoreEncryptionKey": "EncryptionKey",
    "HttpConfig": {
        "LetsEncryptDomain": "",
        "CertFile": "",
        "CertKey": "",
        "AuthAudience": "netbird-client",
        "AuthIssuer": "https://auth.redacted.tld/realms/netbird",
        "AuthUserIDClaim": "",
        "AuthKeysLocation": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/certs",
        "OIDCConfigEndpoint": "https://auth.redacted.tld/realms/netbird/.well-known/openid-configuration",
        "IdpSignKeyRefreshEnabled": false,
        "ExtraAuthAudience": ""
    },
    "IdpManagerConfig": {
        "ManagerType": "keycloak",
        "ClientConfig": {
            "Issuer": "https://auth.redacted.tld/realms/netbird",
            "TokenEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/token",
            "ClientID": "netbird-backend",
            "ClientSecret": "SECRET",
            "GrantType": "client_credentials"
        },
        "ExtraConfig": {
            "AdminEndpoint": "https://auth.redacted.tld/admin/realms/netbird"
        },
        "Auth0ClientCredentials": null,
        "AzureClientCredentials": null,
        "KeycloakClientCredentials": null,
        "ZitadelClientCredentials": null
    },
    "DeviceAuthorizationFlow": {
        "Provider": "hosted",
        "ProviderConfig": {
            "ClientID": "netbird-client",
            "ClientSecret": "",
            "Domain": "auth.redacted.tld",
            "Audience": "netbird-client",
            "TokenEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/token",
            "DeviceAuthEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/auth/device",
            "AuthorizationEndpoint": "",
            "Scope": "openid",
            "UseIDToken": false,
            "RedirectURLs": null,
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "PKCEAuthorizationFlow": {
        "ProviderConfig": {
            "ClientID": "netbird-client",
            "ClientSecret": "",
            "Domain": "",
            "Audience": "netbird-client",
            "TokenEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/token",
            "DeviceAuthEndpoint": "",
            "AuthorizationEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/auth",
            "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 @imgios on GitHub (Dec 18, 2025). Original GitHub issue: https://github.com/netbirdio/netbird/issues/4972 The relay is unable to validate a peer signature with the following error: ``` 2025-12-18T17:57:34Z INFO relay/server/listener/ws/listener.go:105: WS client connected from: 172.18.0.2:47080 2025-12-18T17:57:34Z ERRO relay/server/relay.go:130: failed to handshake: validate sha-nsQpmLr8mDqk+H4Dcuh/pvmxCum8frgy77d3C1Ok85A= (172.18.0.2:47080): invalid signature ``` This issue is replicated for each peer in the network. In this setup, there's a VM with the following apps deployed as containers: - Traefik (manages the ports 80 and 443) - Keycloak - Netbird The VM has rules to expose the following ports: `80`, `443`, `3478`. <details> <summary>Netbird Docker Compose</summary> ```yaml 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 environment: # Endpoints - NETBIRD_MGMT_API_ENDPOINT=https://vpn.redacted.tld:443 - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://vpn.redacted.tld:443 # OIDC - AUTH_AUDIENCE=netbird-client - AUTH_CLIENT_ID=netbird-client - AUTH_CLIENT_SECRET=<SECRET> - AUTH_AUTHORITY=https://auth.vps.redacted.tld/realms/netbird - 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= networks: - traefik_default volumes: - netbird-letsencrypt:/etc/letsencrypt/ labels: - traefik.enable=true - traefik.http.routers.netbird-dashboard.rule=Host(`vpn.redacted.tld`) - traefik.http.routers.netbird-dashboard.entrypoints=websecure - traefik.http.routers.netbird-dashboard.tls=true - traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt - traefik.http.services.netbird-dashboard.loadbalancer.server.port=80 # Signal signal: <<: *default image: netbirdio/signal:latest volumes: - netbird-signal:/var/lib/netbird networks: - traefik_default labels: - traefik.enable=true - traefik.http.routers.netbird-wsproxy-signal.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/ws-proxy/signal`) - traefik.http.routers.netbird-wsproxy-signal.entrypoints=websecure - traefik.http.routers.netbird-wsproxy-signal.tls=true - traefik.http.routers.netbird-wsproxy-signal.service=netbird-wsproxy-signal - traefik.http.services.netbird-wsproxy-signal.loadbalancer.server.port=80 - traefik.http.routers.netbird-signal.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/signalexchange.SignalExchange/`) - traefik.http.routers.netbird-signal.entrypoints=websecure - traefik.http.routers.netbird-signal.tls=true - traefik.http.routers.netbird-signal.service=netbird-signal - traefik.http.services.netbird-signal.loadbalancer.server.port=10000 - traefik.http.services.netbird-signal.loadbalancer.server.scheme=h2c # Relay relay: <<: *default image: netbirdio/relay:latest environment: - NB_LOG_LEVEL=info - NB_LISTEN_ADDRESS=:33080 - NB_EXPOSED_ADDRESS=rels://vpn.redacted.tld:443/relay - NB_AUTH_SECRET=<SECRET> networks: - traefik_default labels: - traefik.enable=true - traefik.http.routers.netbird-relay.rule=Host(`vpn.redacted.tld`) && 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=33080 - traefik.docker.network=traefik_default - traefik.http.routers.netbird-relay.middlewares=netbird-relay-ws - traefik.http.middlewares.netbird-relay-ws.headers.customrequestheaders.Connection=Upgrade - traefik.http.middlewares.netbird-relay-ws.headers.customrequestheaders.Upgrade=websocket # Management management: <<: *default image: netbirdio/management:latest depends_on: - dashboard volumes: - netbird-mgmt:/var/lib/netbird - netbird-letsencrypt:/etc/letsencrypt:ro - /netbird/infrastructure_files/artifacts/management.json:/etc/netbird/management.json command: [ "--port", "33073", "--log-file", "console", "--log-level", "info", "--disable-anonymous-metrics=true", "--single-account-mode-domain=vpn.redacted.tld", "--dns-domain=netbird.redacted.vpn" ] networks: - traefik_default labels: - traefik.enable=true - traefik.http.routers.netbird-api.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/api`) - traefik.http.routers.netbird-api.entrypoints=websecure - traefik.http.routers.netbird-api.tls=true - traefik.http.routers.netbird-api.service=netbird-api - traefik.http.services.netbird-api.loadbalancer.server.port=33073 - traefik.http.routers.netbird-wsproxy-mgmt.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/ws-proxy/management`) - traefik.http.routers.netbird-wsproxy-mgmt.entrypoints=websecure - traefik.http.routers.netbird-wsproxy-mgmt.service=netbird-wsproxy-mgmt - traefik.http.services.netbird-wsproxy-mgmt.loadbalancer.server.port=33073 - traefik.http.routers.netbird-management.rule=Host(`vpn.redacted.tld`) && PathPrefix(`/management.ManagementService/`) - traefik.http.routers.netbird-management.entrypoints=websecure - traefik.http.routers.netbird-management.tls=true - traefik.http.routers.netbird-management.service=netbird-management - traefik.http.services.netbird-management.loadbalancer.server.port=33073 - traefik.http.services.netbird-management.loadbalancer.server.scheme=h2c environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN= - NETBIRD_STORE_ENGINE_MYSQL_DSN= # Coturn coturn: <<: *default image: coturn/coturn:latest domainname: vpn.redacted.tld volumes: - /netbird/infrastructure_files/artifacts/turnserver.conf:/etc/turnserver.conf:ro network_mode: host command: - -c /etc/turnserver.conf volumes: netbird-mgmt: netbird-signal: netbird-letsencrypt: networks: traefik_default: external: true ``` </details> <details> <summary>Netbird <code>management.json</code></summary> ```json { "Stuns": [ { "Proto": "udp", "URI": "stun:vpn.redacted.tld:3478", "Username": "", "Password": "" } ], "TURNConfig": { "TimeBasedCredentials": false, "CredentialsTTL": "12h0m0s", "Secret": "secret", "Turns": [ { "Proto": "udp", "URI": "turn:vpn.redacted.tld:3478", "Username": "self", "Password": "PASSWORD" } ] }, "Relay": { "Addresses": [ "rels://vpn.redacted.tld:443/relay" ], "CredentialsTTL": "24h0m0s", "Secret": "SECRET" }, "Signal": { "Proto": "https", "URI": "vpn.redacted.tld:443", "Username": "", "Password": "" }, "Datadir": "/var/lib/netbird/", "DataStoreEncryptionKey": "EncryptionKey", "HttpConfig": { "LetsEncryptDomain": "", "CertFile": "", "CertKey": "", "AuthAudience": "netbird-client", "AuthIssuer": "https://auth.redacted.tld/realms/netbird", "AuthUserIDClaim": "", "AuthKeysLocation": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/certs", "OIDCConfigEndpoint": "https://auth.redacted.tld/realms/netbird/.well-known/openid-configuration", "IdpSignKeyRefreshEnabled": false, "ExtraAuthAudience": "" }, "IdpManagerConfig": { "ManagerType": "keycloak", "ClientConfig": { "Issuer": "https://auth.redacted.tld/realms/netbird", "TokenEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/token", "ClientID": "netbird-backend", "ClientSecret": "SECRET", "GrantType": "client_credentials" }, "ExtraConfig": { "AdminEndpoint": "https://auth.redacted.tld/admin/realms/netbird" }, "Auth0ClientCredentials": null, "AzureClientCredentials": null, "KeycloakClientCredentials": null, "ZitadelClientCredentials": null }, "DeviceAuthorizationFlow": { "Provider": "hosted", "ProviderConfig": { "ClientID": "netbird-client", "ClientSecret": "", "Domain": "auth.redacted.tld", "Audience": "netbird-client", "TokenEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/token", "DeviceAuthEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/auth/device", "AuthorizationEndpoint": "", "Scope": "openid", "UseIDToken": false, "RedirectURLs": null, "DisablePromptLogin": false, "LoginFlag": 0 } }, "PKCEAuthorizationFlow": { "ProviderConfig": { "ClientID": "netbird-client", "ClientSecret": "", "Domain": "", "Audience": "netbird-client", "TokenEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/token", "DeviceAuthEndpoint": "", "AuthorizationEndpoint": "https://auth.redacted.tld/realms/netbird/protocol/openid-connect/auth", "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 } ``` </details>
saavagebueno added the triage-needed label 2026-08-05 01:25:56 -04:00
Sign in to join this conversation.
No Label triage-needed
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#10447