[GH-ISSUE #4829] [SOLVED] Cannot connect to network after login. Authentik/PocketID + Caddy #9407

Closed
opened 2026-08-05 01:22:00 -04:00 by saavagebueno · 7 comments
Owner

Originally created by @afonsofrancof on GitHub (Nov 20, 2025).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/4829

I setup everything, and after logging in, it stays on "connecting" forever.
The clients say they authenticated successfully, but I get this error in the management container (which makes me think that the clients falsely believe they are authenticated):

2025-11-21T01:16:59Z WARN [requestID: 850f2233-1ba3-43c4-8ae2-159db24915b6, accountID: UNKNOWN, peerID: 1mbbYRtGPw1dQ1riMlbOxjcTWM1jyTKtMfRA171SQUo=, context: GRPC] management/internals/shared/grpc/server.go:598: failed logging in peer 1mbbYRtGPw1dQ1riMlbOxjcTWM1jyTKtMfRA171SQUo=: no peer auth method provided, please use a setup key or interactive SSO login

Compose file

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
    networks:
      - default
      - caddy_proxy
    expose:
      - 80
    environment:
      # Endpoints
      - NETBIRD_MGMT_API_ENDPOINT=https://netbird.domain.tld:443
      - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.domain.tld:443
      # OIDC
      - AUTH_AUDIENCE=<CLIENT_ID>
      - AUTH_CLIENT_ID=<CLIENT_ID>
      - AUTH_CLIENT_SECRET=
      - AUTH_AUTHORITY=https://authentik.domain.tld/application/o/net-bird/
      - 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=

  signal:
    <<: *default
    image: netbirdio/signal:latest
    depends_on:
          - dashboard
    volumes:
      - /home/user/netbird/signal:/var/lib/netbird
    networks:
      - default
      - caddy_proxy
    expose:
      - 80
    command: [
      "--cert-file", "",
      "--cert-key", "",
      "--log-file", "console"
    ]

  # Relay
  relay:
    <<: *default
    image: netbirdio/relay:latest
    environment:
    - NB_LOG_LEVEL=info
    - NB_LISTEN_ADDRESS=0.0.0.0:80
    - NB_EXPOSED_ADDRESS=rels://netbird.domain.tld:443/relay
    - NB_AUTH_SECRET=<REDACTED_SECRET>
    networks:
      - default
      - caddy_proxy
    expose:
      - 80

  management:
    <<: *default
    image: netbirdio/management:latest
    depends_on:
      - dashboard
    volumes:
      - /home/user/netbird/mgmt:/var/lib/netbird
      - /home/user/netbird/conf/management.json:/etc/netbird/management.json
    networks:
      - default
      - caddy_proxy
    expose:
      - 80
    command: [
      "--port", "80",
      "--log-file", "console",
      "--log-level", "info",
      "--disable-anonymous-metrics=false",
      "--single-account-mode-domain=netbird.domain.tld",
      "--dns-domain=netbird.selfhosted"
      ]
    environment:
      - NETBIRD_STORE_ENGINE_POSTGRES_DSN=
      - NETBIRD_STORE_ENGINE_MYSQL_DSN=


networks:
  caddy_proxy:
    external: true
    name: "caddy_proxy"

Management Config

{
    "Stuns": [],
    "TURNConfig": {
        "TimeBasedCredentials": false,
        "CredentialsTTL": "12h0m0s",
        "Secret": "secret",
        "Turns": []
    },
    "Relay": {
        "Addresses": [
            "rels://netbird.domain.tld:443/relay"
        ],
        "CredentialsTTL": "24h0m0s",
        "Secret": "<REDACTED_SECRET>"
    },
    "Signal": {
        "Proto": "http",
        "URI": "netbird.domain.tld:443",
        "Username": "",
        "Password": ""
    },
    "Datadir": "/var/lib/netbird/",
    "DataStoreEncryptionKey": "<REDACTED_KEY>",
    "HttpConfig": {
        "LetsEncryptDomain": "",
        "CertFile": "",
        "CertKey": "",
        "AuthAudience": "<CLIENT_ID>",
        "AuthIssuer": "https://authentik.domain.tld/application/o/net-bird/",
        "AuthUserIDClaim": "",
        "AuthKeysLocation": "https://authentik.domain.tld/application/o/net-bird/jwks/",
        "OIDCConfigEndpoint": "https://authentik.domain.tld/application/o/net-bird/.well-known/openid-configuration",
        "IdpSignKeyRefreshEnabled": false,
        "ExtraAuthAudience": ""
    },
    "IdpManagerConfig": {
        "ManagerType": "authentik",
        "ClientConfig": {
            "Issuer": "https://authentik.domain.tld/application/o/net-bird/",
            "TokenEndpoint": "https://authentik.domain.tld/application/o/token/",
            "ClientID": "<CLIENT_ID>",
            "ClientSecret": "",
            "GrantType": "client_credentials"
        },
        "ExtraConfig": {
            "Password": "<APP_PASSWORD_GENERATED_ON_AUTHENTIK>",
            "Username": "Netbird"
        },
        "Auth0ClientCredentials": null,
        "AzureClientCredentials": null,
        "KeycloakClientCredentials": null,
        "ZitadelClientCredentials": null
    },
    "DeviceAuthorizationFlow": {
        "Provider": "hosted",
        "ProviderConfig": {
            "ClientID": "<CLIENT_ID>",
            "ClientSecret": "",
            "Domain": "authentik.domain.tld",
            "Audience": "<CLIENT_ID>",
            "TokenEndpoint": "https://authentik.domain.tld/application/o/token/",
            "DeviceAuthEndpoint": "https://authentik.domain.tld/application/o/device/",
            "AuthorizationEndpoint": "",
            "Scope": "openid",
            "UseIDToken": false,
            "RedirectURLs": null,
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "PKCEAuthorizationFlow": {
        "ProviderConfig": {
            "ClientID": "<CLIENT_ID>",
            "ClientSecret": "",
            "Domain": "",
            "Audience": "<CLIENT_ID>",
            "TokenEndpoint": "https://authentik.domain.tld/application/o/token/",
            "DeviceAuthEndpoint": "",
            "AuthorizationEndpoint": "https://authentik.domain.tld/application/o/authorize/",
            "Scope": "openid profile email offline_access api",
            "UseIDToken": false,
            "RedirectURLs": [
                "http://localhost:53000"
            ],
            "DisablePromptLogin": true,
            "LoginFlag": 0
        }
    },
    "StoreConfig": {
        "Engine": "sqlite"
    },
    "ReverseProxy": {
        "TrustedHTTPProxies": [],
        "TrustedHTTPProxiesCount": 0,
        "TrustedPeers": [
            "0.0.0.0/0"
        ]
    },
    "DisableDefaultPolicy": false
}

Caddyfile

netbird.domain.tld {

        reverse_proxy /* dashboard

        reverse_proxy /signalexchange.SignalExchange/* h2c://signal

        reverse_proxy /ws-proxy/signal signal

        reverse_proxy /management.ManagementService/* h2c://management

        reverse_proxy /ws-proxy/management management

        reverse_proxy /api* management

        reverse_proxy /relay* relay
}

I have no idea what else to do.
I followed the docs exactly to create the service accounts and app password and device flows, etc. I also followed Authentik's guide for NetBird which mentioned some things that NetBird doesn't mention in its docs.

Originally created by @afonsofrancof on GitHub (Nov 20, 2025). Original GitHub issue: https://github.com/netbirdio/netbird/issues/4829 I setup everything, and after logging in, it stays on "connecting" forever. The clients say they authenticated successfully, but I get this error in the management container (which makes me think that the clients falsely believe they are authenticated): `2025-11-21T01:16:59Z WARN [requestID: 850f2233-1ba3-43c4-8ae2-159db24915b6, accountID: UNKNOWN, peerID: 1mbbYRtGPw1dQ1riMlbOxjcTWM1jyTKtMfRA171SQUo=, context: GRPC] management/internals/shared/grpc/server.go:598: failed logging in peer 1mbbYRtGPw1dQ1riMlbOxjcTWM1jyTKtMfRA171SQUo=: no peer auth method provided, please use a setup key or interactive SSO login` ### Compose file ```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 networks: - default - caddy_proxy expose: - 80 environment: # Endpoints - NETBIRD_MGMT_API_ENDPOINT=https://netbird.domain.tld:443 - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.domain.tld:443 # OIDC - AUTH_AUDIENCE=<CLIENT_ID> - AUTH_CLIENT_ID=<CLIENT_ID> - AUTH_CLIENT_SECRET= - AUTH_AUTHORITY=https://authentik.domain.tld/application/o/net-bird/ - 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= signal: <<: *default image: netbirdio/signal:latest depends_on: - dashboard volumes: - /home/user/netbird/signal:/var/lib/netbird networks: - default - caddy_proxy expose: - 80 command: [ "--cert-file", "", "--cert-key", "", "--log-file", "console" ] # Relay relay: <<: *default image: netbirdio/relay:latest environment: - NB_LOG_LEVEL=info - NB_LISTEN_ADDRESS=0.0.0.0:80 - NB_EXPOSED_ADDRESS=rels://netbird.domain.tld:443/relay - NB_AUTH_SECRET=<REDACTED_SECRET> networks: - default - caddy_proxy expose: - 80 management: <<: *default image: netbirdio/management:latest depends_on: - dashboard volumes: - /home/user/netbird/mgmt:/var/lib/netbird - /home/user/netbird/conf/management.json:/etc/netbird/management.json networks: - default - caddy_proxy expose: - 80 command: [ "--port", "80", "--log-file", "console", "--log-level", "info", "--disable-anonymous-metrics=false", "--single-account-mode-domain=netbird.domain.tld", "--dns-domain=netbird.selfhosted" ] environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN= - NETBIRD_STORE_ENGINE_MYSQL_DSN= networks: caddy_proxy: external: true name: "caddy_proxy" ``` ### Management Config ```json { "Stuns": [], "TURNConfig": { "TimeBasedCredentials": false, "CredentialsTTL": "12h0m0s", "Secret": "secret", "Turns": [] }, "Relay": { "Addresses": [ "rels://netbird.domain.tld:443/relay" ], "CredentialsTTL": "24h0m0s", "Secret": "<REDACTED_SECRET>" }, "Signal": { "Proto": "http", "URI": "netbird.domain.tld:443", "Username": "", "Password": "" }, "Datadir": "/var/lib/netbird/", "DataStoreEncryptionKey": "<REDACTED_KEY>", "HttpConfig": { "LetsEncryptDomain": "", "CertFile": "", "CertKey": "", "AuthAudience": "<CLIENT_ID>", "AuthIssuer": "https://authentik.domain.tld/application/o/net-bird/", "AuthUserIDClaim": "", "AuthKeysLocation": "https://authentik.domain.tld/application/o/net-bird/jwks/", "OIDCConfigEndpoint": "https://authentik.domain.tld/application/o/net-bird/.well-known/openid-configuration", "IdpSignKeyRefreshEnabled": false, "ExtraAuthAudience": "" }, "IdpManagerConfig": { "ManagerType": "authentik", "ClientConfig": { "Issuer": "https://authentik.domain.tld/application/o/net-bird/", "TokenEndpoint": "https://authentik.domain.tld/application/o/token/", "ClientID": "<CLIENT_ID>", "ClientSecret": "", "GrantType": "client_credentials" }, "ExtraConfig": { "Password": "<APP_PASSWORD_GENERATED_ON_AUTHENTIK>", "Username": "Netbird" }, "Auth0ClientCredentials": null, "AzureClientCredentials": null, "KeycloakClientCredentials": null, "ZitadelClientCredentials": null }, "DeviceAuthorizationFlow": { "Provider": "hosted", "ProviderConfig": { "ClientID": "<CLIENT_ID>", "ClientSecret": "", "Domain": "authentik.domain.tld", "Audience": "<CLIENT_ID>", "TokenEndpoint": "https://authentik.domain.tld/application/o/token/", "DeviceAuthEndpoint": "https://authentik.domain.tld/application/o/device/", "AuthorizationEndpoint": "", "Scope": "openid", "UseIDToken": false, "RedirectURLs": null, "DisablePromptLogin": false, "LoginFlag": 0 } }, "PKCEAuthorizationFlow": { "ProviderConfig": { "ClientID": "<CLIENT_ID>", "ClientSecret": "", "Domain": "", "Audience": "<CLIENT_ID>", "TokenEndpoint": "https://authentik.domain.tld/application/o/token/", "DeviceAuthEndpoint": "", "AuthorizationEndpoint": "https://authentik.domain.tld/application/o/authorize/", "Scope": "openid profile email offline_access api", "UseIDToken": false, "RedirectURLs": [ "http://localhost:53000" ], "DisablePromptLogin": true, "LoginFlag": 0 } }, "StoreConfig": { "Engine": "sqlite" }, "ReverseProxy": { "TrustedHTTPProxies": [], "TrustedHTTPProxiesCount": 0, "TrustedPeers": [ "0.0.0.0/0" ] }, "DisableDefaultPolicy": false } ``` ### Caddyfile ```caddyfile netbird.domain.tld { reverse_proxy /* dashboard reverse_proxy /signalexchange.SignalExchange/* h2c://signal reverse_proxy /ws-proxy/signal signal reverse_proxy /management.ManagementService/* h2c://management reverse_proxy /ws-proxy/management management reverse_proxy /api* management reverse_proxy /relay* relay } ``` I have no idea what else to do. I followed the docs exactly to create the service accounts and app password and device flows, etc. I also followed Authentik's guide for NetBird which mentioned some things that NetBird doesn't mention in its docs.
saavagebueno added the triage-needed label 2026-08-05 01:22:00 -04:00
Author
Owner

@afonsofrancof commented on GitHub (Nov 21, 2025):

Btw, today I switched to Pocket ID and I'm still getting the same error.

(I also added a Turn/Stun server just in case, but still nothing)

<!-- gh-comment-id:3565439670 --> @afonsofrancof commented on GitHub (Nov 21, 2025): Btw, today I switched to Pocket ID and I'm still getting the same error. (I also added a Turn/Stun server just in case, but still nothing)
Author
Owner

@cantelope commented on GitHub (Nov 22, 2025):

I got my netbird + authentik + caddy setup working, but unfortunately I don't know enough about this voodoo to help you troubleshoot.

Perhaps what I can offer is this snippet of my caddyfile:

    @netbird host netbird.domain.tld
    handle @netbird {
            import security_headers
            reverse_proxy /* NetBird-Dashboard:80
            reverse_proxy /signalexchange.SignalExchange/* h2c://NetBird-Signal
            reverse_proxy /api/* NetBird-Management:443
            reverse_proxy /management.ManagementService/* NetBird-Management:443 {
                    transport http {
                            versions h2c
                    }
            }
    }
    @netbird-signal host netbird-signal.domain.tld
    handle @netbird-signal {
            import security_headers
            reverse_proxy h2c://NetBird-Signal
    }

    @netbird-relay host netbird-relay.domain.tld
    handle @netbird-relay {
            import security_headers
            reverse_proxy NetBird-Relay
    }
<!-- gh-comment-id:3566713809 --> @cantelope commented on GitHub (Nov 22, 2025): I got my netbird + authentik + caddy setup working, but unfortunately I don't know enough about this voodoo to help you troubleshoot. Perhaps what I can offer is this snippet of my caddyfile: > @netbird host netbird.domain.tld > handle @netbird { > import security_headers > reverse_proxy /* NetBird-Dashboard:80 > reverse_proxy /signalexchange.SignalExchange/* h2c://NetBird-Signal > reverse_proxy /api/* NetBird-Management:443 > reverse_proxy /management.ManagementService/* NetBird-Management:443 { > transport http { > versions h2c > } > } > } > @netbird-signal host netbird-signal.domain.tld > handle @netbird-signal { > import security_headers > reverse_proxy h2c://NetBird-Signal > } > > @netbird-relay host netbird-relay.domain.tld > handle @netbird-relay { > import security_headers > reverse_proxy NetBird-Relay > }
Author
Owner

@afonsofrancof commented on GitHub (Nov 22, 2025):

@cantelope Unfortunately I think my caddy setup is working and the problem is somewhere else.
A big thank you for trying to help me fix this ❤️

<!-- gh-comment-id:3566883106 --> @afonsofrancof commented on GitHub (Nov 22, 2025): @cantelope Unfortunately I think my caddy setup is working and the problem is somewhere else. A big thank you for trying to help me fix this ❤️
Author
Owner

@hc-nolan commented on GitHub (Nov 22, 2025):

I also had this issue and it took me all day to figure out. TL;DR I found #4762 and after making the changes it recommends, everything worked. However, I ended up removing Caddy from my setup before it started working, so still not entirely sure if this is the same issue as when Caddy is in the mix. More details below.


I ended up deciding to remove Caddy from the equation to simplify things. I followed the advanced self hosting setup guide on an AWS EC2 instance, and at the end I was left with the same issue.

From another GitHub issue (I forget which now) I found this command to run from the client:

sudo netbird service stop
sudo bash -c 'GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info netbird up --management-url https://netbird.... --setup-key ... -F -l debug'

When I was using Caddy, the client would get this error:

2025-11-22T15:10:09-05:00 INFO ./caller_not_available:0: 2025/11/22 15:10:09 INFO: [transport] [client-transport 0xc000c98008] Closing: connection error: desc = "error reading server preface: EOF"

After removing Caddy and deploying on AWS, I instead got:

2025-11-22T19:37:52-05:00 INFO ./caller_not_available:0: 2025/11/22 19:37:52 WARNING: [core] [Channel #20 SubChannel #21]grpc: addrConn.createTransport failed to connect to {Addr: "netbird.domain.tld:10000", ServerName: "netbird.domain.tld:10000", BalancerAttributes: {"<%!p(pickfirstleaf.managedByPickfirstKeyType={})>": "<%!p(bool=true)>" }}. Err: connection error: desc = "transport: Error while dialing: nbnet.NewDialer().DialContext: dial tcp x.x.x.x:10000: connect: connection refused"

This was what led me to the issue mentioned above. Once I fixed the port mapping in docker-compose.yml, everything worked as expected.

<!-- gh-comment-id:3567242359 --> @hc-nolan commented on GitHub (Nov 22, 2025): I also had this issue and it took me all day to figure out. TL;DR I found #4762 and after making the changes it recommends, everything worked. However, I ended up removing Caddy from my setup before it started working, so still not entirely sure if this is the same issue as when Caddy is in the mix. More details below. --- I ended up deciding to remove Caddy from the equation to simplify things. I followed the advanced self hosting setup guide on an AWS EC2 instance, and at the end I was left with the same issue. From another GitHub issue (I forget which now) I found this command to run from the client: ```shell sudo netbird service stop sudo bash -c 'GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info netbird up --management-url https://netbird.... --setup-key ... -F -l debug' ``` When I was using Caddy, the client would get this error: ```shell 2025-11-22T15:10:09-05:00 INFO ./caller_not_available:0: 2025/11/22 15:10:09 INFO: [transport] [client-transport 0xc000c98008] Closing: connection error: desc = "error reading server preface: EOF" ``` After removing Caddy and deploying on AWS, I instead got: ```shell 2025-11-22T19:37:52-05:00 INFO ./caller_not_available:0: 2025/11/22 19:37:52 WARNING: [core] [Channel #20 SubChannel #21]grpc: addrConn.createTransport failed to connect to {Addr: "netbird.domain.tld:10000", ServerName: "netbird.domain.tld:10000", BalancerAttributes: {"<%!p(pickfirstleaf.managedByPickfirstKeyType={})>": "<%!p(bool=true)>" }}. Err: connection error: desc = "transport: Error while dialing: nbnet.NewDialer().DialContext: dial tcp x.x.x.x:10000: connect: connection refused" ``` This was what led me to the issue mentioned above. Once I fixed the port mapping in `docker-compose.yml`, everything worked as expected.
Author
Owner

@hc-nolan commented on GitHub (Nov 22, 2025):

Finally got it working with Caddy! There was a second isssue in addition to the one mentioned in my previous comment.

After updating the signal port mapping in docker-compose.yml to 10000:10000, I still had the same connection issue as before. I finally tracked it down to the Proto line in management.json:

    "Signal": {
        "Proto": "http",
        "URI": "netbird.domain.tld:443",
        "Username": "",
        "Password": ""
    },

Changed http to https and everything worked! Below are all my config files in case they are helpful for others.

Caddyfile
{
        ...
        servers :443 {
                protocols h1 h2c h2 h3
        }
}

(security_headers) {
        header * {
                # enable HSTS
                # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#strict-transport-security-hsts
                # NOTE: Read carefully how this header works before using it.
                # If the HSTS header is misconfigured or if there is a problem with
                # the SSL/TLS certificate being used, legitimate users might be unable
                # to access the website. For example, if the HSTS header is set to a
                # very long duration and the SSL/TLS certificate expires or is revoked,
                # legitimate users might be unable to access the website until
                # the HSTS header duration has expired.
                # The recommended value for the max-age is 2 year (63072000 seconds).
                # But we are using 1 hour (3600 seconds) for testing purposes
                # and ensure that the website is working properly before setting
                # to two years.

                Strict-Transport-Security "max-age=3600; includeSubDomains; preload"

                # disable clients from sniffing the media type
                # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-content-type-options
                X-Content-Type-Options "nosniff"

                # clickjacking protection
                # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options
                X-Frame-Options "SAMEORIGIN"

                # xss protection
                # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-xss-protection
                X-XSS-Protection "1; mode=block"

                # Remove -Server header, which is an information leak
                # Remove Caddy from Headers
                -Server

                # keep referrer data off of HTTP connections
                # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#referrer-policy
                Referrer-Policy strict-origin-when-cross-origin
        }
}

netbird.hnolan.ca {
        import security_headers

        reverse_proxy /signalexchange.SignalExchange/* h2c://vpnhost:10000
        reverse_proxy /management.ManagementService/* h2c://vpnhost:33073

        reverse_proxy /ws-proxy/signal* vpnhost:10000
        reverse_proxy /relay* vpnhost:33080

        reverse_proxy /api* vpnhost:33073
        reverse_proxy /ws-proxy/management* vpnhost:33073

        reverse_proxy /* vpnhost:80
}

Note: vpnhost is a separate VM on the same LAN as Caddy's VM.

setup.env
# Dashboard domain. e.g. app.mydomain.com
NETBIRD_DOMAIN="netbird.domain.tld"

# TURN server domain. e.g. turn.mydomain.com
# if not specified it will assume NETBIRD_DOMAIN
NETBIRD_TURN_DOMAIN=""

# TURN server public IP address
# required for a connection involving peers in
# the same network as the server and external peers
# usually matches the IP for the domain set in NETBIRD_TURN_DOMAIN
NETBIRD_TURN_EXTERNAL_IP=""

# -------------------------------------------
# OIDC
#  e.g., https://example.eu.auth0.com/.well-known/openid-configuration
# -------------------------------------------
NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://authentik.domain.tld/application/o/netbird/.well-known/openid-configuration"
NETBIRD_USE_AUTH0=false
NETBIRD_AUTH_CLIENT_ID="<Authentik client ID>"
NETBIRD_AUTH_SUPPORTED_SCOPES="openid profile email offline_access api"
NETBIRD_AUTH_AUDIENCE="<Authentik client ID>"
NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID="<Authentik client ID>"
NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE="<Authentik client ID>"
NETBIRD_AUTH_REDIRECT_URI="/auth"
NETBIRD_AUTH_SILENT_REDIRECT_URI="/silent-auth"

NETBIRD_MGMT_IDP="authentik"
NETBIRD_IDP_MGMT_CLIENT_ID="<Authentik client ID>"
NETBIRD_IDP_MGMT_EXTRA_USERNAME="Netbird"
NETBIRD_IDP_MGMT_EXTRA_PASSWORD="<Authentik app password>"

# needs disabling due to issue with IdP. Learn more [here](https://github.com/netbirdio/netbird/issues/3654)
NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=true

# -------------------------------------------
# Letsencrypt
# -------------------------------------------
# Disable letsencrypt
#  if disabled, cannot use HTTPS anymore and requires setting up a reverse-proxy to do it instead
NETBIRD_DISABLE_LETSENCRYPT=true
# e.g. hello@mydomain.com
NETBIRD_LETSENCRYPT_EMAIL=""
# -------------------------------------------
# Extra settings
# -------------------------------------------
# Disable anonymous metrics collection, see more information at https://netbird.io/docs/FAQ/metrics-collection
NETBIRD_DISABLE_ANONYMOUS_METRICS=false
# DNS DOMAIN configures the domain name used for peer resolution. By default it is netbird.selfhosted
NETBIRD_MGMT_DNS_DOMAIN=netbird.domain.tld
# Disable default all-to-all policy for new accounts
NETBIRD_MGMT_DISABLE_DEFAULT_POLICY=false
# -------------------------------------------
# Relay settings
# -------------------------------------------
# Relay server domain. e.g. relay.mydomain.com
# if not specified it will assume NETBIRD_DOMAIN
NETBIRD_RELAY_DOMAIN=""

# Relay server connection port. If none is supplied
# it will default to 33080
# should be updated to match TLS-port of reverse proxy when netbird is running behind reverse proxy
NETBIRD_RELAY_PORT="443"

# Management API connecting port. If none is supplied
# it will default to 33073
# should be updated to match TLS-port of reverse proxy when netbird is running behind reverse proxy
NETBIRD_MGMT_API_PORT="443"

# Signal service connecting port. If none is supplied
# it will default to 10000
# should be updated to match TLS-port of reverse proxy when netbird is running behind reverse proxy
NETBIRD_SIGNAL_PORT="443"
docker-compose.yml
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:
      - 80:80
      - 443:443
    environment:
      # Endpoints
      - NETBIRD_MGMT_API_ENDPOINT=https://netbird.domain.tld:443
      - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.domain.tld:443
      # OIDC
      - AUTH_AUDIENCE=<Authentik client ID>
      - AUTH_CLIENT_ID=<Authentik client ID>
      - AUTH_CLIENT_SECRET=
      - AUTH_AUTHORITY=https://authentik.domain.tld/application/o/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=
    volumes:
      - netbird-letsencrypt:/etc/letsencrypt/

  # Signal
  signal:
    <<: *default
    image: netbirdio/signal:latest
    depends_on:
          - dashboard
    volumes:
      - netbird-signal:/var/lib/netbird
      - netbird-letsencrypt:/etc/letsencrypt:ro
    ports:
      - 10000:10000
  #      # port and command for Let's Encrypt validation
  #      - 443:443
  #    command: ["--letsencrypt-domain", "", "--log-file", "console"]
    command: [
      "--cert-file", "",
      "--cert-key", "",
      "--log-file", "console",
      "--log-level", "debug",
    ]

  # Relay
  relay:
    <<: *default
    image: netbirdio/relay:latest
    environment:
    - NB_LOG_LEVEL=info
    - NB_LISTEN_ADDRESS=:443
    - NB_EXPOSED_ADDRESS=rels://netbird.domain.tld:443/relay
    # todo: change to a secure secret
    - NB_AUTH_SECRET=<redacted>
    ports:
      - 33080:443

  # 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:
      - 33073:33073 #API port
  #    # command for Let's Encrypt validation without dashboard container
  #    command: ["--letsencrypt-domain", "", "--log-file", "console"]
    command: [
      "--port", "33073",
      "--log-file", "console",
      "--log-level", "debug",
      "--disable-anonymous-metrics=false",
      "--single-account-mode-domain=netbird.domain.tld",
      "--dns-domain=netbird.domain.tld"
      ]
    environment:
      - NETBIRD_STORE_ENGINE_POSTGRES_DSN=
      - NETBIRD_STORE_ENGINE_MYSQL_DSN=

  # Coturn
  coturn:
    <<: *default
    image: coturn/coturn:latest
    #domainname: netbird.hnolan.ca # only needed when TLS is enabled
    volumes:
      - ./turnserver.conf:/etc/turnserver.conf:ro
    #      - ./privkey.pem:/etc/coturn/private/privkey.pem:ro
    #      - ./cert.pem:/etc/coturn/certs/cert.pem:ro
    network_mode: host
    command:
      - -c /etc/turnserver.conf

volumes:
  netbird-mgmt:
  netbird-signal:
  netbird-letsencrypt:
management.json
{
    "Stuns": [
        {
            "Proto": "udp",
            "URI": "stun:netbird.domain.tld:3478",
            "Username": "",
            "Password": ""
        }
    ],
    "TURNConfig": {
        "TimeBasedCredentials": false,
        "CredentialsTTL": "12h0m0s",
        "Secret": "secret",
        "Turns": [
            {
                "Proto": "udp",
                "URI": "turn:netbird.domain.tld:3478",
                "Username": "self",
                "Password": "<redacted>"
            }
        ]
    },
    "Relay": {
        "Addresses": [
            "rels://netbird.domain.tld:443/relay"
        ],
        "CredentialsTTL": "24h0m0s",
        "Secret": "<redacted>"
    },
    "Signal": {
        "Proto": "https",
        "URI": "netbird.domain.tld:443",
        "Username": "",
        "Password": ""
    },
    "Datadir": "/var/lib/netbird/",
    "DataStoreEncryptionKey": "<redacted>",
    "HttpConfig": {
        "LetsEncryptDomain": "",
        "CertFile": "",
        "CertKey": "",
        "AuthAudience": "<Authentik client ID>",
        "AuthIssuer": "https://authentik.domain.tld/application/o/netbird/",
        "AuthUserIDClaim": "",
        "AuthKeysLocation": "https://authentik.domain.tld/application/o/netbird/jwks/",
        "OIDCConfigEndpoint": "https://authentik.domain.tld/application/o/netbird/.well-known/openid-configuration",
        "IdpSignKeyRefreshEnabled": false,
        "ExtraAuthAudience": ""
    },
    "IdpManagerConfig": {
        "ManagerType": "authentik",
        "ClientConfig": {
            "Issuer": "https://authentik.domain.tld/application/o/netbird",
            "TokenEndpoint": "https://authentik.domain.tld/application/o/token/",
            "ClientID": "<Authentik client ID>",
            "ClientSecret": "",
            "GrantType": "client_credentials"
        },
        "ExtraConfig": {
            "Password": "<Authentik app password>",
            "Username": "Netbird"
        },
        "Auth0ClientCredentials": null,
        "AzureClientCredentials": null,
        "KeycloakClientCredentials": null,
        "ZitadelClientCredentials": null
    },
    "DeviceAuthorizationFlow": {
        "Provider": "hosted",
        "ProviderConfig": {
            "ClientID": "<Authentik client ID>",
            "ClientSecret": "",
            "Domain": "authentik.domain.tld",
            "Audience": "<Authentik client ID>",
            "TokenEndpoint": "https://authentik.domain.tld/application/o/token/",
            "DeviceAuthEndpoint": "https://authentik.domain.tld/application/o/device/",
            "AuthorizationEndpoint": "",
            "Scope": "openid",
            "UseIDToken": false,
            "RedirectURLs": null,
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "PKCEAuthorizationFlow": {
        "ProviderConfig": {
            "ClientID": "<Authentik client ID>",
            "ClientSecret": "",
            "Domain": "",
            "Audience": "<Authentik client ID>",
            "TokenEndpoint": "https://authentik.domain.tld/application/o/token/",
            "DeviceAuthEndpoint": "",
            "AuthorizationEndpoint": "https://authentik.domain.tld/application/o/authorize/",
            "Scope": "openid profile email offline_access api",
            "UseIDToken": false,
            "RedirectURLs": [
                "http://localhost:53000"
            ],
            "DisablePromptLogin": true,
            "LoginFlag": 0
        }
    },
    "StoreConfig": {
        "Engine": "sqlite"
    },
    "ReverseProxy": {
        "TrustedHTTPProxies": [],
        "TrustedHTTPProxiesCount": 0,
        "TrustedPeers": [
            "0.0.0.0/0"
        ]
    },
    "DisableDefaultPolicy": false
}
<!-- gh-comment-id:3567313958 --> @hc-nolan commented on GitHub (Nov 22, 2025): Finally got it working with Caddy! There was a second isssue in addition to the one mentioned in my previous comment. After updating the signal port mapping in `docker-compose.yml` to `10000:10000`, I still had the same connection issue as before. I finally tracked it down to the `Proto` line in `management.json`: ```json "Signal": { "Proto": "http", "URI": "netbird.domain.tld:443", "Username": "", "Password": "" }, ``` Changed `http` to `https` and everything worked! Below are all my config files in case they are helpful for others. <details> <summary>Caddyfile</summary> ``` { ... servers :443 { protocols h1 h2c h2 h3 } } (security_headers) { header * { # enable HSTS # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#strict-transport-security-hsts # NOTE: Read carefully how this header works before using it. # If the HSTS header is misconfigured or if there is a problem with # the SSL/TLS certificate being used, legitimate users might be unable # to access the website. For example, if the HSTS header is set to a # very long duration and the SSL/TLS certificate expires or is revoked, # legitimate users might be unable to access the website until # the HSTS header duration has expired. # The recommended value for the max-age is 2 year (63072000 seconds). # But we are using 1 hour (3600 seconds) for testing purposes # and ensure that the website is working properly before setting # to two years. Strict-Transport-Security "max-age=3600; includeSubDomains; preload" # disable clients from sniffing the media type # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-content-type-options X-Content-Type-Options "nosniff" # clickjacking protection # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-frame-options X-Frame-Options "SAMEORIGIN" # xss protection # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-xss-protection X-XSS-Protection "1; mode=block" # Remove -Server header, which is an information leak # Remove Caddy from Headers -Server # keep referrer data off of HTTP connections # https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#referrer-policy Referrer-Policy strict-origin-when-cross-origin } } netbird.hnolan.ca { import security_headers reverse_proxy /signalexchange.SignalExchange/* h2c://vpnhost:10000 reverse_proxy /management.ManagementService/* h2c://vpnhost:33073 reverse_proxy /ws-proxy/signal* vpnhost:10000 reverse_proxy /relay* vpnhost:33080 reverse_proxy /api* vpnhost:33073 reverse_proxy /ws-proxy/management* vpnhost:33073 reverse_proxy /* vpnhost:80 } ``` Note: `vpnhost` is a separate VM on the same LAN as Caddy's VM. </details> <details> <summary>setup.env</summary> ```env # Dashboard domain. e.g. app.mydomain.com NETBIRD_DOMAIN="netbird.domain.tld" # TURN server domain. e.g. turn.mydomain.com # if not specified it will assume NETBIRD_DOMAIN NETBIRD_TURN_DOMAIN="" # TURN server public IP address # required for a connection involving peers in # the same network as the server and external peers # usually matches the IP for the domain set in NETBIRD_TURN_DOMAIN NETBIRD_TURN_EXTERNAL_IP="" # ------------------------------------------- # OIDC # e.g., https://example.eu.auth0.com/.well-known/openid-configuration # ------------------------------------------- NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT="https://authentik.domain.tld/application/o/netbird/.well-known/openid-configuration" NETBIRD_USE_AUTH0=false NETBIRD_AUTH_CLIENT_ID="<Authentik client ID>" NETBIRD_AUTH_SUPPORTED_SCOPES="openid profile email offline_access api" NETBIRD_AUTH_AUDIENCE="<Authentik client ID>" NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID="<Authentik client ID>" NETBIRD_AUTH_DEVICE_AUTH_AUDIENCE="<Authentik client ID>" NETBIRD_AUTH_REDIRECT_URI="/auth" NETBIRD_AUTH_SILENT_REDIRECT_URI="/silent-auth" NETBIRD_MGMT_IDP="authentik" NETBIRD_IDP_MGMT_CLIENT_ID="<Authentik client ID>" NETBIRD_IDP_MGMT_EXTRA_USERNAME="Netbird" NETBIRD_IDP_MGMT_EXTRA_PASSWORD="<Authentik app password>" # needs disabling due to issue with IdP. Learn more [here](https://github.com/netbirdio/netbird/issues/3654) NETBIRD_AUTH_PKCE_DISABLE_PROMPT_LOGIN=true # ------------------------------------------- # Letsencrypt # ------------------------------------------- # Disable letsencrypt # if disabled, cannot use HTTPS anymore and requires setting up a reverse-proxy to do it instead NETBIRD_DISABLE_LETSENCRYPT=true # e.g. hello@mydomain.com NETBIRD_LETSENCRYPT_EMAIL="" # ------------------------------------------- # Extra settings # ------------------------------------------- # Disable anonymous metrics collection, see more information at https://netbird.io/docs/FAQ/metrics-collection NETBIRD_DISABLE_ANONYMOUS_METRICS=false # DNS DOMAIN configures the domain name used for peer resolution. By default it is netbird.selfhosted NETBIRD_MGMT_DNS_DOMAIN=netbird.domain.tld # Disable default all-to-all policy for new accounts NETBIRD_MGMT_DISABLE_DEFAULT_POLICY=false # ------------------------------------------- # Relay settings # ------------------------------------------- # Relay server domain. e.g. relay.mydomain.com # if not specified it will assume NETBIRD_DOMAIN NETBIRD_RELAY_DOMAIN="" # Relay server connection port. If none is supplied # it will default to 33080 # should be updated to match TLS-port of reverse proxy when netbird is running behind reverse proxy NETBIRD_RELAY_PORT="443" # Management API connecting port. If none is supplied # it will default to 33073 # should be updated to match TLS-port of reverse proxy when netbird is running behind reverse proxy NETBIRD_MGMT_API_PORT="443" # Signal service connecting port. If none is supplied # it will default to 10000 # should be updated to match TLS-port of reverse proxy when netbird is running behind reverse proxy NETBIRD_SIGNAL_PORT="443" ``` </details> <details> <summary>docker-compose.yml</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 ports: - 80:80 - 443:443 environment: # Endpoints - NETBIRD_MGMT_API_ENDPOINT=https://netbird.domain.tld:443 - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.domain.tld:443 # OIDC - AUTH_AUDIENCE=<Authentik client ID> - AUTH_CLIENT_ID=<Authentik client ID> - AUTH_CLIENT_SECRET= - AUTH_AUTHORITY=https://authentik.domain.tld/application/o/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= volumes: - netbird-letsencrypt:/etc/letsencrypt/ # Signal signal: <<: *default image: netbirdio/signal:latest depends_on: - dashboard volumes: - netbird-signal:/var/lib/netbird - netbird-letsencrypt:/etc/letsencrypt:ro ports: - 10000:10000 # # port and command for Let's Encrypt validation # - 443:443 # command: ["--letsencrypt-domain", "", "--log-file", "console"] command: [ "--cert-file", "", "--cert-key", "", "--log-file", "console", "--log-level", "debug", ] # Relay relay: <<: *default image: netbirdio/relay:latest environment: - NB_LOG_LEVEL=info - NB_LISTEN_ADDRESS=:443 - NB_EXPOSED_ADDRESS=rels://netbird.domain.tld:443/relay # todo: change to a secure secret - NB_AUTH_SECRET=<redacted> ports: - 33080:443 # 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: - 33073:33073 #API port # # command for Let's Encrypt validation without dashboard container # command: ["--letsencrypt-domain", "", "--log-file", "console"] command: [ "--port", "33073", "--log-file", "console", "--log-level", "debug", "--disable-anonymous-metrics=false", "--single-account-mode-domain=netbird.domain.tld", "--dns-domain=netbird.domain.tld" ] environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN= - NETBIRD_STORE_ENGINE_MYSQL_DSN= # Coturn coturn: <<: *default image: coturn/coturn:latest #domainname: netbird.hnolan.ca # only needed when TLS is enabled volumes: - ./turnserver.conf:/etc/turnserver.conf:ro # - ./privkey.pem:/etc/coturn/private/privkey.pem:ro # - ./cert.pem:/etc/coturn/certs/cert.pem:ro network_mode: host command: - -c /etc/turnserver.conf volumes: netbird-mgmt: netbird-signal: netbird-letsencrypt: ``` </details> <details> <summary>management.json</summary> ```json { "Stuns": [ { "Proto": "udp", "URI": "stun:netbird.domain.tld:3478", "Username": "", "Password": "" } ], "TURNConfig": { "TimeBasedCredentials": false, "CredentialsTTL": "12h0m0s", "Secret": "secret", "Turns": [ { "Proto": "udp", "URI": "turn:netbird.domain.tld:3478", "Username": "self", "Password": "<redacted>" } ] }, "Relay": { "Addresses": [ "rels://netbird.domain.tld:443/relay" ], "CredentialsTTL": "24h0m0s", "Secret": "<redacted>" }, "Signal": { "Proto": "https", "URI": "netbird.domain.tld:443", "Username": "", "Password": "" }, "Datadir": "/var/lib/netbird/", "DataStoreEncryptionKey": "<redacted>", "HttpConfig": { "LetsEncryptDomain": "", "CertFile": "", "CertKey": "", "AuthAudience": "<Authentik client ID>", "AuthIssuer": "https://authentik.domain.tld/application/o/netbird/", "AuthUserIDClaim": "", "AuthKeysLocation": "https://authentik.domain.tld/application/o/netbird/jwks/", "OIDCConfigEndpoint": "https://authentik.domain.tld/application/o/netbird/.well-known/openid-configuration", "IdpSignKeyRefreshEnabled": false, "ExtraAuthAudience": "" }, "IdpManagerConfig": { "ManagerType": "authentik", "ClientConfig": { "Issuer": "https://authentik.domain.tld/application/o/netbird", "TokenEndpoint": "https://authentik.domain.tld/application/o/token/", "ClientID": "<Authentik client ID>", "ClientSecret": "", "GrantType": "client_credentials" }, "ExtraConfig": { "Password": "<Authentik app password>", "Username": "Netbird" }, "Auth0ClientCredentials": null, "AzureClientCredentials": null, "KeycloakClientCredentials": null, "ZitadelClientCredentials": null }, "DeviceAuthorizationFlow": { "Provider": "hosted", "ProviderConfig": { "ClientID": "<Authentik client ID>", "ClientSecret": "", "Domain": "authentik.domain.tld", "Audience": "<Authentik client ID>", "TokenEndpoint": "https://authentik.domain.tld/application/o/token/", "DeviceAuthEndpoint": "https://authentik.domain.tld/application/o/device/", "AuthorizationEndpoint": "", "Scope": "openid", "UseIDToken": false, "RedirectURLs": null, "DisablePromptLogin": false, "LoginFlag": 0 } }, "PKCEAuthorizationFlow": { "ProviderConfig": { "ClientID": "<Authentik client ID>", "ClientSecret": "", "Domain": "", "Audience": "<Authentik client ID>", "TokenEndpoint": "https://authentik.domain.tld/application/o/token/", "DeviceAuthEndpoint": "", "AuthorizationEndpoint": "https://authentik.domain.tld/application/o/authorize/", "Scope": "openid profile email offline_access api", "UseIDToken": false, "RedirectURLs": [ "http://localhost:53000" ], "DisablePromptLogin": true, "LoginFlag": 0 } }, "StoreConfig": { "Engine": "sqlite" }, "ReverseProxy": { "TrustedHTTPProxies": [], "TrustedHTTPProxiesCount": 0, "TrustedPeers": [ "0.0.0.0/0" ] }, "DisableDefaultPolicy": false } ``` </details>
Author
Owner

@afonsofrancof commented on GitHub (Nov 22, 2025):

@hc-nolan

OMG! It worked!

Thank you so much!!!

The documentation needs to be updated, this is such a difficult problem to troubleshoot.
(I still get the same log message in the management container, but it works)

I had a missing port mapping, but I think the https thing is what solved it.

Caddy and port mappings

The signal container opens 2 distinct ports, one for HTTP (80) and another for gRPC (10000) . In caddy I had a mistake where both were forwarding to the HTTP port, so I had to map the correct ones and forward from caddy correctly.

The management container on the other hand binds both gRPC and HTTP to the same port, so I forwarded them both to that same one.

I also added a slash after api in reverse_proxy /api/* management:80 (the path was /api* before, but idk if this is relevant)

The big thing was what @hc-nolan found in management.json!

Changed http to https and everything worked

That made it instantly work.
So I guess in the end the clients were actually authenticated but the problem was the Signal container. The management container printing that error (which I guess is harmless, as it all works and it still prints it) lead me down the wrong path.

My Full Config

I leave here my whole config so that anyone with this problem can solve it!

I basically changed every port to port 80 on every container, except for Signal's gRPC, which remained at port 10000 and for management I used the combined HTTP+gRPC port and ignore the backwards compatible gRPC only port.

(I am using Pocket ID now, but I guess Authentik was never the problem. I am staying on Pocket ID nonetheless, as it is much simpler)

docker-compose.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:v2.22.2
    networks:
      - default
      - caddy_proxy
    expose:
      - 80
    environment:
      # Endpoints
      - NETBIRD_MGMT_API_ENDPOINT=https://netbird.domain.tld:443
      - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.domain.tld:443
      # OIDC
      - AUTH_AUDIENCE=<client-id>
      - AUTH_CLIENT_ID=<client-id>
      - AUTH_CLIENT_SECRET=
      - AUTH_AUTHORITY=https://pocketid.domain.tld
      - USE_AUTH0=false
      - AUTH_SUPPORTED_SCOPES=openid profile email groups
      - AUTH_REDIRECT_URI=/auth
      - AUTH_SILENT_REDIRECT_URI=/silent-auth
      - NETBIRD_TOKEN_SOURCE=idToken
      # SSL
      - NGINX_SSL_PORT=443
      # Letsencrypt
      - LETSENCRYPT_DOMAIN=
      - LETSENCRYPT_EMAIL=

  signal:
    <<: *default
    image: netbirdio/signal:0.60.2
    depends_on:
          - dashboard
    volumes:
      - /home/user/netbird/signal:/var/lib/netbird
    networks:
      - default
      - caddy_proxy
    expose:
      - 80
      - 10000
    command: [
      "--cert-file", "",
      "--cert-key", "",
      "--log-file", "console",
      "--log-level", "info"
    ]

  # Relay
  relay:
    <<: *default
    image: netbirdio/relay:0.60.2
    environment:
    - NB_LOG_LEVEL=info
    - NB_LISTEN_ADDRESS=0.0.0.0:80
    - NB_EXPOSED_ADDRESS=rels://netbird.domain.tld:443
    - NB_AUTH_SECRET=<nb-auth-secret>
    networks:
      - default
      - caddy_proxy
    expose:
      - 80

  management:
    <<: *default
    image: netbirdio/management:0.60.2
    depends_on:
      - dashboard
    volumes:
      - /home/user/netbird/mgmt:/var/lib/netbird
      - /home/user/netbird/conf/management.json:/etc/netbird/management.json
    networks:
      - default
      - caddy_proxy
    expose:
      - 80
      - 33073
    command: [
      "--port", "80",
      "--log-file", "console",
      "--log-level", "info",
      "--disable-anonymous-metrics=false",
      "--single-account-mode-domain=netbird.domain.tld",
      "--dns-domain=netbird.selfhosted"
      ]
    environment:
      - NETBIRD_STORE_ENGINE_POSTGRES_DSN=
      - NETBIRD_STORE_ENGINE_MYSQL_DSN=


networks:
  caddy_proxy:
    external: true
    name: "caddy_proxy"

Caddyfile
netbird.domain.tld {

        reverse_proxy /* dashboard:80

        reverse_proxy /signalexchange.SignalExchange/* h2c://signal:10000

        reverse_proxy /ws-proxy/signal signal:80

        reverse_proxy /management.ManagementService/* h2c://management:80

        reverse_proxy /ws-proxy/management management:80

        reverse_proxy /api/* management:80

        reverse_proxy /relay* relay:80

}
management.json

{
    "Stuns": [],
    "TURNConfig": {
        "TimeBasedCredentials": false,
        "CredentialsTTL": "12h0m0s",
        "Secret": "secret",
        "Turns": []
    },
    "Relay": {
        "Addresses": [
            "rels://netbird.domain.tld:443"
        ],
        "CredentialsTTL": "24h0m0s",
        "Secret": "<nb-auth-secret>"
    },
    "Signal": {
        "Proto": "https",
        "URI": "netbird.domain.tld:443",
        "Username": "",
        "Password": ""
    },
    "Datadir": "/var/lib/netbird/",
    "DataStoreEncryptionKey": "<key>",
    "HttpConfig": {
        "LetsEncryptDomain": "",
        "CertFile": "",
        "CertKey": "",
        "AuthAudience": "<client-id>",
        "AuthIssuer": "https://pocketid.domain.tld",
        "AuthUserIDClaim": "",
        "AuthKeysLocation": "https://pocketid.domain.tld/.well-known/jwks.json",
        "OIDCConfigEndpoint": "https://pocketid.domain.tld/.well-known/openid-configuration",
        "IdpSignKeyRefreshEnabled": false,
        "ExtraAuthAudience": ""
    },
    "IdpManagerConfig": {
        "ManagerType": "pocketid",
        "ClientConfig": {
            "Issuer": "https://pocketid.domain.tld",
            "TokenEndpoint": "https://pocketid.domain.tld/api/oidc/token",
            "ClientID": "netbird",
            "ClientSecret": "",
            "GrantType": "client_credentials"
        },
        "ExtraConfig": {
            "ApiToken": "<pocket-id-api-token>",
            "ManagementEndpoint": "https://pocketid.domain.tld"
        },
        "Auth0ClientCredentials": null,
        "AzureClientCredentials": null,
        "KeycloakClientCredentials": null,
        "ZitadelClientCredentials": null
    },
    "DeviceAuthorizationFlow": {
        "Provider": "hosted",
        "ProviderConfig": {
            "ClientID": "<client-id>",
            "ClientSecret": "",
            "Domain": "pocketid.domain.tld",
            "Audience": "<client-id>",
            "TokenEndpoint": "https://pocketid.domain.tld/api/oidc/token",
            "DeviceAuthEndpoint": "https://pocketid.domain.tld/api/oidc/device/authorize",
            "AuthorizationEndpoint": "",
            "Scope": "openid profile email groups",
            "UseIDToken": true,
            "RedirectURLs": null,
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "PKCEAuthorizationFlow": {
        "ProviderConfig": {
            "ClientID": "<client-id>",
            "ClientSecret": "",
            "Domain": "",
            "Audience": "<client-id>",
            "TokenEndpoint": "https://pocketid.domain.tld/api/oidc/token",
            "DeviceAuthEndpoint": "",
            "AuthorizationEndpoint": "https://pocketid.domain.tld/authorize",
            "Scope": "openid profile email groups",
            "UseIDToken": true,
            "RedirectURLs": [
                "http://localhost:53000"
            ],
            "DisablePromptLogin": false,
            "LoginFlag": 0
        }
    },
    "StoreConfig": {
        "Engine": "sqlite"
    },
    "ReverseProxy": {
        "TrustedHTTPProxies": [],
        "TrustedHTTPProxiesCount": 0,
        "TrustedPeers": [
            "0.0.0.0/0"
        ]
    },
    "DisableDefaultPolicy": false
}


<!-- gh-comment-id:3567373829 --> @afonsofrancof commented on GitHub (Nov 22, 2025): @hc-nolan OMG! It worked! Thank you so much!!! The documentation needs to be updated, this is such a difficult problem to troubleshoot. (I still get the same log message in the management container, but it works) I had a missing port mapping, but I think the `https` thing is what solved it. # Caddy and port mappings The signal container opens 2 distinct ports, one for HTTP (80) and another for gRPC (10000) . In caddy I had a mistake where both were forwarding to the HTTP port, so I had to map the correct ones and forward from caddy correctly. The management container on the other hand binds both gRPC and HTTP to the same port, so I forwarded them both to that same one. I also added a slash after api in `reverse_proxy /api/* management:80` (the path was `/api*` before, but idk if this is relevant) The big thing was what @hc-nolan found in `management.json`! > Changed http to https and everything worked That made it instantly work. So I guess in the end the clients were actually authenticated but the problem was the Signal container. The management container printing that error (which I guess is harmless, as it all works and it still prints it) lead me down the wrong path. # My Full Config I leave here my whole config so that anyone with this problem can solve it! I basically changed every port to port 80 on every container, except for Signal's gRPC, which remained at port 10000 and for management I used the combined HTTP+gRPC port and ignore the backwards compatible gRPC only port. (I am using Pocket ID now, but I guess Authentik was never the problem. I am staying on Pocket ID nonetheless, as it is much simpler) <details> <summary> docker-compose.yaml </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:v2.22.2 networks: - default - caddy_proxy expose: - 80 environment: # Endpoints - NETBIRD_MGMT_API_ENDPOINT=https://netbird.domain.tld:443 - NETBIRD_MGMT_GRPC_API_ENDPOINT=https://netbird.domain.tld:443 # OIDC - AUTH_AUDIENCE=<client-id> - AUTH_CLIENT_ID=<client-id> - AUTH_CLIENT_SECRET= - AUTH_AUTHORITY=https://pocketid.domain.tld - USE_AUTH0=false - AUTH_SUPPORTED_SCOPES=openid profile email groups - AUTH_REDIRECT_URI=/auth - AUTH_SILENT_REDIRECT_URI=/silent-auth - NETBIRD_TOKEN_SOURCE=idToken # SSL - NGINX_SSL_PORT=443 # Letsencrypt - LETSENCRYPT_DOMAIN= - LETSENCRYPT_EMAIL= signal: <<: *default image: netbirdio/signal:0.60.2 depends_on: - dashboard volumes: - /home/user/netbird/signal:/var/lib/netbird networks: - default - caddy_proxy expose: - 80 - 10000 command: [ "--cert-file", "", "--cert-key", "", "--log-file", "console", "--log-level", "info" ] # Relay relay: <<: *default image: netbirdio/relay:0.60.2 environment: - NB_LOG_LEVEL=info - NB_LISTEN_ADDRESS=0.0.0.0:80 - NB_EXPOSED_ADDRESS=rels://netbird.domain.tld:443 - NB_AUTH_SECRET=<nb-auth-secret> networks: - default - caddy_proxy expose: - 80 management: <<: *default image: netbirdio/management:0.60.2 depends_on: - dashboard volumes: - /home/user/netbird/mgmt:/var/lib/netbird - /home/user/netbird/conf/management.json:/etc/netbird/management.json networks: - default - caddy_proxy expose: - 80 - 33073 command: [ "--port", "80", "--log-file", "console", "--log-level", "info", "--disable-anonymous-metrics=false", "--single-account-mode-domain=netbird.domain.tld", "--dns-domain=netbird.selfhosted" ] environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN= - NETBIRD_STORE_ENGINE_MYSQL_DSN= networks: caddy_proxy: external: true name: "caddy_proxy" ``` </details> <details> <summary> Caddyfile </summary> ```Caddyfile netbird.domain.tld { reverse_proxy /* dashboard:80 reverse_proxy /signalexchange.SignalExchange/* h2c://signal:10000 reverse_proxy /ws-proxy/signal signal:80 reverse_proxy /management.ManagementService/* h2c://management:80 reverse_proxy /ws-proxy/management management:80 reverse_proxy /api/* management:80 reverse_proxy /relay* relay:80 } ``` </details> <details> <summary> management.json </summary> ```json { "Stuns": [], "TURNConfig": { "TimeBasedCredentials": false, "CredentialsTTL": "12h0m0s", "Secret": "secret", "Turns": [] }, "Relay": { "Addresses": [ "rels://netbird.domain.tld:443" ], "CredentialsTTL": "24h0m0s", "Secret": "<nb-auth-secret>" }, "Signal": { "Proto": "https", "URI": "netbird.domain.tld:443", "Username": "", "Password": "" }, "Datadir": "/var/lib/netbird/", "DataStoreEncryptionKey": "<key>", "HttpConfig": { "LetsEncryptDomain": "", "CertFile": "", "CertKey": "", "AuthAudience": "<client-id>", "AuthIssuer": "https://pocketid.domain.tld", "AuthUserIDClaim": "", "AuthKeysLocation": "https://pocketid.domain.tld/.well-known/jwks.json", "OIDCConfigEndpoint": "https://pocketid.domain.tld/.well-known/openid-configuration", "IdpSignKeyRefreshEnabled": false, "ExtraAuthAudience": "" }, "IdpManagerConfig": { "ManagerType": "pocketid", "ClientConfig": { "Issuer": "https://pocketid.domain.tld", "TokenEndpoint": "https://pocketid.domain.tld/api/oidc/token", "ClientID": "netbird", "ClientSecret": "", "GrantType": "client_credentials" }, "ExtraConfig": { "ApiToken": "<pocket-id-api-token>", "ManagementEndpoint": "https://pocketid.domain.tld" }, "Auth0ClientCredentials": null, "AzureClientCredentials": null, "KeycloakClientCredentials": null, "ZitadelClientCredentials": null }, "DeviceAuthorizationFlow": { "Provider": "hosted", "ProviderConfig": { "ClientID": "<client-id>", "ClientSecret": "", "Domain": "pocketid.domain.tld", "Audience": "<client-id>", "TokenEndpoint": "https://pocketid.domain.tld/api/oidc/token", "DeviceAuthEndpoint": "https://pocketid.domain.tld/api/oidc/device/authorize", "AuthorizationEndpoint": "", "Scope": "openid profile email groups", "UseIDToken": true, "RedirectURLs": null, "DisablePromptLogin": false, "LoginFlag": 0 } }, "PKCEAuthorizationFlow": { "ProviderConfig": { "ClientID": "<client-id>", "ClientSecret": "", "Domain": "", "Audience": "<client-id>", "TokenEndpoint": "https://pocketid.domain.tld/api/oidc/token", "DeviceAuthEndpoint": "", "AuthorizationEndpoint": "https://pocketid.domain.tld/authorize", "Scope": "openid profile email groups", "UseIDToken": true, "RedirectURLs": [ "http://localhost:53000" ], "DisablePromptLogin": false, "LoginFlag": 0 } }, "StoreConfig": { "Engine": "sqlite" }, "ReverseProxy": { "TrustedHTTPProxies": [], "TrustedHTTPProxiesCount": 0, "TrustedPeers": [ "0.0.0.0/0" ] }, "DisableDefaultPolicy": false } ``` </details>
Author
Owner

@hc-nolan commented on GitHub (Nov 22, 2025):

Glad it helped!

I didn't find it until now, but there is an issue open to update the docs: https://github.com/netbirdio/docs/issues/459

<!-- gh-comment-id:3567409063 --> @hc-nolan commented on GitHub (Nov 22, 2025): Glad it helped! I didn't find it until now, but there is an issue open to update the docs: https://github.com/netbirdio/docs/issues/459
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#9407