Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Vault Ubuntu
GUIDEInstall Guides

How to Install HashiCorp Vault on Ubuntu 24.04 VPS: Self-Hosted Secrets Management

28 min read

How to Install HashiCorp Vault on Ubuntu 24.04 VPS: Self-Hosted Secrets Management

Secrets sprawl is a silent liability. Database passwords end up in .env files, API keys get pasted into Slack, TLS private keys sit in git repos, and nobody can answer "who accessed this secret last week?". HashiCorp Vault fixes that by giving you a single, audited, policy-controlled source of truth for every secret, certificate, and encryption key in your stack. This guide walks you through installing Vault on an Ubuntu 24.04 VPS from apt install to a production-grade deployment with integrated Raft storage, TLS, KV v2, PKI, Transit, policies, audit logs, and encrypted snapshot backups.

Want a proven VPS for Vault? The CloudCore Professional plan gives you 6 vCPU, 12 GB RAM, and 100 GB NVMe — enough headroom for Vault plus Consul or Nomad on the same server.

Table of Contents

  • What is HashiCorp Vault?
  • Why Self-Host Vault?
  • Licensing: BSL and OpenBao
  • Prerequisites
  • Step 1: Update System Packages
  • Step 2: Add the HashiCorp apt Repository
  • Step 3: Install Vault
  • Step 4: Obtain a TLS Certificate from Let's Encrypt
  • Step 5: Configure Vault with Integrated Raft Storage and TLS
  • Step 6: Start Vault with systemd
  • Step 7: Initialize and Unseal Vault
  • Step 8: Enable Auth Methods (userpass, AppRole, OIDC)
  • Step 9: Enable the KV v2 Secrets Engine
  • Step 10: Write Policies in HCL
  • Step 11: Configure PKI for Internal Certificates
  • Step 12: Enable the Transit Engine
  • Step 13: Enable Audit Logging
  • Step 14: Auto-Unseal via Cloud KMS
  • Step 15: Snapshot Backups
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is HashiCorp Vault?

    HashiCorp Vault is a secrets management platform that centralizes how an organization stores, distributes, rotates, and audits sensitive data. At its core, Vault is an encrypted key-value store, but the value comes from the abstractions layered on top: pluggable authentication backends, policy-based access control, dynamic secrets (credentials generated on demand and revoked automatically), an encryption-as-a-service engine, a full certificate authority, tokens with fine-grained TTLs, and tamper-evident audit logs.

    Rather than hand-crafting access control for every database and service, you mount a secrets engine in Vault, write an HCL policy, attach it to an identity (a human, a machine, or a CI job), and let Vault broker every request. Applications never see long-lived credentials — they authenticate to Vault using something they already have (a Kubernetes service account, an AWS IAM role, a JWT from your identity provider) and receive a short-lived token scoped to exactly what they need.

    Vault is the centerpiece of HashiCorp's production stack. It pairs naturally with Consul for service discovery and Nomad for orchestration, and sits comfortably behind an Nginx reverse proxy when you need extra ingress controls.

    Why Self-Host Vault?

    Managed secret stores (AWS Secrets Manager, Google Secret Manager, Azure Key Vault, 1Password Secrets Automation) are convenient, but running Vault on your own VPS has concrete benefits:

    • Vendor neutrality — A self-hosted Vault works identically whether your workloads run on AWS, GCP, Azure, Hetzner, OVH, Contabo, or on-prem. No rewriting integrations when you change providers.
    • Predictable pricing — Managed secret services charge per-secret, per-access, or both. Self-hosted Vault is flat-rate: one VPS, unlimited secrets, unlimited reads.
    • Advanced engines without enterprise fees — The open-source Vault (and OpenBao) include PKI, Transit encryption, KV v2, Database dynamic secrets, SSH CA, and Kubernetes auth at no extra cost.
    • Full audit trail under your control — Audit logs live on your disk. Ship them to your own SIEM without per-event fees.
    • Data sovereignty — For GDPR, HIPAA, SOC 2, or PCI DSS, keeping root-of-trust keys on infrastructure you physically control simplifies compliance scoping.
    • Offline and air-gapped deployments — Once the binary is installed, Vault needs no internet to serve secrets.
    • Low latency — A local Vault node responds in single-digit milliseconds, versus 40-150ms for cross-region managed APIs.
    For teams with more than a few dozen services or more than a handful of engineers, self-hosted Vault pays for itself in the first month compared to per-secret managed pricing.

    Licensing: BSL and OpenBao

    A quick note before you install. In August 2023, HashiCorp changed the license for Vault (and most of their products) from the permissive Mozilla Public License 2.0 to the Business Source License 1.1 (BSL). In practice this means:

    • You can still freely use, modify, and self-host Vault for your own internal use — including commercial production use inside your company.
    • You cannot offer Vault as a competing managed/hosted service without a commercial agreement.
    • Four years after each release, the BSL code converts to MPL 2.0.
    If you prefer a fully open-source fork governed by the Linux Foundation, OpenBao is the community-led continuation of Vault 1.14 under the MPL 2.0. OpenBao is API-compatible with Vault — the vault CLI, the HTTP API, policies, and configuration files work almost unchanged. Most of the steps in this guide apply to OpenBao verbatim; replace the apt package name with bao and the binary with bao.

    For this tutorial we install the official HashiCorp Vault from the HashiCorp apt repository.

    Prerequisites

    Before you begin, you will need:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access
    • SSH access to the server
    • A public DNS record (e.g. vault.example.com) pointing to your VPS IP — required for the Let's Encrypt TLS certificate
    • Ports 80, 443, 8200, and 8201 reachable as appropriate (see Step 4)
    • At least 2 GB RAM and 2 vCPU (Vault itself is small; Raft storage is I/O heavy)
    • At least 20 GB of SSD storage — NVMe strongly recommended for Raft write latency
    Recommended Plan: CloudCore Professional
    >
    For a production Vault node that will also serve as a Raft participant (and leave room for Consul or Nomad on the same box), we recommend the CloudCore Professional plan:
    >
    - 6 vCPU cores
    - 12 GB RAM
    - 100 GB NVMe SSD
    - Unmetered bandwidth

    Connect to your server:

    bash
    ssh root@your-server-ip

    Step 1: Update System Packages

    bash
    sudo apt update && sudo apt upgrade -y

    Install prerequisite packages used later in this guide:

    bash
    sudo apt install -y gnupg software-properties-common curl jq unzip ufw

    Set a descriptive hostname for the Vault node — this will show up in audit logs and cluster output:

    bash
    sudo hostnamectl set-hostname vault-01

    Step 2: Add the HashiCorp apt Repository

    HashiCorp publishes signed apt packages for Ubuntu. Install the GPG key:

    bash
    wget -O- https://apt.releases.hashicorp.com/gpg | \
      sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

    Verify the key fingerprint:

    bash
    gpg --no-default-keyring \
      --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \
      --fingerprint

    Expected fingerprint: 798A EC65 4E5C 1542 8C8E 42EE AA16 FCBC A621 E701.

    Add the repository:

    bash
    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

    Refresh the package index:

    bash
    sudo apt update

    Step 3: Install Vault

    bash
    sudo apt install -y vault

    Verify the installation:

    bash
    vault --version

    Expected output:

    text
    Vault v1.17.6 (...)

    The package creates:

    • /usr/bin/vault — the binary
    • A vault system user and group
    • /etc/vault.d/vault.hcl — default config file (we will replace it)
    • /opt/vault/ — data and TLS directories
    • /usr/lib/systemd/system/vault.service — systemd unit
    • Linux capability IPC_LOCK allowing Vault to mlock memory so secrets never hit swap
    Stop the service for now (we need to replace the default config before starting it):

    bash
    sudo systemctl stop vault 2>/dev/null || true
    sudo systemctl disable vault 2>/dev/null || true

    Step 4: Obtain a TLS Certificate from Let's Encrypt

    Vault's TLS listener needs a certificate. You have three good options: a public Let's Encrypt cert (easiest when the node is internet-reachable), a cert from Vault's own PKI engine (chicken-and-egg on day one), or a cert from an internal CA. We'll use Let's Encrypt with standalone mode so you don't need Nginx running yet.

    Open port 80 for the HTTP-01 challenge:

    bash
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw allow 22/tcp
    sudo ufw allow 8200/tcp comment "Vault API"
    sudo ufw allow 8201/tcp comment "Vault Raft"
    sudo ufw --force enable

    Install Certbot and issue the cert:

    bash
    sudo apt install -y certbot
    sudo certbot certonly --standalone \
      -d vault.example.com \
      --non-interactive --agree-tos -m [email protected]

    Certbot drops the cert at /etc/letsencrypt/live/vault.example.com/. Vault runs as the vault user and needs to read these files. Instead of opening up /etc/letsencrypt, copy the cert into a directory Vault already owns and set up a renewal hook.

    bash
    sudo mkdir -p /opt/vault/tls
    sudo cp /etc/letsencrypt/live/vault.example.com/fullchain.pem /opt/vault/tls/vault.crt
    sudo cp /etc/letsencrypt/live/vault.example.com/privkey.pem /opt/vault/tls/vault.key
    sudo chown -R vault:vault /opt/vault/tls
    sudo chmod 640 /opt/vault/tls/vault.key

    Create a renewal hook so the cert stays fresh:

    bash
    sudo tee /etc/letsencrypt/renewal-hooks/deploy/vault.sh > /dev/null <<'EOF'
    #!/bin/bash
    set -e
    cp /etc/letsencrypt/live/vault.example.com/fullchain.pem /opt/vault/tls/vault.crt
    cp /etc/letsencrypt/live/vault.example.com/privkey.pem   /opt/vault/tls/vault.key
    chown vault:vault /opt/vault/tls/vault.crt /opt/vault/tls/vault.key
    chmod 640 /opt/vault/tls/vault.key
    systemctl reload vault || true
    EOF
    sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/vault.sh

    Step 5: Configure Vault with Integrated Raft Storage and TLS

    Integrated Raft is Vault's built-in, HashiCorp-recommended storage backend. It stores everything on the local disk, supports clustering out of the box, and removes the dependency on a separate Consul cluster for storage. For a single-node install (or the first node of a future cluster), Raft is the right choice.

    Replace the default config:

    bash
    sudo tee /etc/vault.d/vault.hcl > /dev/null <<'EOF'
    ui = true
    cluster_name = "vault-prod"
    disable_mlock = false

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

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

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

    Uncomment after Step 14 to enable auto-unseal with AWS KMS

    seal "awskms" {

    region = "eu-central-1"

    kms_key_id = "alias/vault-unseal"

    }

    EOF

    Set tight permissions on the data directory and config:

    bash
    sudo mkdir -p /opt/vault/data
    sudo chown -R vault:vault /opt/vault
    sudo chmod 700 /opt/vault/data
    sudo chown root:vault /etc/vault.d/vault.hcl
    sudo chmod 640 /etc/vault.d/vault.hcl

    Step 6: Start Vault with systemd

    The apt package ships a hardened systemd unit. Inspect it:

    bash
    systemctl cat vault

    Key hardening flags already in place: ProtectSystem=full, PrivateTmp=yes, NoNewPrivileges=yes, Capabilities=CAP_IPC_LOCK+ep, AmbientCapabilities=CAP_IPC_LOCK, LimitMEMLOCK=infinity.

    Enable and start Vault:

    bash
    sudo systemctl enable vault
    sudo systemctl start vault
    sudo systemctl status vault

    Expected output:

    text
    ● vault.service - "HashiCorp Vault - A tool for managing secrets"
         Loaded: loaded (/usr/lib/systemd/system/vault.service; enabled; preset: enabled)
         Active: active (running) since ...
       Main PID: 4321 (vault)
          Tasks: 9
         Memory: 110.0M

    Point the CLI at Vault over TLS:

    bash
    export VAULT_ADDR="https://vault.example.com:8200"
    echo "export VAULT_ADDR=\"https://vault.example.com:8200\"" | sudo tee /etc/profile.d/vault.sh

    Check status — at this point Vault is running but uninitialized and sealed:

    bash
    vault status

    Expected output (exit code 2 is normal for a sealed vault):

    text
    Key                Value
    ---                -----
    Seal Type          shamir
    Initialized        false
    Sealed             true
    Total Shares       0
    Threshold          0
    Version            1.17.6
    Storage Type       raft
    HA Enabled         true

    Step 7: Initialize and Unseal Vault

    Initialization generates the master key, splits it into 5 unseal key shares using Shamir's Secret Sharing, and requires any 3 of 5 to unseal. It also prints the initial root token — the only token with unlimited permissions — which you will use once to bootstrap the rest of Vault, then revoke.

    bash
    vault operator init -key-shares=5 -key-threshold=3

    Expected output:

    text
    Unseal Key 1: abcd1234...
    Unseal Key 2: efgh5678...
    Unseal Key 3: ijkl9012...
    Unseal Key 4: mnop3456...
    Unseal Key 5: qrst7890...

    Initial Root Token: hvs.XXXXXXXXXXXXXXXXXX

    Vault initialized with 5 key shares and a key threshold of 3.

    CRITICAL: Save each unseal key in a different place (a different team member, a different password manager, an offline hardware token). If you lose 3 of 5 keys, your data is unrecoverable. Never store unseal keys in the same location as the encrypted Vault data, and never commit them to git.

    Unseal Vault by providing 3 of the 5 keys:

    bash
    vault operator unseal
    

    paste Unseal Key 1

    vault operator unseal

    paste Unseal Key 2

    vault operator unseal

    paste Unseal Key 3

    After the third key, Vault transitions to unsealed and vault status shows Sealed: false. Log in with the root token:

    bash
    vault login hvs.XXXXXXXXXXXXXXXXXX

    Step 8: Enable Auth Methods (userpass, AppRole, OIDC)

    The root token is too powerful for everyday use. Before going further, enable proper auth methods so humans and machines log in with their own identities.

    userpass — Humans with Username/Password

    bash
    vault auth enable userpass

    vault write auth/userpass/users/alice \ password='change-me-first-login' \ token_policies="admin"

    Alice can now log in:

    bash
    vault login -method=userpass username=alice

    In production, put MFA in front with Vault's built-in TOTP or point userpass at your SSO via OIDC instead.

    AppRole — Machines, CI Jobs, and Applications

    AppRole is the standard way for a non-human client (a Jenkins job, a web app, a deployment script) to authenticate. Each app gets a RoleID (semi-public) and a SecretID (secret), exchanges them for a short-lived token, and uses that token to fetch secrets.

    bash
    vault auth enable approle

    vault write auth/approle/role/web-app \ token_policies="web-app-policy" \ token_ttl=1h \ token_max_ttl=4h \ secret_id_ttl=24h \ secret_id_num_uses=1

    Retrieve the RoleID (bake this into your app config):

    bash
    vault read auth/approle/role/web-app/role-id

    Generate a SecretID (inject this at deploy time via a trusted delivery mechanism — never commit it):

    bash
    vault write -f auth/approle/role/web-app/secret-id

    Exchange them for a token:

    bash
    vault write auth/approle/login \
      role_id="ROLE_ID_HERE" \
      secret_id="SECRET_ID_HERE"

    OIDC — Single Sign-On with Google, Okta, Azure AD, or Keycloak

    For teams, federate Vault login through your existing identity provider.

    bash
    vault auth enable oidc

    vault write auth/oidc/config \ oidc_discovery_url="https://accounts.google.com" \ oidc_client_id="YOUR_CLIENT_ID.apps.googleusercontent.com" \ oidc_client_secret="YOUR_CLIENT_SECRET" \ default_role="engineer"

    vault write auth/oidc/role/engineer \ bound_audiences="YOUR_CLIENT_ID.apps.googleusercontent.com" \ allowed_redirect_uris="https://vault.example.com:8200/ui/vault/auth/oidc/oidc/callback" \ allowed_redirect_uris="http://localhost:8250/oidc/callback" \ user_claim="email" \ token_policies="engineer"

    Engineers now click "Sign in with OIDC" in the Vault UI and land in an authenticated session scoped by the engineer policy.

    Step 9: Enable the KV v2 Secrets Engine

    Key-Value v2 is the general-purpose secrets store, with versioning, metadata, and soft delete.

    bash
    vault secrets enable -path=secret -version=2 kv

    Write and read a secret:

    bash
    vault kv put secret/apps/billing/db \
      username="billing_svc" \
      password="s3cretP@ss" \
      host="db.internal" \
      port="5432"

    vault kv get secret/apps/billing/db

    Read a specific version:

    bash
    vault kv get -version=1 secret/apps/billing/db

    Soft-delete and undelete:

    bash
    vault kv delete secret/apps/billing/db
    vault kv undelete -versions=1 secret/apps/billing/db

    Permanently destroy a version (audit-logged, irreversible):

    bash
    vault kv destroy -versions=1 secret/apps/billing/db

    Fetch a secret over the HTTP API with a token:

    bash
    curl -s \
      -H "X-Vault-Token: $VAULT_TOKEN" \
      https://vault.example.com:8200/v1/secret/data/apps/billing/db | jq .

    Step 10: Write Policies in HCL

    Policies define what a token can do. They are written in HashiCorp Configuration Language (HCL) and attached to tokens at creation.

    Create a least-privilege policy for the billing app:

    bash
    sudo tee /tmp/web-app-policy.hcl > /dev/null <<'EOF'
    

    Read and list billing secrets

    path "secret/data/apps/billing/*" { capabilities = ["read", "list"] }

    path "secret/metadata/apps/billing/*" { capabilities = ["list", "read"] }

    Use the transit engine to encrypt/decrypt data

    path "transit/encrypt/billing" { capabilities = ["update"] } path "transit/decrypt/billing" { capabilities = ["update"] }

    Renew own token

    path "auth/token/renew-self" { capabilities = ["update"] } EOF

    vault policy write web-app-policy /tmp/web-app-policy.hcl rm /tmp/web-app-policy.hcl

    A broader admin policy for human operators:

    bash
    sudo tee /tmp/admin.hcl > /dev/null <<'EOF'
    path "sys/mounts/*"    { capabilities = ["create", "read", "update", "delete", "sudo"] }
    path "sys/mounts"      { capabilities = ["read"] }
    path "sys/policies/acl/*" { capabilities = ["create", "read", "update", "delete", "list"] }
    path "sys/policies/acl" { capabilities = ["list"] }
    path "secret/*"        { capabilities = ["create", "read", "update", "delete", "list"] }
    path "auth/*"          { capabilities = ["create", "read", "update", "delete", "list", "sudo"] }
    path "sys/auth"        { capabilities = ["read"] }
    path "sys/health"      { capabilities = ["read", "sudo"] }
    EOF

    vault policy write admin /tmp/admin.hcl rm /tmp/admin.hcl

    List policies:

    bash
    vault policy list

    Assign the admin policy to Alice (already done in Step 8) and revoke the root token now that you have alternatives:

    bash
    vault token revoke hvs.XXXXXXXXXXXXXXXXXX

    You can always regenerate a root token later with unseal key quorum:

    bash
    vault operator generate-root -init

    Step 11: Configure PKI for Internal Certificates

    Vault's PKI engine turns your Vault into a full-fledged internal CA that issues short-lived, automatically-rotated certificates for internal services.

    Enable and tune the root CA mount:

    bash
    vault secrets enable -path=pki pki
    vault secrets tune -max-lease-ttl=87600h pki

    Generate the root certificate (10-year lifespan for the root, short-lived for leaves):

    bash
    vault write -field=certificate pki/root/generate/internal \
      common_name="example.com Internal Root CA" \
      issuer_name="example-root-2026" \
      ttl=87600h > /opt/vault/tls/ca.crt

    Configure CRL and issuing URLs:

    bash
    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 an intermediate CA (best practice — never issue directly from the root):

    bash
    vault secrets enable -path=pki_int pki
    vault secrets tune -max-lease-ttl=43800h pki_int

    vault write -format=json pki_int/intermediate/generate/internal \ common_name="example.com Intermediate CA" \ issuer_name="example-intermediate-2026" \ | jq -r '.data.csr' > /tmp/pki_intermediate.csr

    vault write -format=json pki/root/sign-intermediate \ csr=@/tmp/pki_intermediate.csr \ format=pem_bundle \ ttl="43800h" \ | jq -r '.data.certificate' > /tmp/intermediate.cert.pem

    vault write pki_int/intermediate/set-signed \ certificate=@/tmp/intermediate.cert.pem

    Create a role that issues certs for *.internal.example.com:

    bash
    vault write pki_int/roles/internal-dot-example-dot-com \
      issuer_ref="$(vault read -field=default pki_int/config/issuers)" \
      allowed_domains="internal.example.com" \
      allow_subdomains=true \
      max_ttl="720h"

    Issue a certificate for a service:

    bash
    vault write pki_int/issue/internal-dot-example-dot-com \
      common_name="api.internal.example.com" \
      ttl="168h"

    The response contains certificate, private_key, ca_chain, and serial_number. Integrate this with your deploy tooling or use consul-template / vault-agent to write certs to disk and reload services.

    Step 12: Enable the Transit Engine

    The Transit engine is encryption-as-a-service. Your apps send plaintext to Vault, get ciphertext back, and store the ciphertext in their own databases. Keys never leave Vault, and rotation is centralized.

    bash
    vault secrets enable transit

    vault write -f transit/keys/billing

    Encrypt a payload (plaintext must be base64-encoded):

    bash
    vault write transit/encrypt/billing \
      plaintext=$(echo -n "4242 4242 4242 4242" | base64)

    Expected output:

    text
    Key               Value
    ---               -----
    ciphertext        vault:v1:...
    key_version       1

    Decrypt:

    bash
    vault write transit/decrypt/billing \
      ciphertext="vault:v1:..." \
      | grep plaintext \
      | awk '{print $2}' | base64 -d

    Rotate the key (all new encryptions use v2; existing ciphertext stays decryptable):

    bash
    vault write -f transit/keys/billing/rotate

    Rewrap existing ciphertext to the latest key version without revealing plaintext:

    bash
    vault write transit/rewrap/billing ciphertext="vault:v1:..."

    This pattern eliminates the "where do we keep the encryption key" question entirely — the key is in Vault, and your policy decides who can use it.

    Step 13: Enable Audit Logging

    Audit devices produce a tamper-evident log of every request and response Vault processes. In production, enable at least two so a single disk failure doesn't stop auditing (Vault refuses to serve requests if no audit device can write).

    Enable a file audit device:

    bash
    sudo mkdir -p /var/log/vault
    sudo chown vault:vault /var/log/vault

    vault audit enable file file_path=/var/log/vault/audit.log

    Enable a second (syslog) device for redundancy:

    bash
    vault audit enable -path=syslog syslog tag=vault facility=AUTH

    Rotate the file log with logrotate:

    bash
    sudo tee /etc/logrotate.d/vault > /dev/null <<'EOF'
    /var/log/vault/audit.log {
        daily
        rotate 30
        compress
        delaycompress
        missingok
        notifempty
        create 0640 vault vault
        postrotate
            kill -HUP $(pidof vault) 2>/dev/null || true
        endscript
    }
    EOF

    Inspect audit entries — request/response hashes are HMAC-salted, so the actual secret values are not logged in plaintext:

    bash
    sudo tail -n 20 /var/log/vault/audit.log | jq .

    Step 14: Auto-Unseal via Cloud KMS

    The default Shamir unseal means every Vault restart requires 3 humans to paste keys. For production you want auto-unseal: Vault stores its master key encrypted by an external KMS, and at startup it calls the KMS to decrypt the key and unseal itself.

    Supported KMS providers: AWS KMS, GCP Cloud KMS, Azure Key Vault, AliCloud KMS, OCI KMS, HSM (PKCS#11 — Vault Enterprise), and Transit seal (another Vault cluster).

    AWS KMS Example

    Create a KMS key in AWS (alias/vault-unseal) and an IAM role/user with kms:Encrypt, kms:Decrypt, and kms:DescribeKey permissions. Attach the role to your EC2 instance, or set AWS credentials as environment variables in /etc/vault.d/vault.env.

    Uncomment the seal stanza in /etc/vault.d/vault.hcl:

    hcl
    seal "awskms" {
      region     = "eu-central-1"
      kms_key_id = "alias/vault-unseal"
    }

    Because you already initialized with Shamir, you need to migrate the seal:

    bash
    sudo systemctl restart vault
    vault operator unseal -migrate
    

    provide 3 of 5 Shamir keys when prompted

    After migration, the unseal keys returned by vault operator init become recovery keys — still 3 of 5, still needed for operator-level recovery actions like regenerating the root token, but no longer required to unseal at startup.

    For non-cloud VPS deployments, a self-hosted Transit seal works well: run a second tiny Vault cluster whose only job is to hold the unsealing key. This is the "auto-unseal with Vault" pattern and is the recommended approach when you don't want to tie a self-hosted Vault to a cloud provider's KMS.

    Step 15: Snapshot Backups

    Raft storage supports online snapshots — a point-in-time copy of the entire Vault state (secrets, policies, auth methods, tokens, everything).

    Take a manual snapshot:

    bash
    vault operator raft snapshot save /var/backups/vault/snapshot-$(date +%F-%H%M).snap

    Automate daily snapshots with a systemd timer:

    bash
    sudo mkdir -p /var/backups/vault
    sudo chown vault:vault /var/backups/vault

    sudo tee /etc/systemd/system/vault-snapshot.service > /dev/null <<'EOF' [Unit] Description=Vault Raft snapshot After=vault.service Requires=vault.service

    [Service] Type=oneshot User=vault Environment="VAULT_ADDR=https://vault.example.com:8200" EnvironmentFile=/etc/vault.d/snapshot.env ExecStart=/bin/bash -c '/usr/bin/vault operator raft snapshot save /var/backups/vault/snapshot-$(date +%%F-%%H%%M).snap' ExecStartPost=/bin/bash -c 'find /var/backups/vault -name "snapshot-*.snap" -mtime +14 -delete' EOF

    sudo tee /etc/systemd/system/vault-snapshot.timer > /dev/null <<'EOF' [Unit] Description=Daily Vault snapshot

    [Timer] OnCalendar=daily Persistent=true

    [Install] WantedBy=timers.target EOF

    The snapshot service needs a token with the sys/storage/raft/snapshot capability. Create one:

    bash
    vault policy write snapshot - <<'EOF'
    path "sys/storage/raft/snapshot" { capabilities = ["read"] }
    EOF

    vault token create -policy=snapshot -period=768h -format=json \ | jq -r '.auth.client_token' \ | sudo tee /etc/vault.d/snapshot.env > /dev/null

    sudo sed -i '1s/^/VAULT_TOKEN=/' /etc/vault.d/snapshot.env sudo chown root:vault /etc/vault.d/snapshot.env sudo chmod 640 /etc/vault.d/snapshot.env

    Enable the timer:

    bash
    sudo systemctl daemon-reload
    sudo systemctl enable --now vault-snapshot.timer
    sudo systemctl list-timers | grep vault

    Ship snapshots off-box. Snapshots are encrypted with your Vault's master key, so a stolen snapshot is useless without the unseal/recovery keys — but still push them to encrypted object storage (S3, Backblaze B2, Wasabi) via rclone or restic for disaster recovery.

    Restore a snapshot (destructive — use only on a rebuilt node):

    bash
    vault operator raft snapshot restore /var/backups/vault/snapshot-2026-04-16-0200.snap

    Troubleshooting

    ProblemCauseSolution
    Error checking seal status: Get ... x509: certificate signed by unknown authorityVAULT_ADDR using IP or short hostname that isn't on the certUse the FQDN from the cert: export VAULT_ADDR="https://vault.example.com:8200"
    Error initializing: ... server is not yet initialized then immediately sealed again after restartExpected with ShamirUnseal after each restart, or configure auto-unseal (Step 14)
    permission denied reading /opt/vault/tls/vault.keyCert copied but ownership not updatedsudo chown vault:vault /opt/vault/tls/
    failed to read cluster members: rpc errorRaft node IDs or cluster_addr misconfiguredEnsure node_id is unique per node and cluster_addr resolves between peers
    storage migration check error: ... mlock is not supportedRunning Vault inside an unprivileged containerEither grant IPC_LOCK capability or set disable_mlock = true (reduces security)
    Audit logs growing fast and filling diskHigh request volume, no rotationConfigure logrotate (Step 13) and consider shipping to remote syslog
    Error making API request: 503: local node not activeNode is a Raft follower — only the leader serves writesPoint VAULT_ADDR at the leader, or use a load balancer that health-checks /v1/sys/health?standbyok=true

    Useful diagnostic commands

    bash
    sudo journalctl -u vault -f
    vault status
    vault operator raft list-peers
    vault operator raft autopilot state
    vault read sys/health

    FAQ

    Do I need a cluster, or is a single Vault node enough?

    A single Raft node is fully functional for development and low-stakes internal use. For anything production-critical, run three nodes: Raft tolerates (N-1)/2 failures, so three nodes survive one failure and five nodes survive two. Add nodes by installing Vault on new VPS instances with unique node_id values, pointing retry_join at an existing leader, and unsealing with the same keys.

    How is Vault different from AWS Secrets Manager or 1Password Secrets Automation?

    Managed secret stores are good at static KV. Vault's differentiator is the breadth of secrets engines: dynamic database credentials (Vault generates a new MySQL/Postgres user with a scoped role, hands over the creds, and auto-revokes them after TTL), PKI certificate issuance, SSH CA signing, AWS/Azure/GCP credential leasing, Kubernetes service account tokens, and Transit encryption-as-a-service. Plus you own the data and the audit pipeline.

    How do I rotate the unseal keys?

    Use vault operator rekey with a quorum of existing keys. This generates a new set of shares without changing any data; apps keep working without restart. Rotate annually and whenever a keyholder leaves the company.

    What's the difference between a root token and a recovery key?

    A root token is an actual Vault token that bypasses all policies — use it once during bootstrap, then revoke it. Unseal keys (Shamir) decrypt the master key at startup. Recovery keys (auto-unseal) replace unseal keys once the KMS is doing the unsealing — they're needed for operator-level actions like generating a new root token or rekeying.

    Can Vault run inside Kubernetes instead of on a VPS?

    Yes — HashiCorp publishes an official Helm chart, and many teams run Vault in-cluster backed by integrated Raft on persistent volumes. The VPS path in this guide is simpler, more portable across clouds, and keeps Vault decoupled from any one Kubernetes cluster's lifecycle. Either works.

    What are the resource requirements for Vault?

    Vault itself is light: idle memory is under 200 MB, and CPU is near zero outside of request bursts. The demanding resources are disk IOPS (Raft fsyncs every write) and network latency between Raft peers. A 2 vCPU / 4 GB RAM NVMe VPS handles thousands of secrets/sec. The CloudCore Professional plan's 6 vCPU / 12 GB / NVMe gives you room to run Vault alongside Consul or Nomad on the same node for a small-team HashiStack.

    How do I migrate from Vault to OpenBao (or vice versa)?

    Because OpenBao forked from Vault 1.14, a Vault Raft snapshot restores cleanly into OpenBao up to that version. For newer Vault releases, export secrets via the API into a neutral format and reimport. API compatibility is maintained for the core paths and engines covered in this guide.

    Next Steps

    With Vault running and hardened, build out the rest of your platform:

    • Pair Vault with Consul for service discovery — Install Consul on Ubuntu and use Consul Connect intentions to gate which services can talk to Vault.
    • Orchestrate workloads with Nomad — Install Nomad on Ubuntu and use its native Vault integration to inject short-lived tokens into every job.
    • Front Vault with Nginx — Install Nginx on Ubuntu to add IP allowlists, rate limiting, and a cleaner URL in front of the Vault API.
    • Deploy Vault Agent — Vault Agent runs alongside your apps, handles auto-auth (AppRole, JWT, Kubernetes), caches tokens, and templates secrets into config files. It eliminates most of the custom glue code for secret consumption.
    • Turn on Vault's UI — The UI is already enabled in the config (ui = true). Browse to https://vault.example.com:8200/ui and log in with Alice's userpass credentials or OIDC.
    • Explore dynamic database secrets — Enable the database secrets engine to generate just-in-time Postgres or MySQL users with scoped privileges and automatic revocation. This is the single biggest security win most teams get from Vault.
    For the canonical reference, the HashiCorp Vault documentation covers every engine, auth method, and API endpoint in depth.

    Deploy Vault on a VPS built for it
    >
    The CloudCore Professional plan gives you the 6 vCPU, 12 GB RAM, and NVMe SSD that Vault's Raft storage loves — with enough headroom to run Consul and Nomad on the same box. Unmetered bandwidth, EU-hosted options for GDPR, and root access from minute one.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket