Licensing in 2026

HashiCorp moved Vault to the BSL (Business Source License) in 2023; a community fork called OpenBao (now part of the Linux Foundation) maintains the API-compatible MPL-2.0 codebase. The commands below work identically against either. For ideological or commercial-redistribution reasons, OpenBao is the safer default in 2026; the Vault binary is still fine for self-hosted internal use under the BSL.

Install (OpenBao or Vault, same CLI)

# OpenBao on Debian / Ubuntu
curl -fsSL https://openbao.org/install.sh | bash
# (uses an apt repo with signed packages)

bao --version

# Or HashiCorp's Vault
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [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

Configuration

The default config under /etc/vault.d/vault.hcl:

ui = true
disable_mlock = false

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "node1"
}

listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/etc/vault.d/cert.pem"
  tls_key_file  = "/etc/vault.d/key.pem"
}

api_addr     = "https://vault.lab.example.com:8200"
cluster_addr = "https://vault-node1.lab.example.com:8201"

raft is the recommended storage backend — it's embedded, supports HA without an external etcd/consul, and writes to the local disk. For a single-node lab, one node is fine.

Generate or provision the TLS cert (Step CA in that tutorial works perfectly here), then:

sudo systemctl enable --now vault     # or openbao
sudo systemctl status vault

Initialize and unseal

Vault starts sealed — the storage is encrypted with a master key that's split using Shamir's Secret Sharing. On first init, Vault generates 5 unseal-key shares and 1 root token, and the operator has to provide 3 of those 5 shares to unseal the master key into memory.

export VAULT_ADDR=https://vault.lab.example.com:8200
vault operator init

Output:

Unseal Key 1: yC1...
Unseal Key 2: vbk...
Unseal Key 3: 9pq...
Unseal Key 4: ...
Unseal Key 5: ...

Initial Root Token: hvs.AAAA...

Copy these out-of-band immediately — they appear once and are unrecoverable. Standard practice: each key share to a different trusted operator (or a different password manager / safe / printed paper backup). The root token should be revoked once non-root admin tokens are set up.

Unseal (run on each restart):

vault operator unseal <key-1>
vault operator unseal <key-2>
vault operator unseal <key-3>     # threshold reached, Vault unseals

vault status
Auto-unseal in production

Manual unseal-on-restart is friction. Production Vault uses auto-unseal with a KMS (cloud HSM, AWS KMS, GCP KMS, an on-prem PKCS#11 token). For a homelab, leaving Vault sealed across reboots is acceptable; for a server that has to come up unattended, auto-unseal against a TPM-backed key is the right path.

Login

vault login <root-token>

The login token is cached at ~/.vault-token. Subsequent vault commands use it automatically.

The KV secrets engine

The default "secret store" engine is KV (key-value). Mount it at a path:

vault secrets enable -path=secret kv-v2

Write and read secrets:

vault kv put secret/grafana admin_password=hunter2 smtp_password=...
vault kv get secret/grafana
vault kv get -field=admin_password secret/grafana

# Versioned: KV-v2 keeps history of every overwrite
vault kv get -version=3 secret/grafana
vault kv metadata get secret/grafana

Policies

Policies are HCL documents describing what paths a token may touch. grafana-policy.hcl:

path "secret/data/grafana" {
  capabilities = ["read"]
}
path "secret/metadata/grafana" {
  capabilities = ["read"]
}
vault policy write grafana ./grafana-policy.hcl

Auth methods: how applications get tokens

Vault supports many auth methods. Three useful ones:

  • AppRole — role-id + secret-id pair, designed for machines. The role-id is durable; the secret-id is short-lived and can be rotated.
  • Token — the simplest case; create a token with policies attached, hand it to the app via env var.
  • OIDC / LDAP / GitHub — for humans logging in via SSO.

AppRole example for an app:

vault auth enable approle
vault write auth/approle/role/grafana \
    token_policies=grafana \
    token_ttl=1h \
    token_max_ttl=4h

vault read  auth/approle/role/grafana/role-id     # durable
vault write -f auth/approle/role/grafana/secret-id # rotatable

The application logs in with both pieces:

vault write auth/approle/login \
    role_id=<role-id> \
    secret_id=<secret-id>

The response contains a token with the grafana policy attached, valid for 1 hour. The app then reads its secret via that token.

Dynamic database credentials

The killer feature: ask Vault for a database username/password pair, get one that exists for the next 30 minutes, then ceases to exist:

vault secrets enable database

vault write database/config/prod-postgres \
    plugin_name=postgresql-database-plugin \
    connection_url="postgresql://{{username}}:{{password}}@db.prod:5432/postgres?sslmode=require" \
    allowed_roles="readonly,readwrite" \
    username=vault-admin \
    password=<rotated>

vault write database/roles/readonly \
    db_name=prod-postgres \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT pg_read_all_data TO \"{{name}}\";" \
    default_ttl=30m \
    max_ttl=1h

# Now any token with policy "database/creds/readonly" can:
vault read database/creds/readonly
# returns a freshly-created Postgres user/pass valid for 30 min

When the lease expires, Vault revokes the user from Postgres. The blast radius of a leaked credential is bounded by the lease, not by "until someone notices."

Backups

For the raft storage backend, vault operator raft snapshot save backup.snap writes a consistent snapshot of the entire state. Combined with the unseal-key shares (stored elsewhere), this is enough to rebuild Vault on a different machine.

For file storage backends, copy /opt/vault/data while Vault is stopped or fenced.

What Vault is not for

  • Storing data the application reads frequently — Vault is "fetch once, cache, refresh before TTL." Hitting Vault on every request kills throughput.
  • Per-user secrets at consumer scale — Vault is operator-facing infrastructure, not a Bitwarden replacement. Use Vaultwarden (see that tutorial) for human-scale password management.
  • Anywhere you're not prepared to operate the unseal ceremony — without it, Vault is a fancy KV store; with it, the data is meaningfully protected from disk theft.