Skip to content

How-to Guides

Scope

Task recipes for running HashiCorp Vault: install, deploy on VMs and Kubernetes, initialize and unseal, configure auth and secrets engines, audit, back up, monitor, upgrade to 2.x, and troubleshoot. Commands target Vault 2.1 unless marked. Background is in Explanation; tables of options are in Reference.

Install Vault

Linux Packages (Debian/Ubuntu)

wget -O - https://apt.releases.hashicorp.com/gpg | \
  sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
  sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vault
vault version

Container Image

# Official image; tags include 2.1.1, 2.1 and latest (checked 2026-09-25)
docker run --rm -p 8200:8200 hashicorp/vault:2.1.1 server -dev -dev-listen-address=0.0.0.0:8200

Dev mode is for testing only

vault server -dev runs in memory, unsealed, with a root token printed to the console and TLS off.

Deploy a Production Cluster

Choose an HA Pattern

Pattern Storage Auto-unseal When
Integrated Storage (Raft) Built-in AWS KMS, Azure Key Vault, GCP CKMS Default for new deployments
Consul backend Dedicated Consul cluster Cloud KMS Existing Consul-backed clusters
External database (for example PostgreSQL) Managed DB Cloud KMS Only when you accept limited HashiCorp support

Configure a Raft Node with AWS KMS Auto-Unseal

# /etc/vault.d/vault.hcl
ui            = true
disable_mlock = true   # required to be explicit with Raft since 1.20; disable swap on the host
api_addr      = "https://vault-0.example.internal:8200"
cluster_addr  = "https://vault-0.example.internal:8201"

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-0"
  retry_join {
    leader_api_addr = "https://vault-1.example.internal:8200"
  }
  retry_join {
    leader_api_addr = "https://vault-2.example.internal:8200"
  }
}

listener "tcp" {
  address         = "0.0.0.0:8200"
  cluster_address = "0.0.0.0:8201"
  tls_cert_file   = "/opt/vault/tls/tls.crt"
  tls_key_file    = "/opt/vault/tls/tls.key"
  tls_min_version = "tls12"
}

seal "awskms" {
  region     = "us-east-1"
  kms_key_id = "alias/vault-unseal"
}
sudo systemctl enable --now vault
export VAULT_ADDR="https://vault-0.example.internal:8200"
vault status

Enable Mutual TLS on the Listener

listener "tcp" {
  address                            = "0.0.0.0:8200"
  cluster_address                    = "0.0.0.0:8201"
  tls_cert_file                      = "/etc/vault/tls/tls.crt"
  tls_key_file                       = "/etc/vault/tls/tls.key"
  tls_client_ca_file                 = "/etc/vault/tls/ca.crt"
  tls_require_and_verify_client_cert = true
  tls_min_version                    = "tls12"
}

Reload certificates without a restart by sending SIGHUP to the Vault process. Rotate certificates before expiry, for example with cert-manager or Vault PKI plus Vault Agent.

Deploy on Kubernetes with Helm

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
# Chart 0.34.1 (2026-08-13) defaults to Vault 2.0.4; pin server.image.tag for newer releases
helm install vault hashicorp/vault \
  --namespace vault --create-namespace \
  --set server.ha.enabled=true \
  --set server.ha.replicas=3 \
  --set server.ha.raft.enabled=true \
  --set server.auditStorage.enabled=true \
  --set injector.enabled=true

For containerized Vault 2.0.2 and later, keep disable_mlock = true in the server config because official images no longer carry IPC_LOCK.

Initialize and Unseal

# Shamir seal: 5 shares, threshold 3 (the defaults), each share PGP-encrypted to its holder
vault operator init -key-shares=5 -key-threshold=3 \
  -pgp-keys="alice.asc,bob.asc,carol.asc,dave.asc,erin.asc"

# Manual unseal: run on every node, three different key holders
vault operator unseal   # prompts for a share

# Auto-unseal: init returns recovery keys instead
vault operator init -recovery-shares=5 -recovery-threshold=3

# Join additional Raft nodes if retry_join is not configured
vault operator raft join https://vault-0.example.internal:8200

Recovery keys

With auto-unseal, the key shares are recovery keys. They authorize root-token generation and rekeying but cannot decrypt data. Store them in separate locations; losing the KMS key still loses the data.

Bootstrap, Then Revoke the Root Token

vault login            # initial root token, once
vault audit enable file file_path=/var/log/vault/audit.log
vault auth enable oidc # or another human auth method
vault policy write admin admin.hcl
vault token revoke -self

# Break-glass later: generate a new root token with a quorum of key holders
# (Vault 2.0+ also needs a valid token, or enable_unauthenticated_access = ["generate-root"])
vault operator generate-root -init

Enable Audit Devices

# File device; values are HMAC-hashed by default (log_raw=false)
vault audit enable file file_path=/var/log/vault/audit.log

# Second device for redundancy and SIEM
vault audit enable syslog tag="vault" facility="AUTH"

vault audit list -detailed

# Find a known value in the logs by computing its HMAC
vault write sys/audit-hash/file input="s3cr3t"

Ship the file device to append-only storage or a SIEM. Alert on gaps and on any root-token use.

Commands & Recipes

KV Secrets

vault secrets enable -path=secret kv-v2
vault kv put secret/myapp/db username="admin" password="s3cr3t"
vault kv get secret/myapp/db
vault kv get -field=password secret/myapp/db
vault kv list secret/myapp/
vault kv rollback -version=1 secret/myapp/db
vault kv metadata put -max-versions=20 secret/myapp/db

Dynamic Database Secrets

vault secrets enable database

vault write database/config/mydb \
  plugin_name=postgresql-database-plugin \
  allowed_roles="readonly" \
  connection_url="postgresql://{{username}}:{{password}}@db:5432/mydb?sslmode=verify-full" \
  username="vault_admin" \
  password="initial-password"

# Rotate the admin password so only Vault knows it
vault write -f database/rotate-root/mydb

vault write database/roles/readonly \
  db_name=mydb \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# A new user every time
vault read database/creds/readonly

# Renew or revoke by lease
vault lease renew database/creds/readonly/<lease_id>
vault lease revoke -prefix database/creds/readonly

Kubernetes Auth

vault auth enable kubernetes

# When Vault runs in the same cluster, the local ServiceAccount token and CA are used
vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc"

vault write auth/kubernetes/role/myapp \
  bound_service_account_names=myapp \
  bound_service_account_namespaces=default \
  token_policies=myapp-policy \
  token_ttl=1h

AppRole with Response Wrapping

vault auth enable approle
vault write auth/approle/role/ci token_policies=ci token_ttl=20m secret_id_ttl=10m secret_id_num_uses=1
vault read -field=role_id auth/approle/role/ci/role-id

# Deliver the secret_id wrapped; the consumer unwraps it once
vault write -wrap-ttl=60s -f auth/approle/role/ci/secret-id
vault unwrap <wrapping_token>

Transit (Encryption as a Service)

vault secrets enable transit
vault write -f transit/keys/mykey

vault write transit/encrypt/mykey plaintext=$(echo -n "secret data" | base64)
vault write transit/decrypt/mykey ciphertext="vault:v1:..."

# Rotate, then upgrade stored ciphertext without seeing plaintext
vault write -f transit/keys/mykey/rotate
vault write transit/rewrap/mykey ciphertext="vault:v1:..."

Sync Secrets into Kubernetes with Vault Secrets Operator

# VSO 1.6.0 released 2026-09-23
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
  --namespace vault-secrets-operator-system --create-namespace

Then create VaultConnection, VaultAuth, and VaultStaticSecret or VaultDynamicSecret resources. Alternatives are the Agent injector (injector.enabled=true), the Vault CSI provider, and External Secrets Operator.

Back Up and Restore Integrated Storage

vault operator raft list-peers
vault operator raft autopilot state

vault operator raft snapshot save /backup/vault-$(date +%F).snap
vault operator raft snapshot restore /backup/vault-2026-09-25.snap

Snapshots are encrypted by the barrier, so restoring needs the same unseal or recovery mechanism. Vault Enterprise can schedule automated snapshots to object storage.

Migrate from Consul to Integrated Storage

# migrate.hcl
storage_source "consul" {
  address = "127.0.0.1:8500"
  path    = "vault/"
}
storage_destination "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-0"
}
cluster_addr = "https://vault-0.example.internal:8201"
# Vault must be stopped (offline migration)
vault operator migrate -config=migrate.hcl

Start the migrated node, unseal it, then join the other nodes. Plan disk space for Raft data and snapshots, and add disable_mlock to the new config.

Upgrade to Vault 2.x

  1. Read the 2.x important changes and release notes for every version you skip.
  2. Take a Raft snapshot.
  3. Check these 2.0 breaking changes:
    • sys/rekey, sys/generate-root, and sys/replication/dr/secondary/generate-operation-token now need a Vault token. Update automation, or set enable_unauthenticated_access.
    • Request paths must be canonical: /../, /./, and // are rejected.
    • The listener max_token_header_size defaults to 8 KB. Raise it if you send large OAuth/OIDC JWTs.
    • Official containers run as the vault user without IPC_LOCK (2.0.2+): set disable_mlock = true.
    • Templated policy paths that render wildcards or globs are rejected (2.0.1+).
  4. Upgrade standbys one at a time, wait until each is unsealed and healthy, then step down the active node and upgrade it last:
vault operator raft autopilot state   # all voters healthy
vault operator step-down              # on the active node, after standbys are upgraded
vault status

Vault Enterprise can instead use autopilot automated upgrades, which promote a new set of nodes once they outnumber the old version.

Monitor Vault

# Seal state per node (0 = sealed)
vault_core_unsealed

# Token creation rate by auth method
sum by (auth_method) (rate(vault_token_creation[5m]))

# Login latency (summary quantiles)
vault_core_handle_login_request{quantile="0.99"}

# Lease growth and audit failures
vault_expire_num_leases
rate(vault_audit_log_request_failure[5m])

# Storage latency through the barrier
vault_barrier_put{quantile="0.99"}

Enable Prometheus output with a telemetry stanza (prometheus_retention_time = "24h", disable_hostname = true) and scrape /v1/sys/metrics?format=prometheus with a token that can read sys/metrics. Metric meanings are in Reference.

Troubleshoot Common Issues

Issue Root cause Resolution
Vault sealed after restart Shamir seal, no auto-unseal Unseal each node or configure KMS auto-unseal
Server refuses to start: disable_mlock must be set Vault 1.20+ with Integrated Storage Set disable_mlock = true (and disable swap) or false explicitly
mlock errors in containers Image lacks IPC_LOCK (2.0.2+) disable_mlock = true
Token expired TTL too short for workload Use Vault Agent auto-auth, periodic tokens, or re-login
permission denied Policy path or capability missing vault token capabilities <path>; check KV v2 data/ and metadata/ prefixes
generate-root or rekey returns 403 after upgrade 2.0 requires a token on these endpoints Pass a token or set enable_unauthenticated_access
Raft leader election failures Lost quorum or network partition vault operator raft list-peers; restore connectivity; peers.json recovery as last resort
High memory and slow unseal Too many leases Shorter TTLs, batch tokens, lease count quotas, revoke stale leases
Requests blocked, audit errors in log All audit devices failing Fix disk or socket target; keep two devices enabled

Sources