Backing up a secrets vault without backing up the secrets
A vault backup often holds every value, or the key that opens them. Which part of each design's backup is secret, where to keep it, and how to test it.
Crunu teamPublished 7 min read
On this page
A backup of a secrets store can be a copy of every secret, or one key away from it. A backup of a folder of .env files is the secrets. A database dump of a server-side vault is ciphertext, but the root key in the server's configuration opens it, and the two can end up in the same snapshot. The rule that holds across designs: split the backup into the data and whatever opens it, keep them in different places with different people able to reach them, and test a restore before you need one. In a design where the server never holds a decryption key, the data part contains no plaintext values and can be stored like any other archive. You still keep the server's settings file separately, and you still plan for what a restore rolls back.
The guide to self-hosting a secrets vault compares the designs themselves. This post is about their backups.
What's in the backup, by design
| Design | The data | What opens it |
|---|---|---|
.env files on a share, in a bucket or a repository | plaintext values | nothing needed |
| SOPS, git-crypt, Ansible Vault | encrypted files | the age, PGP or KMS keys, or the vault password |
| HashiCorp Vault, OpenBao | a storage snapshot | unseal key shares, or the auto-unseal KMS key |
| Infisical, self-hosted | a Postgres dump | the root encryption key in the server's environment, or in an HSM |
| Crunu, self-hosted on the Enterprise plan | an export archive with ciphertext, public keys and signed documents | no server-side key opens values; environment keys live only with people and machines |
The middle rows share a pattern. Infisical's security documentation describes "a 256-bit AES key provided by the operator as an environment variable" that encrypts the internal KMS root key, which in turn encrypts per-project data keys stored in the database, a form of envelope encryption. Decryption "requires both server configuration (the root encryption key) and data from the storage backend", so "compromising the database alone is insufficient" (Infisical: security). That's a sound design, and it makes the backup question concrete: if the dump and the environment file sit in the same server image, the image is a full copy of every secret.
Vault's version has a sharp edge in the other direction: "If the seal mechanism or its keys are permanently deleted, then the Vault cluster cannot be recovered, even from backups" (Vault: seal/unseal). Losing what opens the backup is as final as losing the backup.
Four mistakes that turn a backup into a leak
- Whole-machine snapshots. A VM or disk snapshot of the vault server contains the database and the configuration file with its keys. Anyone who can read the snapshot bucket can read everything.
- One bucket for everything. Database dumps and the
.envcopied "for completeness" into the same place. Same result. - Backup access outlives people. Removing someone from the vault doesn't remove them from the storage account where backups go. For a plaintext design, backup access is read access to every secret.
- Deleted values live on. Removing a value from the store doesn't remove it from last month's backups. Retention decides how long it survives.
None of these need an attacker to be clever. They need someone to have kept access they shouldn't have.
What a blind-server backup looks like
Crunu's server stores ciphertext, public keys, token HMACs, signed documents and metadata. Its backup is an archive of exactly that, made with crunu-server export. Nothing in it opens a variable: environment keys belong to people and machines, and passwords and Secret Keys never reach the server (Crunu docs: backups and restore).
The backup still has two parts, with very different secrecy:
| Part | What's inside | Where it goes |
|---|---|---|
| the export archive | ciphertext and metadata: names of organizations, projects, environments and machines, email addresses, the audit log | any reliable storage where that metadata may be seen |
the server's .env | SERVER_KEY, BOOTSTRAP_TOKEN, the database address with its password, email keys | a password manager or a safe, never next to the archive |
The reason for the split is specific. Without SERVER_KEY, a restored server recognizes no session, share link, invitation or authenticator-app secret; environment data survives, but everyone signs in again. Together with an archive, SERVER_KEY reveals the authenticator-app secrets and the alert webhook addresses. So the .env is a backup-critical secret, and the archive and the .env never share a location.
A daily export and a 30-day retention look like this on a self-hosted Docker server:
node scripts/crunu-server.mjs export --env-file .env --from sqlite:/data/crunu.db --out backups/crunu-$(date +%F).ndjson.gzThe archive is written to a temporary file and appears under its name only when complete, so an interrupted export never looks like a finished backup. On Postgres platforms and Cloudflare, run the same command from any machine that can reach the database; the server has no built-in backup scheduler.
The honest limits of "no plaintext"
- Metadata is real data. Environment names, email addresses and the audit log say a lot about a company. Treat the archive as confidential, just not as secret.
- History is in the archive. The archive holds the ciphertext of each environment's last 100 versions. When someone is removed, their environments get new keys, so their old key opens no version made after the removal, even from a backup. What they could read before they left, they could read anyway, which is why removal comes with a reissue checklist.
- The
.envis still a secret. "No plaintext in the backup" applies to the archive only.
Testing a backup
An untested backup is a hope. Crunu's archive has a check built in:
node scripts/crunu-server.mjs verify --in backups/crunu-2026-10-24.ndjson.gzIt checks the checksum and record counts and prints the file's SHA-256. The stronger test is a trial restore into an empty SQLite file on a laptop, then signing in on top of it. Do that monthly, and before any server move or upgrade.
For Vault and Infisical, the equivalent is a restore into a scratch cluster or database using the keys from where you keep them, not from the production server. If the test only works with the production server's configuration at hand, your backup depends on a machine you're trying to survive losing.
What a restore rolls back
A restore returns the database to the moment of the backup. Everything after it is gone: new versions, invited people, enrolled machines, granted access. In a design with rollback protection, there's a second effect.
Crunu's clients and agents remember the newest state number of each environment and reject lower ones, which protects against a server that serves old data. A restore looks exactly like that to them. After a restore you made yourself, machines and terminals need their memory of the server reset (state.json for the agent and the command line), and browsers need the site's data cleared before signing in again. The docs give the exact steps, with a warning worth repeating: if a rollback warning appears without a restore, don't reset anything. The server may be serving old data on purpose.
That's a property to weigh, not a flaw. A vault that accepts an older state silently after a restore would also accept it silently from a compromised server.
Backups when the platform is managed
On platforms where someone else runs the database, there are two layers of backup, and both count.
- The platform's own. Cloudflare D1 Time Travel, Neon point-in-time restore, Cloud SQL automatic backups and RDS snapshots restore the database in place, inside the same provider and usually the same account. Turn them on.
- Your export. An archive you take out of the platform is the one that survives losing the account, moves to another platform and can be verified on its own. Crunu's docs treat platform backups as an extra layer, not a replacement, and recommend copying the latest archive to another provider weekly.
The split rule applies here too. The platform's backup of the database and the platform's secret store holding the server's settings are in the same account, so one compromised account reaches both. Keep a copy of the .env outside the platform.
A backup plan for any design
- Split the data from what opens it, and store them with different providers or different accounts.
- Restrict who can read the backup storage, and review it when people leave.
- Retain for as long as you need to recover, and no longer, because old backups keep deleted values alive in plaintext designs.
- Test a restore on a schedule, using only what's in the backup locations.
- Write down what a restore undoes and who has to do what afterwards.
The glossary entry on encrypted backups has the short definition.
Sources
- Security internals, Infisical Docs, checked October 30, 2026.
- Seal/Unseal, HashiCorp Vault docs, checked October 30, 2026.