nats-io/nats-server · error

operator %s expected major version %d > server major version

Error message

operator %s expected major version %d > server major version %d

What it means

The operator asserts a minimum server version. If its declared major version exceeds the running server's major version, validateOptions rejects the config because the server predates guarantees the operator requires.

Source

Thrown at server/jwt.go:133

		if foundNonEmpty && !foundSys {
			return fmt.Errorf("system_account in config and operator JWT must be identical")
		}
	} else if o.TrustedOperators[0].SystemAccount == _EMPTY_ {
		// In case the system account is neither defined in config nor in the first operator.
		// If it would be needed due to the nats account resolver, raise an error.
		switch o.AccountResolver.(type) {
		case *DirAccResolver, *CacheDirAccResolver:
			return fmt.Errorf("using nats based account resolver - the system account needs to be specified in configuration or the operator jwt")
		}
	}

	srvMajor, srvMinor, srvUpdate, _ := versionComponents(VERSION)
	for _, opc := range o.TrustedOperators {
		if major, minor, update, err := jwt.ParseServerVersion(opc.AssertServerVersion); err != nil {
			return fmt.Errorf("operator %s expects version %s got error instead: %s",
				opc.Subject, opc.AssertServerVersion, err)
		} else if major > srvMajor {
			return fmt.Errorf("operator %s expected major version %d > server major version %d",
				opc.Subject, major, srvMajor)
		} else if srvMajor > major {
		} else if minor > srvMinor {
			return fmt.Errorf("operator %s expected minor version %d > server minor version %d",
				opc.Subject, minor, srvMinor)
		} else if srvMinor > minor {
		} else if update > srvUpdate {
			return fmt.Errorf("operator %s expected update version %d > server update version %d",
				opc.Subject, update, srvUpdate)
		}
	}
	// If we have operators, fill in the trusted keys.
	// FIXME(dlc) - We had TrustedKeys before TrustedOperators. The jwt.OperatorClaims
	// has a DidSign(). Use that longer term. For now we can expand in place.
	for _, opc := range o.TrustedOperators {
		if o.TrustedKeys == nil {
			o.TrustedKeys = make([]string, 0, 4)
		}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Upgrade nats-server to a version with a major >= the operator's assert
  2. Or re-issue the operator JWT with a lower assertServerVersion appropriate for your deployment

Example fix

// before
nats-server v2.10.x with operator asserting 3.0.0
// after
upgrade: go install github.com/nats-io/nats-server/v2@latest  (v3+)
# or reissue operator with assert 2.10.0
Defensive patterns

Strategy: validation

Validate before calling

// Go: compare asserted major against running server before startup
srvMajor, _, _, _ := versionComponents(VERSION)
if major, _, _, err := jwt.ParseServerVersion(opc.AssertServerVersion); err == nil && major > srvMajor {
    return fmt.Errorf("server too old for operator (major %d > %d)", major, srvMajor)
}

Prevention

When it happens

Trigger: Operator JWT asserts e.g. 3.0.0 while running a 2.x nats-server; validateOptions compares versionComponents(VERSION) against the claim.

Common situations: Deploying an operator JWT generated with bleeding-edge tooling onto a production server running an older major release; rolling back the server binary without rolling back the operator.

Related errors


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