In this episode we'll cover the first and most commonly used secrets engine: the KV Secrets Engine. We'll compare KV v1 and KV v2 with versioning, and practice the put, get, list, metadata, rollback, destroy, and undelete operations.

After discussing Vault's core architecture in episode 3 — from the storage backend and Vault Core Engine, the initialization process with Shamir's Secret Sharing, to the root token risk — this episode enters the phase most often touched in a Vault practitioner's daily work: secrets engines. And the starting point is the most fundamental one: the KV Secrets Engine.
Why does this topic matter in the real world? Because nearly every Vault implementation running in production starts with KV. Applications need API keys, database connection strings, SMTP credentials, or third-party integration tokens — and all those "static secrets" are first stored in KV before being consumed by more sophisticated secrets engines. Understanding how mount paths work, the difference between KV v1 and KV v2, and its versioning mechanism will be the foundation that keeps you from getting lost when reading Vault documentation or writing policies (which we'll cover in episode 9).
Misunderstanding this difference isn't just a terminal error — it can cause applications to fail reading secrets, old secrets to be accidentally restored, or sensitive data that should have been "gone" to still be undeletable. Let's break it all down.
Before discussing KV, it's important to understand its position. A secrets engine is a Vault component that stores, generates, or encrypts secret data. It's mounted at a specific path, and each path becomes both an API access point and an isolation boundary.
Imagine Vault as a bank building. Each secrets engine is one division in the building with its own entrance:
| Secrets Engine | Analogy | Default Path |
|---|---|---|
| KV | The safe for storing valuables | secret/ |
| Database | The machine that prints temporary ATM cards | database/ |
| Transit | The data encryption room (doesn't store data) | transit/ |
| PKI | The certificate-issuing division | pki/ |
Each mount has its own path so one secrets engine can't interfere with another. This is what allows a single Vault server to serve many needs at once.
To mount KV, use the vault secrets enable command. KV v2 is the version that supports versioning, and it's the recommended default since Vault 0.10:
vault secrets enable -path=secret kv-v2Output:
Success! Enabled the kv-v2 secrets engine at: secret/Tip
By default, the KV mount path is secret/. But you're free to choose another path name with the -path flag — for example -path=app-prod to separate production secrets from development ones. That path name later becomes part of the API URL, e.g. GET /v1/secret/data/my-app. So choosing a mount name is a design decision, not just a formality.
To make sure the engine is mounted, list all secrets engines:
vault secrets listOutput (example):
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_xxa3... per-token private secret storage
identity/ identity identity_xxa3... identity store
secret/ kv kv_xxa3... n/a
sys/ system system_xxa3... system endpoints used for control, policy and debuggingNotice two things: the secret/ mount of type kv, and the built-in mounts cubbyhole/, identity/, and sys/ which can't be disabled. The cubbyhole mount is per-token private secret storage, which we'll discuss in episode 13.
This is where many beginners get lost. KV has two versions with fundamentally different behavior:
| Aspect | KV v1 | KV v2 |
|---|---|---|
| Versioning | None — rewriting = permanent overwrite | Yes — every write creates a new version, history is stored |
| Metadata | None | Yes (created time, version, deletion time, etc.) |
| Soft delete | None | Yes (destroy marks a version destroyed, still undeletable) |
| Undelete | None | Yes (undelete restores a soft-deleted version) |
| Permanent destroy | None | Yes (destroy with a specific version, or metadata delete for the whole path) |
| Rollback to an old version | Impossible | Possible with kv rollback |
| API path | secret/my-app | secret/data/my-app (/data/ prefix) |
| When to use | Legacy / migration | All new usage (recommended) |
In short: KV v2 treats every value as a collection of restorable versions, while KV v1 treats it as a single value that can be overwritten. In the real world, the ability to roll back is extremely valuable — for example, when a bad configuration overwrites a production secret, you don't need to panic because the previous version is still stored.
Important
The path difference is the most common source of errors: in KV v2, secrets are stored at secret/data/<path>, while the vault kv CLI CRUD commands automatically add the /data/ prefix for you. If you use the raw API (e.g. curl), don't forget to add the /data/ prefix when writing/reading. Conversely, if you try vault write secret/foo directly without the kv subcommand on a KV v2 mount, Vault will reject it with a path-not-found error.
kv put, kv get, kv listLet's start by writing our first secret:
vault kv put secret/my-app/db \
username="appuser" \
password="SuperSecret-2026!"Output:
== Secret Path ==
secret/data/my-app/db
======= Metadata =======
Key Value
--- -----
created_time 2026-08-02T08:15:00.123456Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1Notice the output: Vault shows version 1 and created_time. This is the marker that you're dealing with KV v2 — every write operation produces a new version, not just an overwrite.
To read it back:
vault kv get secret/my-app/dbOutput:
== Secret Path ==
secret/data/my-app/db
======= Metadata =======
Key Value
--- -----
created_time 2026-08-02T08:15:00.123456Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
===== Data =====
Key Value
--- -----
password SuperSecret-2026!
username appuserTo read a specific version, use the -version flag:
vault kv get -version=1 secret/my-app/dbTo list all paths stored under the mount:
vault kv list secret/my-appOutput:
Keys
----
dbTip
Get into the habit of writing multi-key secrets in a single kv put call (like username + password above) rather than splitting them across many paths. Besides saving API operations, it makes the secret one logical unit that's easy to roll back and audit.
kv metadataOne of KV v2's advantages is metadata. To view the full version history of a secret:
vault kv metadata get secret/my-app/dbOutput:
========== Metadata Path ==========
secret/metadata/my-app/db
========= Metadata =========
Key Value
--- -----
created_time 2026-08-02T08:15:00.123456Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2
====== Version Tree ======
Key Value
--- -----
1 2026-08-02T08:15:00.123456Z
2 2026-08-02T08:30:45.987654ZIf we rewrite the secret, the version increases to 3, and version 1's data is still stored behind the scenes. That's the power of history: every change is recorded, complete with timestamps.
Now we enter the most interesting part — managing the version lifecycle.
1. Soft delete with kv destroy
kv destroy destroys a specific version "softly" (soft delete). That version can no longer be read, but its metadata still exists and can be restored:
vault kv destroy -versions=2 secret/my-app/dbOutput:
Success! Data written to: secret/destroy/my-app/dbTry reading the destroyed version:
vault kv get -version=2 secret/my-app/dbOutput:
No value found at secret/data/my-app/db2. Restoring with kv undelete
Realize version 2 is actually still needed? Restore it anytime as long as it hasn't been permanently destroyed:
vault kv undelete -versions=2 secret/my-app/dbSuccess! Data written to: secret/undelete/my-app/dbAfter that, vault kv get -version=2 secret/my-app/db shows the data again.
3. Rolling back to an old version
What if the latest configuration (version 3) turns out wrong and you want to go back to version 1? Use kv rollback — this command reads the old version's data and writes it as a new version (without deleting history):
vault kv rollback -version=1 secret/my-app/dbOutput:
Success! Data written to: secret/data/my-app/dbWarning
kv rollback doesn't delete the wrong version — it creates a new version containing a copy of the old version's data. This is a safe-by-design behavior: the audit trail stays complete. If you want to delete a path's entire history (for example, because the secret is no longer used and contains highly sensitive data), use vault kv metadata delete secret/my-app/db, which permanently deletes the data and all its versions.
Here are the traps most often encountered when working with KV:
| Mistake | Symptom | Solution |
|---|---|---|
Forgetting the /data/ prefix when using the raw API | Error 404 no handler exists for path "secret/my-app/db" | Add the prefix: GET /v1/secret/data/my-app/db |
Using vault write secret/foo on a KV v2 mount | Path-not-found error | Use vault kv put secret/foo, which handles the /data/ prefix |
Thinking kv destroy deletes permanently | Version "gone" when it can still be undeleted | Understand the difference between soft delete and metadata delete |
| Not thinking about the mount name from the start | Path migration while production is running | Plan a clear -path since the setup episode |
| Creating one path per key | Many API operations & hard to roll back | Group logically related keys into one path |
Forgetting to add the list capability in policies | kv list fails even though read succeeds | Add the list capability on the appropriate path (episode 9) |
Caution
KV v2 with versioning sounds "safe", but remember: versioning also means secret history (including old passwords) stays stored as long as the metadata path hasn't been deleted. If you manage data that must be destroyed (compliance like PCI-DSS or data retention policies), make sure there's a vault kv metadata delete procedure to remove history, not just per-version kv destroy.
KV v1 is indeed no longer recommended for new usage, but you'll still encounter it in many long-running Vaults. Its behavior is simple: every vault kv put immediately overwrites the old value with no history.
vault secrets enable -path=legacy kvvault kv put legacy/api-key value="old-secret-123"
vault kv get legacy/api-keyWhen is KV v1 still used? Generally for compatibility with old tooling that already calls paths without /data/, or as a step in a gradual migration. Beyond that, always choose KV v2.
In this episode 4, we've dissected the first and most fundamental secrets engine: the KV Secrets Engine. You've learned the concept of a mount path as an API access point, how to enable KV v2 with vault secrets enable, a thorough KV v1 vs KV v2 comparison, and all the core operations — kv put, kv get, kv list, kv metadata, kv rollback, kv destroy, and kv undelete. The main trap to remember: the /data/ path difference in KV v2 and the behavior difference between soft delete and permanent delete.
The essence of this episode: KV v2 changes how you think about "storing secrets" — from merely overwriting values to managing a restorable history. This capability is what makes KV safe to use in production: configuration mistakes are no longer a permanent disaster.
In episode 5, we'll jump to a more advanced paradigm: the Dynamic Database Secrets Engine — where database credentials are no longer stored permanently, but generated on-demand with short TTLs and automatic destruction. This shifts the mindset from "storing secrets" to "lending secrets". Keep your enthusiasm up!