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

  1. Delete or migrate stale JWTs from the store directory before re-keying subjects
  2. Verify the existing file's claims; if it is a leftover, remove it and retry the Store/Merge
  3. 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

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


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