netbirdio/netbird · error

login: %w

Error message

login: %w

What it means

Returned by Client.Start when authClient.Login fails while authenticating to the management server with the setup key or JWT token chosen in embed.New. The wrapped error is the management login error: rejected/expired/over-quota setup key, invalid JWT, unreachable or misbehaving management server, or TLS handshake failure.

Source

Thrown at client/embed/embed.go:265

	ctx, cancel := context.WithCancel(internal.CtxInitState(context.Background()))
	defer func() {
		if c.connect == nil {
			cancel()
		}
	}()

	// nolint:staticcheck
	ctx = context.WithValue(ctx, system.DeviceNameCtxKey, c.deviceName)

	authClient, err := auth.NewAuth(ctx, c.config.PrivateKey, c.config.ManagementURL, c.config)
	if err != nil {
		return fmt.Errorf("create auth client: %w", err)
	}
	defer authClient.Close()

	if err, _ := authClient.Login(ctx, c.setupKey, c.jwtToken); err != nil {
		return fmt.Errorf("login: %w", err)
	}
	client := internal.NewConnectClient(ctx, c.config, c.recorder)
	client.SetSyncResponsePersistence(true)

	// either startup error (permanent backoff err) or nil err (successful engine up)
	// TODO: make after-startup backoff err available
	run := make(chan struct{})
	clientErr := make(chan error, 1)
	go func() {
		if err := client.Run(run, ""); err != nil {
			clientErr <- err
		}
	}()

	select {
	case <-startCtx.Done():
		// ConnectClient.Stop now cancels its own run context and waits for the
		// run loop to tear the engine down, so this cancel() is no longer

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Read the wrapped error to separate credential rejection (fix the credential) from connectivity (fix network/server).
  2. For setup keys: check status/expiry/usage limits in the management dashboard and generate a new key if needed.
  3. For JWT: fetch a fresh token immediately before Start instead of caching it long-term.
  4. For connectivity: verify the management URL responds (curl) and TLS trusts; retry Start with backoff if the server was temporarily down.

Example fix

// before
if err := client.Start(ctx); err != nil { log.Fatalf("start: %v", err) }

// after
if err := client.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "login:") {
        // credential or management-side problem: refresh token/setup key, then retry
        token, terr := fetchFreshJWT()
        if terr == nil {
            client, _ = embed.New(embed.Options{JWTToken: token, ...})
            err = client.Start(ctx)
        }
    }
    if err != nil { log.Fatalf("start: %v", err) }
}
Defensive patterns

Strategy: retry

Try / catch

err := client.Start(ctx)
if err != nil && strings.Contains(err.Error(), "login:") {
    // credential rejections are permanent: refresh token/setup key, recreate client, retry once
    // connectivity errors: backoff and retry Start with the same client recreated

Prevention

When it happens

Trigger: Client.Start after embed.New with a revoked or expired SetupKey, a JWTToken past expiry, a management URL pointing at a server that is down or returns an error, or clock skew breaking token validation.

Common situations: Setup keys rotated or deleted in the management dashboard while the embedding app kept the old one; JWTs fetched once and reused after expiry; management server behind a load balancer with a stale cert; running against a self-hosted management that is not yet up when the embedder starts.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/251c04754b6635d8. Report an issue: GitHub.