Learn Vault - KV Secrets Engine (Key-Value v1 vs KV v2 with Versioning)
Episode 4 of 26

Learn Vault - KV Secrets Engine (Key-Value v1 vs KV v2 with Versioning)

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.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Introduction

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.

Main Discussion

What Is a Secrets Engine?

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 EngineAnalogyDefault Path
KVThe safe for storing valuablessecret/
DatabaseThe machine that prints temporary ATM cardsdatabase/
TransitThe data encryption room (doesn't store data)transit/
PKIThe certificate-issuing divisionpki/

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.

Enabling the KV Secrets Engine

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:

Enable KV v2 at the mount path 'secret'
vault secrets enable -path=secret kv-v2

Output:

vault secrets enable output
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:

List active secrets engines
vault secrets list

Output (example):

vault secrets list output
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 debugging

Notice 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.

KV v1 vs KV v2: What's the Difference?

This is where many beginners get lost. KV has two versions with fundamentally different behavior:

AspectKV v1KV v2
VersioningNone — rewriting = permanent overwriteYes — every write creates a new version, history is stored
MetadataNoneYes (created time, version, deletion time, etc.)
Soft deleteNoneYes (destroy marks a version destroyed, still undeletable)
UndeleteNoneYes (undelete restores a soft-deleted version)
Permanent destroyNoneYes (destroy with a specific version, or metadata delete for the whole path)
Rollback to an old versionImpossiblePossible with kv rollback
API pathsecret/my-appsecret/data/my-app (/data/ prefix)
When to useLegacy / migrationAll 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.

Basic Operations: kv put, kv get, kv list

Let's start by writing our first secret:

Write an application secret
vault kv put secret/my-app/db \
  username="appuser" \
  password="SuperSecret-2026!"

Output:

vault kv put output (KV v2)
== 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

Notice 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:

Read the secret
vault kv get secret/my-app/db

Output:

vault kv get 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            1
 
===== Data =====
Key         Value
---         -----
password    SuperSecret-2026!
username    appuser

To read a specific version, use the -version flag:

Read a specific version
vault kv get -version=1 secret/my-app/db

To list all paths stored under the mount:

List secrets
vault kv list secret/my-app

Output:

vault kv list output
Keys
----
db

Tip

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.

Versioning: Viewing History with kv metadata

One of KV v2's advantages is metadata. To view the full version history of a secret:

View metadata and version history
vault kv metadata get secret/my-app/db

Output:

vault kv metadata get output
========== 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.987654Z

If 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.

The Full Lifecycle: Destroy, Undelete, and Rollback

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:

Soft-delete version 2
vault kv destroy -versions=2 secret/my-app/db

Output:

vault kv destroy output
Success! Data written to: secret/destroy/my-app/db

Try reading the destroyed version:

Read a destroyed version
vault kv get -version=2 secret/my-app/db

Output:

kv get output for a destroyed version
No value found at secret/data/my-app/db

2. Restoring with kv undelete

Realize version 2 is actually still needed? Restore it anytime as long as it hasn't been permanently destroyed:

Restore a destroyed version
vault kv undelete -versions=2 secret/my-app/db
vault kv undelete output
Success! Data written to: secret/undelete/my-app/db

After 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):

Rollback to version 1
vault kv rollback -version=1 secret/my-app/db

Output:

vault kv rollback output
Success! Data written to: secret/data/my-app/db

Warning

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.

Common KV Mistakes

Here are the traps most often encountered when working with KV:

MistakeSymptomSolution
Forgetting the /data/ prefix when using the raw APIError 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 mountPath-not-found errorUse vault kv put secret/foo, which handles the /data/ prefix
Thinking kv destroy deletes permanentlyVersion "gone" when it can still be undeletedUnderstand the difference between soft delete and metadata delete
Not thinking about the mount name from the startPath migration while production is runningPlan a clear -path since the setup episode
Creating one path per keyMany API operations & hard to roll backGroup logically related keys into one path
Forgetting to add the list capability in policieskv list fails even though read succeedsAdd 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: When Is It Still Relevant?

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.

Enable KV v1
vault secrets enable -path=legacy kv
Write & read in KV v1 (no /data/ prefix)
vault kv put legacy/api-key value="old-secret-123"
vault kv get legacy/api-key

When 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.

Conclusion

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!

Learn Vault - KV Secrets Engine (Key-Value v1 vs KV v2 with Versioning) | Learn Secret Management with HashiCorp Vault