nats-io/nats-server · error

default sentinel must be a bearer token

Error message

default sentinel must be a bearer token

What it means

After successfully decoding DefaultSentinel, validateTrustedOperators requires it to be a bearer token: a JWT whose UserClaims have BearerToken set (no signature requirement / auth bypass semantics). If the claims are not a bearer token (juc.BearerToken is false and it does not qualify as an exempt scoped user), the server refuses to start with 'default sentinel must be a bearer token'.

Source

Thrown at server/jwt.go:88

func validateTrustedOperators(o *Options) error {
	if len(o.TrustedOperators) == 0 {
		// if we have no operator, default sentinel shouldn't be set
		if o.DefaultSentinel != _EMPTY_ {
			return fmt.Errorf("default sentinel requires operators and accounts")
		}
		return nil
	}
	if o.DefaultSentinel != _EMPTY_ {
		juc, err := jwt.DecodeUserClaims(o.DefaultSentinel)
		if err != nil {
			return fmt.Errorf("default sentinel JWT not valid")
		}

		if !juc.BearerToken && juc.IssuerAccount != "" && juc.HasEmptyPermissions() {
			// we cannot resolve the account yet - but this looks like a scoped user
			// it will be rejected at runtime if not valid
		} else if !juc.BearerToken {
			return fmt.Errorf("default sentinel must be a bearer token")
		}
	}
	if o.AccountResolver == nil {
		return fmt.Errorf("operators require an account resolver to be configured")
	}
	if len(o.Accounts) > 0 {
		return fmt.Errorf("operators do not allow Accounts to be configured directly")
	}
	if len(o.Users) > 0 || len(o.Nkeys) > 0 {
		return fmt.Errorf("operators do not allow users to be configured directly")
	}
	if len(o.TrustedOperators) > 0 && len(o.TrustedKeys) > 0 {
		return fmt.Errorf("conflicting options for 'TrustedKeys' and 'TrustedOperators'")
	}
	if o.SystemAccount != _EMPTY_ {
		foundSys := false
		foundNonEmpty := false
		for _, op := range o.TrustedOperators {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Create the sentinel as a bearer token user: with nsc add the user with bearer token enabled (bearer_token: true in the claims) and re-export the JWT.
  2. Edit the user in nsc to set BearerToken, then regenerate/update default_sentinel in the config.
  3. Alternatively, if a scoped user is intended, ensure it has an IssuerAccount and empty permissions so it is accepted as a scoped default user.
  4. Restart the server and confirm startup passes validation.

Example fix

// before: non-bearer user JWT in config
default_sentinel: "eyJ..."  // bearer_token missing
// after (nsc)
$ nsc edit user sentinel --bearer
$ nsc describe user sentinel --raw > sentinel.jwt
// nats.conf
default_sentinel: "<contents of sentinel.jwt>"
Defensive patterns

Strategy: validation

Validate before calling

// Go: ensure the sentinel is a bearer token before configuring
uc, err := jwt.DecodeUserClaims(sentinelJWT)
if err != nil || !uc.BearerToken {
    return errors.New("default_sentinel must be a bearer token user JWT")
}

Type guard

func isBearerSentinel(tok string) bool {
    uc, err := jwt.DecodeUserClaims(tok)
    return err == nil && uc.BearerToken
}

Prevention

When it happens

Trigger: Configuring default_sentinel with a normal (non-bearer) user JWT — one carrying permissions requiring full claim verification — instead of a bearer-token user created for sentinel purposes.

Common situations: Generating the sentinel user with `nsc` without enabling bearer token (e.g. missing the bearer_token claim or not using `--bearer`); reusing an existing scoped user JWT as the sentinel.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/1038288bf567c4fd. Report an issue: GitHub.