Skip to content

Setup wizard unreachable on fresh install when using external reverse proxy (static export + OAuth-first flow race condition) #601

@nonanonymousanon

Description

@nonanonymousanon

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

  1. 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
  2. Configure external NPM per the documentation given by the script
  3. Navigate to https://yourdomain.com
  4. Observe redirect to /oauth2/auth/local/login instead of /setup
  5. Confirm setup is still required: curl https://yourdomain.com/api/instance returns {"setup_required":true}
  6. 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:

  1. 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
  2. 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
  3. Have the backend return a 302 to /setup from GET / when setup_required is true, so any reverse proxy gets the redirect automatically

References:

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions