How HashiCorp Vault Handles Secret Leases
A deep dive into Vault's lease system — how secrets get a TTL, how they're renewed, and what happens when they expire in a distributed cluster.
What is a secret lease?
When Vault issues a dynamic secret — a database credential, an AWS access key, a TLS certificate — it doesn't hand you a credential and walk away. Every dynamic secret is paired with a lease: a structured record that says "this secret is valid for N seconds, and can be renewed up to M times."
This is one of Vault's most powerful and underappreciated design decisions. It turns the problem of secret rotation from a scheduled maintenance task into a first-class runtime operation.
The anatomy of a lease
A lease record stored in Vault's backend looks roughly like this:
type leaseEntry struct {
LeaseID string
ClientToken string
Path string
Data map[string]interface{}
IssueTime time.Time
ExpireTime time.Time
RenewTime time.Time
MaxTTL time.Duration
Renewable bool
}
Every time a secret is issued, Vault creates one of these entries in its storage backend, indexes it by expiry time, and registers it with the expiration manager.
The expiration manager
The expiration manager is a background goroutine that runs a heap-ordered priority queue of leases, sorted by their ExpireTime. Every time a lease nears expiration, the manager wakes up and either:
- Notifies the caller that their lease has expired (and revokes the underlying secret)
- Automatically renews the lease if the secret backend supports it
Here's the core of the expiration loop in simplified Go:
func (m *ExpirationManager) runExpirationLoop() {
for {
select {
case <-m.quitCh:
return
case <-time.After(m.nextExpiration()):
m.expireLeases()
}
}
}
The beautiful part: the expiration manager is deterministic and restartable. If Vault crashes and restarts, it reads all lease entries from the backend, rebuilds the heap, and picks up right where it left off. No secrets silently outlive their intended lifetime.
Raft and lease consistency
On a Vault HA cluster using integrated Raft storage, lease operations flow through the Raft log before being applied to state. This means:
- A lease creation is not confirmed until it has been committed to a quorum of nodes
- A lease revocation is linearizable — once you call
vault lease revoke, no follower will ever serve that lease again
This is a hard constraint: you can't have a database credential that Vault thinks is revoked but one replica still considers valid. The Raft log is the single source of truth.
What this means for you as a consumer
If you're calling Vault from an application:
- Always cache the
lease_idalongside the secret - Set up a renewer goroutine (the official Go SDK has
vault.Renewerbuilt-in) - Don't use
MaxTTLas a crutch — treat every secret as temporary and test your rotation path regularly
The lease system is what makes Vault operationally honest about secret lifecycles. It's one of the design decisions I find most interesting to work on.
This is the first of a series on Vault internals. Next up: how Vault's token hierarchy and policies interact during auth.