nats-io/nats-server · critical
unable to recover keys
Error message
unable to recover keys
What it means
Returned by the JetStream encryption key recovery routine when none of the available key sources (TPM-sealed keys / pass-protected key file entries) could successfully open the encrypted key blob. Every candidate key failed to decrypt the stored key material.
Source
Thrown at server/jetstream.go:331
ns := kek.NonceSize()
seed, err := kek.Open(nil, ekey[:ns], ekey[ns:], nil)
if err != nil {
continue
}
aek, err := genEncryptionKey(prf.StoreCipher, seed)
if err != nil {
continue
}
if aek.NonceSize() != kek.NonceSize() {
continue
}
plain, err := aek.Open(nil, buf[:ns], buf[ns:], nil)
if err != nil {
continue
}
return plain, i > 0, nil
}
return nil, false, fmt.Errorf("unable to recover keys")
}
// Check to make sure directory has the jetstream directory.
// We will have it properly configured here now regardless, so need to look inside.
func (s *Server) checkStoreDir(cfg *JetStreamConfig) error {
fis, _ := os.ReadDir(cfg.StoreDir)
// If we have nothing underneath us, could be just starting new, but if we see this we can check.
if len(fis) != 0 {
return nil
}
// Let's check the directory above. If it has us 'jetstream' but also other stuff that we can
// identify as accounts then we can fix.
fis, _ = os.ReadDir(filepath.Dir(cfg.StoreDir))
// If just one that is us 'jetstream' and all is ok.
if len(fis) == 1 {
return nil
}
View on GitHub (pinned to 3a66a489d2)
Solutions
- Restore the original key file / TPM keys used when the data was encrypted (from backup)
- If data is disposable, delete the encrypted key and re-enable JetStream with a fresh key (data loss)
- Check that the server's key/TPM configuration (JetStreamKey, JetStreamTpm.KeysFile) matches the machine and keys that sealed the data
- Verify key file permissions and integrity (not truncated/edited)
Example fix
// before // keys file regenerated; old encrypted store present -> unable to recover keys // after // restore backed-up keys file: // cp /backup/nats/keys.json /etc/nats/jetstream-keys.json && systemctl restart nats-server
Defensive patterns
Strategy: validation
Validate before calling
// Before starting the server, verify the key material matches the sealed store:
// - confirm the JetStream key / TPM keys file is the same one used when the store was encrypted
// - back it up and diff on deploy
if _, err := os.Stat(keysFile); err != nil {
return fmt.Errorf("jetstream keys file missing: %w", err)
} Prevention
- Back up the encryption key file / TPM keys with the same care as the data
- Never regenerate keys while encrypted JetStream data remains on disk
- Avoid moving JetStream store dirs between machines with different TPMs
- Document and version key rotation procedures with re-encryption steps
When it happens
Trigger: Recovering the JetStream encryption key at startup when the AES key file/TPM keys don't match the key that sealed the data (aek.Open fails for all i keys), e.g. keys were rotated, regenerated, or the store dir was moved between machines with different TPM keys.
Common situations: Regenerating the key file while old JetStream data remains on disk; moving storage directories between hosts; TPM key hierarchy changed (e.g. after clearing the TPM); wrong passphrase-supplied key entries.
Related errors
- JetStream encryption key may not be used with TPM options
- TPM functionality is not supported on this platform
- loading encryption for block %d failed: %w
- unable to seal data: %v
- got corrupted escaped character
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/0ce775e4d685175b.
Report an issue: GitHub.