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
- Upgrade nats-server to a version with a major >= the operator's assert
- 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
- Pin nsc and nats-server versions together in your release pipeline
- Decode operator asserts in CI and compare against the deployed server version
- Upgrade servers before distributing re-asserted operator JWTs
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
- operator %s expected minor version %d > server minor version
- operator %s expected update version %d > server update versi
- operators require an account resolver to be configured
- operators do not allow Accounts to be configured directly
- operators do not allow users to be configured directly
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/37bfb23efdd2ba11.
Report an issue: GitHub.