netbirdio/netbird · error

only one of SetupKey, JWTToken, or PrivateKey can be specifi

Error message

only one of SetupKey, JWTToken, or PrivateKey can be specified

What it means

The mirror case of 438 in embed.Options.validateCredentials: more than one of SetupKey, JWTToken, or PrivateKey was set, making the intended registration credential ambiguous. New() refuses to guess and aborts. Exactly one must be present.

Source

Thrown at client/embed/embed.go:143

// validateCredentials checks that exactly one credential type is provided
func (opts *Options) validateCredentials() error {
	credentialsProvided := 0
	if opts.SetupKey != "" {
		credentialsProvided++
	}
	if opts.JWTToken != "" {
		credentialsProvided++
	}
	if opts.PrivateKey != "" {
		credentialsProvided++
	}

	if credentialsProvided == 0 {
		return fmt.Errorf("one of SetupKey, JWTToken, or PrivateKey must be provided")
	}
	if credentialsProvided > 1 {
		return fmt.Errorf("only one of SetupKey, JWTToken, or PrivateKey can be specified")
	}

	return nil
}

// New creates a new netbird embedded client.
func New(opts Options) (*Client, error) {
	if err := opts.validateCredentials(); err != nil {
		return nil, err
	}

	if opts.MTU != nil {
		if err := iface.ValidateMTU(*opts.MTU); err != nil {
			return nil, fmt.Errorf("invalid MTU: %w", err)
		}
	}

	if opts.LogOutput != nil {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Set exactly one credential field and leave the others empty
  2. In config loaders, apply precedence explicitly (e.g. SetupKey wins, so clear JWTToken/PrivateKey) instead of populating all
  3. When reusing persisted state with PrivateKey, drop the setup key from options

Example fix

// before
client, err := embed.New(embed.Options{SetupKey: sk, PrivateKey: pk})  // err: only one of ...
// after
client, err := embed.New(embed.Options{PrivateKey: pk})  // exactly one credential
Defensive patterns

Strategy: validation

Validate before calling

// enforce single-credential precedence before New()
switch {
case o.SetupKey != "":
	o.JWTToken, o.PrivateKey = "", ""
case o.JWTToken != "":
	o.PrivateKey = ""
}

Prevention

When it happens

Trigger: embed.New with both SetupKey and JWTToken set (common when falling back between env vars), or PrivateKey combined with either of the others.

Common situations: Config loaders that set every credential field they find (e.g. setup key from env plus a cached private key from state), or defensive 'set everything' initialization.

Related errors


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