nats-io/nats-server · error
subject of existing and new jwt do not match
Error message
subject of existing and new jwt do not match
What it means
During saveIfNewer, the existing JWT on disk and the incoming JWT decode to different subject claims. Even though the incoming JWT matches the provided key, replacing the file would change which entity the stored JWT belongs to, so the store refuses. This guards against overwriting a JWT for one account/activation with one for another.
Source
Thrown at server/dirstore.go:537
if err := os.MkdirAll(dirPath, defaultDirPerms); err != nil {
return err
}
}
if _, err := os.Stat(path); err == nil {
if newJWT, err := jwt.DecodeGeneric(theJWT); err != nil {
return err
} else if existing, err := os.ReadFile(path); err != nil {
return err
} else if existingJWT, err := jwt.DecodeGeneric(string(existing)); err != nil {
// skip if it can't be decoded
} else if existingJWT.ID == newJWT.ID {
return nil
} else if existingJWT.IssuedAt > newJWT.IssuedAt {
return nil
} else if newJWT.Subject != publicKey {
return fmt.Errorf("jwt subject nkey and provided nkey do not match")
} else if existingJWT.Subject != newJWT.Subject {
return fmt.Errorf("subject of existing and new jwt do not match")
}
}
store.Lock()
cb := store.changed
changed, err := store.write(path, publicKey, theJWT)
store.Unlock()
if err != nil {
return err
} else if changed && cb != nil {
cb(publicKey)
}
return nil
}
func xorAssign(lVal *[sha256.Size]byte, rVal [sha256.Size]byte) {
for i := range rVal {
(*lVal)[i] ^= rVal[i]
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Delete or migrate stale JWTs from the store directory before re-keying subjects
- Verify the existing file's claims; if it is a leftover, remove it and retry the Store/Merge
- Ensure key-generation/rotation flows update the directory in step with subject changes
Example fix
// before // dir still holds JWT for old subject A; storing JWT for subject B under same key store.Store(key, newSubjectJWT) // error: subject of existing and new jwt do not match // after store.delete(key) // remove stale JWT for old subject first (delete-enabled store) store.Store(key, newSubjectJWT)
Defensive patterns
Strategy: validation
Validate before calling
existing, err := loadExistingJWT(path)
if err == nil {
old, _ := jwt.DecodeAccountClaims(existing)
new, _ := jwt.DecodeAccountClaims(theJWT)
if old != nil && new != nil && old.Subject != new.Subject {
return fmt.Errorf("refusing overwrite across subjects")
}
}
err = store.Store(publicKey, theJWT) Try / catch
if err := store.Store(pub, jwt); err != nil && strings.Contains(err.Error(), "subject of existing") {
log.Printf("stale JWT on disk for %s; clean the store dir", pub)
} Prevention
- Clean stale JWTs after account re-keying/migrations
- Keep one operator/account namespace per store directory
- Monitor resolver dirs during key rotation
When it happens
Trigger: Calling Store/Merge where the file at path already contains a JWT whose Subject differs from theJWT.Subject (and the earlier checks for same ID / older IssuedAt did not short-circuit).
Common situations: Directory reuse across operator/account migrations where old JWTs remain; re-keying an account and writing the new JWT under the old path; corrupted or swapped JWT files in the resolver dir.
Related errors
- store is not set up to for delete
- jwt subject nkey and provided nkey do not match
- account jwt not found
- auth callout violation: auth callout response is not for exp
- auth callout violation: auth callout response is not for ser
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/03ff33d623869459.
Report an issue: GitHub.