Encryption at rest
Nimbus encrypts the local database files it owns: embedded SQLite and redb
tenant databases, the redb control-plane database, and libSQL replica
caches. Encryption is disabled by default and is enabled with the
--encryption-key-provider flag on nimbus start. External databases
(Postgres, MySQL, a remote libSQL primary) are encrypted by that database,
not by Nimbus.
For the full flag ↔ environment variable ↔ config-key table, see the configuration reference.
Enable encryption
Section titled “Enable encryption”-
Generate a 32-byte key file and lock down its permissions. Store it outside the data directory:
Terminal window openssl rand -out /secure/path/master.key 32chmod 400 /secure/path/master.key -
Start Nimbus with encryption enabled:
Terminal window nimbus start \--encryption-key-provider master-key-file \--encryption-master-key-file /secure/path/master.key
New tenant databases are created encrypted from then on. Nimbus generates a
random data-encryption key (DEK) per protected file and stores the wrapped
DEK in a sidecar manifest at <file>.nimbus-enc. Keep manifests with their
database files — a database without its manifest cannot be opened.
If you enable encryption on a server that already has plaintext database files, startup fails for those files with a “sidecar manifest is missing” error. Migrate them first — see Migrate existing plaintext data.
What gets encrypted
Section titled “What gets encrypted”| Local file | Cipher |
|---|---|
| Embedded SQLite tenant databases | SQLCipher |
| Embedded redb tenant databases | AES-256-GCM-SIV (per page) |
Control-plane redb database (nimbus-control.db) | AES-256-GCM-SIV (per page) |
| libSQL replica cache files | SQLCipher |
With --tenant-provider postgres or mysql, only the local control-plane
database is encryptable by Nimbus; tenant data encryption belongs to the
external database.
Choose a key provider
Section titled “Choose a key provider”Exactly one provider is active at a time. Flags belonging to a provider you
have not selected are rejected at startup, and encryption flags without
--encryption-key-provider are also rejected.
master-key-file (recommended)
Section titled “master-key-file (recommended)”One operator-managed 32-byte key file. Nimbus derives a unique wrapping key per protected file from it with HKDF-SHA256, so the single key protects many databases without reusing DEKs:
nimbus start \ --encryption-key-provider master-key-file \ --encryption-master-key-file /secure/path/master.keyThe file must contain exactly 32 bytes of raw key material — not hex, not base64.
key-dir
Section titled “key-dir”One 32-byte wrapping-key file per protected subject, for deployments that want explicit per-tenant or per-role key custody:
nimbus start \ --encryption-key-provider key-dir \ --encryption-key-dir /secure/path/keys/Key files are named by hex-encoding the subject descriptor recorded in the
manifest, with a .key extension. For example, the descriptor
db:sqlite:tenant:demo:demo.sqlite3 maps to
64623a73716c6974653a74656e616e743a64656d6f3a64656d6f2e73716c69746533.key.
Each file must contain exactly 32 raw bytes.
aws-kms
Section titled “aws-kms”Envelope encryption with AWS KMS-managed wrapping keys. The per-file manifest contract is unchanged — KMS replaces only the wrapping step:
nimbus start \ --encryption-key-provider aws-kms \ --encryption-aws-kms-key-id alias/nimbus-production \ --encryption-aws-region us-east-1--encryption-aws-kms-key-id is required; region and
--encryption-aws-endpoint-url (for VPC endpoints or local KMS emulators)
are optional, falling back to the standard AWS credential and region chain.
Nimbus calls GenerateDataKey to create DEKs, Decrypt to reopen
databases, and ReEncrypt during key rotation, and binds manifest metadata
into the KMS EncryptionContext — so grant the server identity those KMS
permissions on the selected key. A tampered manifest or wrong key surfaces
as a decrypt failure, not silent corruption.
Configure the admin commands
Section titled “Configure the admin commands”The nimbus encryption admin commands (status, migrate, export,
rotate-kek, rotate-dek) do not take server flags. They read the same
persistence and encryption settings from environment variables
(NIMBUS_ENCRYPTION_*, NIMBUS_DATA_DIR, …) or from the JSON config file
named by NIMBUS_CONFIG. Set those before running any admin command:
export NIMBUS_ENCRYPTION_KEY_PROVIDER=master-key-fileexport NIMBUS_ENCRYPTION_MASTER_KEY_FILE=/secure/path/master.keyEvery command below assumes this environment is in place. The commands refuse to run when encryption is not configured.
Check status
Section titled “Check status”From the CLI:
nimbus encryption status # human-readablenimbus encryption status --format jsonThis reports the enabled state, the key provider, and which local file families are covered under the current provider configuration.
On a running server, the same posture is available over HTTP. The endpoint is admin-only — send the local admin token (see the self-host quickstart for where the token lives):
curl -s http://localhost:8080/debug/encryption/status \ -H "Authorization: Bearer $NIMBUS_TOKEN"{ "enabled": true, "encrypted_families": ["embedded_sqlite", "control_plane_redb"], "descriptor": { "status": "enabled", "provider": "master_key_file", "path": "/secure/path/master.key" }}The response never contains key material.
Migrate existing plaintext data
Section titled “Migrate existing plaintext data”Stop the server before migrating files it has open. Then convert each plaintext database into an encrypted copy:
nimbus encryption migrate \ --source /data/tenant-a.sqlite3 \ --provider sqlite \ --tenant-id tenant-a--providerissqlite,redb, orlibsql-cache.--targetis optional; the default appends.encryptedto the source name. The target (and its manifest) must not already exist.--tenant-idnames the owning tenant. For redb it may be omitted only fornimbus-control.db.- Migration validates the encrypted copy afterwards unless you pass
--skip-validation, and publishes the target only after the copy succeeds. - Pass
--retire-sourceto delete the plaintext source (and its SQLite sidecar files) after a successful migration.
libSQL replica caches are not migrated this way: restart the server with encryption enabled and the cache rebuilds from the remote primary under the new key.
Rotate keys
Section titled “Rotate keys”Rotate the key-encryption key (KEK)
Section titled “Rotate the key-encryption key (KEK)”KEK rotation rewraps the manifest only — database pages are not rewritten. Run it with the current provider configured in the environment, and name the replacement provider on the command:
NIMBUS_ENCRYPTION_KEY_PROVIDER=master-key-file \NIMBUS_ENCRYPTION_MASTER_KEY_FILE=/secure/path/old.key \nimbus encryption rotate-kek \ --path /data/tenant-a.sqlite3 \ --new-master-key-file /secure/path/new.key-
--pathis the protected database file; pass--allwith a directory path to rotate every.nimbus-encmanifest in that directory. -
The replacement provider is inferred when you pass exactly one of
--new-master-key-file,--new-key-dir, or--new-aws-kms-key-id; set--new-key-providerexplicitly otherwise. -
To rotate onto AWS KMS:
Terminal window nimbus encryption rotate-kek \--path /data/tenant-a.sqlite3 \--new-key-provider aws-kms \--new-aws-kms-key-id alias/nimbus-production \--new-aws-region us-east-1
After rotation, use the new key configuration for all subsequent starts and admin commands.
Rotate the data-encryption key (DEK)
Section titled “Rotate the data-encryption key (DEK)”DEK rotation replaces the per-file key and is provider-specific:
nimbus encryption rotate-dek \ --path /data/tenant-a.sqlite3 \ --provider sqlite \ --tenant-id tenant-a- sqlite — checkpoints WAL state, backs up the database artifact set
to
.bakcopies, rekeys in place with SQLCipher’sPRAGMA rekey, then updates the manifest. On failure the backups are restored automatically. - redb — re-encrypts every page into a staged file under fresh nonces, then commits the staged database and manifest together. An interrupted rotation is recovered automatically on the next rotate or server start.
- libsql-cache — rotates the manifest to a fresh DEK and deletes the local cache files; stop any running instance first, and the next start rebuilds the cache from the remote primary under the new key.
Pass --skip-backup to skip the .bak copies if you have your own backup
in place.
Recover
Section titled “Recover”To export an encrypted database back to plaintext (disaster recovery, or moving data somewhere that needs plaintext):
nimbus encryption export \ --source /data/tenant-a.sqlite3 \ --target /recovery/tenant-a.sqlite3 \ --provider sqlite \ --tenant-id tenant-a--target is required and must not exist. libSQL caches cannot be
exported — rebuild them from the remote primary instead.
Backup rules:
- Back up the
.nimbus-encmanifest sidecars together with the database files. KMS access alone cannot recover a database whose manifest is lost. - Back up the key material itself — the master key file or key directory —
or, for
aws-kms, preserve access to the KMS key. - Encrypted backups without the keys are unrecoverable. Test restores before you depend on them. See backup and restore.
Troubleshoot
Section titled “Troubleshoot”- Key file missing or unreadable — the configured master key or per-subject key file path does not exist or is not readable. Startup fails before serving traffic.
- Wrong key size — key files must be exactly 32 bytes of raw binary data, not hex or base64.
- “sidecar manifest is missing” — encryption is enabled for an existing plaintext file. Migrate it first (see above).
- Cannot open an encrypted database — the configured provider cannot unwrap the DEK in the manifest, or the file and manifest no longer match. Verify the key material and restore the file/manifest pair from backup.
- AWS KMS errors — key-not-found means the key ID or alias does not resolve in the selected region; permission-denied means the caller lacks the KMS operation on the key; network errors point at region, endpoint override, VPC routing, or the AWS credential chain.