Describe the problem
On a fresh install with no users, navigating to the dashboard root (https://yourdomain.com) never shows the /setup wizard. Instead, the browser is immediately redirected to /oauth2/auth/local/login?back=&state=.... The setup wizard is permanently inaccessible through normal browser navigation.
The root cause is that the dashboard is built as a Next.js static export (output: 'export'), which means:
- There is no
/setup or setup.html file in /usr/share/nginx/html — the setup route does not exist as a static file
- All routing logic lives inside client-side JavaScript
- The nginx config serves
index.html at /, which loads the spinner and client-side JS
- Before that JS can call
GET /api/instance and act on setup_required: true, the OAuth flow has already been initiated server-side by the embedded Dex IdP
- There is no server-side interception point in a static export to check
setup_required before the OAuth redirect fires
This works in the default Traefik setup only because Traefik + the combined container happen to handle the timing differently. It breaks for any external reverse proxy (Nginx Proxy Manager, Caddy, standalone Nginx) because the OAuth redirect from Dex wins the race before the client-side JS can redirect to /setup.
An additional bug was discovered during investigation: the documented setup completion endpoint POST /api/instance/setup returns 404. The actual working endpoint is POST /api/setup. The docs at docs.netbird.io are incorrect.
To Reproduce
- Fresh install using
curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh | bash selecting Nginx Proxy Manager as the reverse proxy
- Configure external NPM per the documentation given by the script
- Navigate to
https://yourdomain.com
- Observe redirect to
/oauth2/auth/local/login instead of /setup
- Confirm setup is still required:
curl https://yourdomain.com/api/instance returns {"setup_required":true}
- Navigate directly to
https://yourdomain.com/setup — still redirects to OAuth
Expected behavior
On a fresh install with no users, navigating to https://yourdomain.com should automatically redirect to https://yourdomain.com/setup so the admin can create the first user account. This should work regardless of which supported reverse proxy is used.
Are you using NetBird Cloud?
Self-hosted, installed via getting-started.sh with Nginx Proxy Manager as the external reverse proxy.
NetBird version
0.67.1 (combined container netbirdio/netbird-server:latest, dashboard netbirdio/dashboard:latest)
NetBird status -d output:
Not applicable — issue occurs before any user account exists or any client is connected.
Screenshots
Not applicable.
Additional context
Workaround 1 — API call (recommended): If the containers are already running, setup can be completed via direct API call on the server itself:
curl -X POST "http://127.0.0.1:8081/api/setup" \
-H "Content-Type: application/json" \
-d '{
"email": "admin@example.com",
"password": "yourpassword",
"name": "Admin"
}'
Note the correct endpoint is /api/setup, not /api/instance/setup as documented.
Workaround 2 — config.yaml owner block (before first startup): Before bringing up the containers for the first time, you can pre-seed the owner account in config.yaml under the auth section. However, the password must be a bcrypt hash — plaintext will cause crypto/bcrypt: hashedSecret too short to be a bcrypted password. Generate the hash first:
echo "yourpassword" | htpasswd -BinC 10 admin | cut -d: -f2
Then add the result to config.yaml:
auth:
owner:
email: "admin@example.com"
password: "$2y$10$examplehashgoeshere..."
Bring up the stack after saving. The owner account will be created on first boot and the owner block can and should be removed afterwards.
Proposed fixes — any one of the following would resolve this:
- Switch the dashboard from
output: 'export' to a Node.js server so that src/app/page.tsx can perform a server-side fetch to /api/instance and issue a server-side redirect("/setup") before any OAuth flow begins
- Add a
setup.html static file to the build output and add an nginx location = / rule that checks /api/instance and redirects to /setup when setup_required is true
- Have the backend return a
302 to /setup from GET / when setup_required is true, so any reverse proxy gets the redirect automatically
References:
Describe the problem
On a fresh install with no users, navigating to the dashboard root (
https://yourdomain.com) never shows the/setupwizard. Instead, the browser is immediately redirected to/oauth2/auth/local/login?back=&state=.... The setup wizard is permanently inaccessible through normal browser navigation.The root cause is that the dashboard is built as a Next.js static export (
output: 'export'), which means:/setuporsetup.htmlfile in/usr/share/nginx/html— the setup route does not exist as a static fileindex.htmlat/, which loads the spinner and client-side JSGET /api/instanceand act onsetup_required: true, the OAuth flow has already been initiated server-side by the embedded Dex IdPsetup_requiredbefore the OAuth redirect firesThis works in the default Traefik setup only because Traefik + the combined container happen to handle the timing differently. It breaks for any external reverse proxy (Nginx Proxy Manager, Caddy, standalone Nginx) because the OAuth redirect from Dex wins the race before the client-side JS can redirect to
/setup.An additional bug was discovered during investigation: the documented setup completion endpoint
POST /api/instance/setupreturns 404. The actual working endpoint isPOST /api/setup. The docs at docs.netbird.io are incorrect.To Reproduce
curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh | bashselecting Nginx Proxy Manager as the reverse proxyhttps://yourdomain.com/oauth2/auth/local/logininstead of/setupcurl https://yourdomain.com/api/instancereturns{"setup_required":true}https://yourdomain.com/setup— still redirects to OAuthExpected behavior
On a fresh install with no users, navigating to
https://yourdomain.comshould automatically redirect tohttps://yourdomain.com/setupso the admin can create the first user account. This should work regardless of which supported reverse proxy is used.Are you using NetBird Cloud?
Self-hosted, installed via
getting-started.shwith Nginx Proxy Manager as the external reverse proxy.NetBird version
0.67.1(combined containernetbirdio/netbird-server:latest, dashboardnetbirdio/dashboard:latest)NetBird status -d output:
Not applicable — issue occurs before any user account exists or any client is connected.
Screenshots
Not applicable.
Additional context
Workaround 1 — API call (recommended): If the containers are already running, setup can be completed via direct API call on the server itself:
Note the correct endpoint is
/api/setup, not/api/instance/setupas documented.Workaround 2 — config.yaml owner block (before first startup): Before bringing up the containers for the first time, you can pre-seed the owner account in
config.yamlunder theauthsection. However, the password must be a bcrypt hash — plaintext will causecrypto/bcrypt: hashedSecret too short to be a bcrypted password. Generate the hash first:Then add the result to
config.yaml:Bring up the stack after saving. The owner account will be created on first boot and the
ownerblock can and should be removed afterwards.Proposed fixes — any one of the following would resolve this:
output: 'export'to a Node.js server so thatsrc/app/page.tsxcan perform a server-side fetch to/api/instanceand issue a server-sideredirect("/setup")before any OAuth flow beginssetup.htmlstatic file to the build output and add an nginxlocation = /rule that checks/api/instanceand redirects to/setupwhensetup_requiredis true302to/setupfromGET /whensetup_requiredis true, so any reverse proxy gets the redirect automaticallyReferences:
POST /api/instance/setup: https://docs.netbird.io/selfhosted/identity-providers/local