Vault’s KV v2 secrets engine doesn’t actually store new versions of secrets when you update them; it reuses the same underlying storage location and simply overwrites the data.

Let’s see it in action. Imagine we have a secret at secret/data/myapp/db-config. We’ll write an initial version:

vault kv put secret/myapp/db-config username="admin" password="password123"

Vault responds with a confirmation, and importantly, an index:

Success! Data written to: secret/myapp/db-config

Now, let’s update the password:

vault kv put secret/myapp/db-config password="new_super_secret_password"

Again, success. But what about the version? If we check the history:

vault kv list -version secret/myapp/db-config

We see something like:

Key        Version
myapp/db-config  2

Notice how the version number incremented. This implies a new, separate storage slot was created. But that’s not quite right. The metadata for the secret has been updated, and the data itself has been overwritten. Vault v2’s "versioning" is more about tracking a history of changes through overwrites and providing a mechanism to roll back, rather than creating entirely new, immutable data blobs for each change. The underlying storage is still a single logical key for myapp/db-config.

This design solves the problem of managing sensitive configuration data across distributed systems. Instead of scattering secrets across configuration files or environment variables, you centralize them in Vault, which provides encryption at rest, access control, and an audit trail. The versioning, while not strictly immutable storage, allows you to recover from accidental misconfigurations or malicious changes by reverting to a previous state.

Internally, Vault uses a backend storage system (like Consul, etcd, or even a file system) to store key-value pairs. When you put a secret in KV v2, Vault first reads the existing data for that key, increments the version counter associated with that key, stores the previous version’s data in a separate "versions" location within its storage, and then writes the new data to the primary location for the current version. The list -version command queries this version metadata.

The key levers you control are primarily:

  • versioning_enabled: This is set to true by default when you enable the KV v2 engine. You can’t disable it after creation.
  • max_versions: This parameter, configurable at the engine level during creation or by editing the engine configuration, dictates how many historical versions Vault will retain. If max_versions is set to 5, Vault will keep the current version and the 4 previous ones. Older versions are automatically purged.
  • cas (Check-And-Set): This is a powerful feature for optimistic concurrency control. When reading a secret, you get back a cas field (the current version number). When writing, you can include this cas number. Vault will only perform the write if the cas number matches the current version of the secret. If another client has updated the secret in the meantime, the write fails, preventing lost updates.

The difference between kv put and kv hide is subtle but important for understanding version management. kv put increments the version and makes the new data the current active version. kv hide marks the current active version as deleted without incrementing the version counter. This means the data for that version is still accessible if you explicitly request it by version number, but it’s no longer considered the "latest" and won’t be returned by a regular kv get. It’s effectively a soft delete that can be undone by unhiding.

When you delete a secret using vault kv delete secret/myapp/db-config, Vault marks the current version as deleted and increments the version counter. The old data is still retained until max_versions is exceeded or you explicitly purge deleted versions.

The vault kv undelete command can bring back a "hidden" or "deleted" version, making it the current active version again, provided it hasn’t been purged due to max_versions.

The next concept you’ll likely encounter is how to manage these versions at scale, particularly with automated systems, and the implications of max_versions on storage consumption and recovery capabilities.

Want structured learning?

Take the full Vault course →