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

  1. Restore the original key file / TPM keys used when the data was encrypted (from backup)
  2. If data is disposable, delete the encrypted key and re-enable JetStream with a fresh key (data loss)
  3. Check that the server's key/TPM configuration (JetStreamKey, JetStreamTpm.KeysFile) matches the machine and keys that sealed the data
  4. 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

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


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