HashiCorp Vault Installation and Secrets Management
HashiCorp Vault is a secrets management platform that provides secure storage, access control, and dynamic secret generation for tokens, passwords, certificates, and API keys. This guide covers installing Vault on Linux, initialization and unsealing, configuring secret engines, authentication methods, policies, and setting up high availability.
Prerequisites
- Ubuntu 22.04/Debian 12 or CentOS/Rocky 9
- Root or sudo access
- A TLS certificate and key (Let's Encrypt or self-signed)
- Port 8200 (HTTP API) open in firewall
- For HA: 3 nodes with port 8201 open between them
Install Vault
# Ubuntu/Debian
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 -y vault
# CentOS/Rocky
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf install -y vault
vault version
# Create required directories
sudo mkdir -p /etc/vault.d /opt/vault/data
sudo chown vault:vault /opt/vault/data
Configure Vault Storage and Listener
For a single-node deployment using the integrated Raft storage backend:
sudo tee /etc/vault.d/vault.hcl << 'EOF'
# API address
api_addr = "https://vault.example.com:8200"
cluster_addr = "https://vault.example.com:8201"
ui = true
# Disable memory lock warning (enable mlock on production)
disable_mlock = false
# Raft integrated storage
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-01"
}
# TLS listener
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault.d/tls/vault.crt"
tls_key_file = "/etc/vault.d/tls/vault.key"
tls_client_ca_file = "/etc/vault.d/tls/ca.crt"
}
# Telemetry
telemetry {
prometheus_retention_time = "30s"
disable_hostname = true
}
EOF
sudo chmod 640 /etc/vault.d/vault.hcl
Install TLS certificates:
sudo mkdir -p /etc/vault.d/tls
# Copy your certificates (or generate self-signed):
openssl req -x509 -newkey rsa:4096 -keyout /tmp/vault.key -out /tmp/vault.crt \
-days 365 -nodes -subj "/CN=vault.example.com" \
-addext "subjectAltName=DNS:vault.example.com,IP:127.0.0.1"
sudo mv /tmp/vault.key /etc/vault.d/tls/vault.key
sudo mv /tmp/vault.crt /etc/vault.d/tls/vault.crt
sudo cp /etc/vault.d/tls/vault.crt /etc/vault.d/tls/ca.crt
sudo chown -R vault:vault /etc/vault.d/tls/
sudo chmod 640 /etc/vault.d/tls/vault.key
sudo systemctl enable --now vault
Initialize and Unseal Vault
Vault must be initialized once per cluster and unsealed after every restart:
# Set the Vault address
export VAULT_ADDR=https://vault.example.com:8200
export VAULT_CACERT=/etc/vault.d/tls/ca.crt
# Initialize Vault - generates unseal keys and root token
# Use 5 key shares, require 3 to unseal (Shamir's Secret Sharing)
vault operator init -key-shares=5 -key-threshold=3
# SAVE THE OUTPUT SECURELY - you cannot recover these keys later
# Example output:
# Unseal Key 1: abc123...
# Unseal Key 2: def456...
# ...
# Initial Root Token: hvs.XXXXXXXXXXXXXX
Unseal Vault (required after every restart):
# Provide 3 of the 5 unseal keys
vault operator unseal <Unseal Key 1>
vault operator unseal <Unseal Key 2>
vault operator unseal <Unseal Key 3>
# Verify Vault is unsealed
vault status
# Login with root token (for initial setup only)
vault login hvs.XXXXXXXXXXXXXX
For production, use Auto Unseal with AWS KMS or GCP CKMS to avoid manual unsealing:
# Add to vault.hcl for AWS KMS auto-unseal
seal "awskms" {
region = "us-east-1"
kms_key_id = "arn:aws:kms:us-east-1:123456789:key/your-key-id"
}
Secret Engines
Vault supports multiple secret engines for different use cases:
# Enable the KV v2 secrets engine (key-value store with versioning)
vault secrets enable -path=secret kv-v2
# Write and read secrets
vault kv put secret/myapp/database \
username="dbuser" \
password="s3cr3tpassword"
vault kv get secret/myapp/database
vault kv get -field=password secret/myapp/database
# List secrets at a path
vault kv list secret/myapp/
# Get previous version
vault kv get -version=1 secret/myapp/database
# Delete (soft delete - recoverable)
vault kv delete secret/myapp/database
# Permanently destroy a version
vault kv destroy -versions=1 secret/myapp/database
Enable the database secret engine for dynamic credentials:
vault secrets enable database
# Configure PostgreSQL connection
vault write database/config/mypostgres \
plugin_name=postgresql-database-plugin \
allowed_roles="readonly,readwrite" \
connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/mydb?sslmode=require" \
username="vault-root" \
password="VaultRootPassword"
# Create a role that generates temporary read-only credentials
vault write database/roles/readonly \
db_name=mypostgres \
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"
# Generate temporary credentials (each call returns unique user/pass)
vault read database/creds/readonly
Enable the PKI secret engine for certificate issuance:
vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki
# Generate root CA
vault write pki/root/generate/internal \
common_name="example.com" \
ttl=87600h
# Configure issuing endpoints
vault write pki/config/urls \
issuing_certificates="https://vault.example.com:8200/v1/pki/ca" \
crl_distribution_points="https://vault.example.com:8200/v1/pki/crl"
# Create a role for issuing certs
vault write pki/roles/web-server \
allowed_domains="example.com" \
allow_subdomains=true \
max_ttl=8760h
# Issue a certificate
vault write pki/issue/web-server \
common_name="web.example.com" \
ttl=720h
Authentication Methods
Configure how applications and users authenticate to Vault:
# Enable AppRole auth (for applications/CI-CD)
vault auth enable approle
vault write auth/approle/role/myapp \
secret_id_ttl=24h \
token_ttl=1h \
token_max_ttl=4h \
policies="myapp-policy"
# Get Role ID (embed in app config)
vault read auth/approle/role/myapp/role-id
# Generate a Secret ID (treat as a password)
vault write -f auth/approle/role/myapp/secret-id
# Login using AppRole
vault write auth/approle/login \
role_id=<ROLE_ID> \
secret_id=<SECRET_ID>
Enable GitHub auth for developer access:
vault auth enable github
vault write auth/github/config organization="myorg"
# Allow a team to use the 'developer' policy
vault write auth/github/map/teams/sysadmins value=admin-policy
# Developers login with a GitHub token
vault login -method=github token=ghp_xxxxxxxxxxxx
Policies and Access Control
Vault policies are written in HCL and control what secrets a token can access:
# Create a policy file
cat > /tmp/myapp-policy.hcl << 'EOF'
# Read access to app secrets
path "secret/data/myapp/*" {
capabilities = ["read", "list"]
}
# Allow the app to generate database credentials
path "database/creds/readonly" {
capabilities = ["read"]
}
# Allow renewal of its own token
path "auth/token/renew-self" {
capabilities = ["update"]
}
# Deny access to other paths
path "secret/data/other-app/*" {
capabilities = ["deny"]
}
EOF
# Write the policy
vault policy write myapp-policy /tmp/myapp-policy.hcl
# List policies
vault policy list
# Read a policy
vault policy read myapp-policy
High Availability with Raft
For HA, configure 3 nodes with Raft storage:
# On vault-02 and vault-03, configure with join:
sudo tee /etc/vault.d/vault.hcl << 'EOF'
api_addr = "https://vault-02.example.com:8200"
cluster_addr = "https://vault-02.example.com:8201"
ui = true
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-02"
retry_join {
leader_api_addr = "https://vault-01.example.com:8200"
leader_ca_cert_file = "/etc/vault.d/tls/ca.crt"
leader_client_cert_file = "/etc/vault.d/tls/vault.crt"
leader_client_key_file = "/etc/vault.d/tls/vault.key"
}
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault.d/tls/vault.crt"
tls_key_file = "/etc/vault.d/tls/vault.key"
}
EOF
# Start vault-02 and vault-03, then unseal them
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>
# Verify HA status
vault operator raft list-peers
Troubleshooting
Vault sealed after restart:
vault status
# Vault is sealed: true -> needs unsealing
vault operator unseal <key> # 3 times
Permission denied errors:
# Check the token's policies
vault token lookup
# Check effective capabilities on a path
vault token capabilities secret/data/myapp/database
Storage backend errors:
journalctl -u vault -n 100 --no-pager
# Check raft peer status
vault operator raft list-peers
Secret engine not responding:
# List enabled secrets engines
vault secrets list
# Check the engine's configuration
vault read database/config/mypostgres
Conclusion
HashiCorp Vault centralizes secrets management and eliminates hardcoded credentials by providing short-lived dynamic secrets for databases, certificates, and cloud providers. The Raft storage backend simplifies deployment by removing the need for an external database, while auto-unseal with a cloud KMS removes the operational burden of manual unsealing after restarts. Start by replacing static database passwords with Vault's dynamic database credentials engine — it delivers the highest immediate security improvement with minimal application changes.


