Move HashiCorp Vault OSS to Another AWS EC2 Instance (Ubuntu 24.04, File Storage, AWS KMS Auto-Unseal)

This post shows how to move a single-node Vault OSS deployment from one EC2 instance to another existing EC2 host (already running other systems). The Vault service is moved, but the Vault URL stays the same.

  • Target host real DNS / machine name: app.maksonlee.com (10.0.128.5)
  • Vault service DNS name (the URL users use): vault.maksonlee.com

We do not rename the target host to vault.maksonlee.com. Instead, after migration, we update DNS so vault.maksonlee.com points to the target host.

This setup uses:

  • Vault OSS (apt)
  • Storage: file (example: /opt/vault/data) — single node, no HA
  • Auto-unseal: AWS KMS (seal "awskms")
  • HTTPS: NGINX reverse proxy → http://127.0.0.1:8200
  • TLS: Let’s Encrypt DNS-01 (DNS challenge)

Note: Exposing Vault via NGINX exposes both UI + API at the same HTTPS endpoint (the UI calls the API).

This post assumes you already have a working Vault OSS deployment as set up in my previous post:


Lab context

Source (current Vault host)

  • Host: vault.maksonlee.com currently resolves to this machine
  • Private IP: 10.0.128.6
  • Vault data: /opt/vault/data (file storage)
  • Vault config: /etc/vault.d/vault.hcl

Target (existing EC2 host with other services)

  • Host: app.maksonlee.com
  • Private IP: 10.0.128.5
  • Already running: other services (NGINX vhosts, etc.)

Goal: Move Vault to app.maksonlee.com, then change DNS so vault.maksonlee.com10.0.128.5.


Prerequisites

  • You have your Vault admin/root token and recovery keys stored safely.
  • The target instance has an IAM instance profile/role that can use the same KMS key:
    • kms:Encrypt, kms:Decrypt, kms:GenerateDataKey, kms:DescribeKey
  • SSH access from source → target
  • DNS provider API access for Let’s Encrypt DNS-01 (Cloudflare example below)

  1. Confirm storage path and KMS seal on the source

On the source host (10.0.128.6):

vault status
sudo sed -n '/^storage "file" {/,/^}/p' /etc/vault.d/vault.hcl
sudo grep -nE '^(storage|seal|listener|api_addr|cluster_addr|ui)' /etc/vault.d/vault.hcl

Confirm:

  • Storage Type: file
  • Seal Type: awskms
  • storage path (example): path = "/opt/vault/data"

  1. Prepare the target host (install Vault, but stop it)

On the target host app.maksonlee.com (10.0.128.5):

  • Install Vault
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 \
$(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

sudo apt update && sudo apt install vault -y
  • Enable service, then stop it immediately
sudo systemctl enable vault
sudo systemctl stop vault
  • Verify IAM role via IMDSv2 (target)
TOKEN=$(curl -sS -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

curl -sS -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/

curl -sS -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/info

You should see your instance profile/role.


  1. Issue the Vault TLS certificate on the target (Let’s Encrypt DNS-01)
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 30 \
  -d vault.maksonlee.com

  1. Create the Vault NGINX site on the target

On the target host, create /etc/nginx/sites-available/vault:

server {
    server_name vault.maksonlee.com;

    location / {
        proxy_pass http://127.0.0.1:8200;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/vault.maksonlee.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vault.maksonlee.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

server {
    listen 80;
    server_name vault.maksonlee.com;
    return 301 https://$host$request_uri;
}

Enable and reload:

sudo ln -sf /etc/nginx/sites-available/vault /etc/nginx/sites-enabled/vault
sudo nginx -t && sudo systemctl reload nginx

  1. Stop Vault on the source (downtime begins)

On the source host:

sudo systemctl stop vault
sudo systemctl status vault --no-pager

  1. Copy Vault data and config from source → target (rsync)

We use rsync to preserve numeric ownership and permissions. We also run rsync as root on the target using --rsync-path="sudo rsync".

On the source host (10.0.128.6):

  • Copy /opt/vault/data
sudo rsync -aHAX --numeric-ids --delete \
  -e "ssh -i /home/ubuntu/.ssh/aws-cdlee" \
  --rsync-path="sudo rsync" \
  /opt/vault/data/ ubuntu@10.0.128.5:/opt/vault/data/
  • Copy /etc/vault.d
sudo rsync -aHAX --numeric-ids --delete \
  -e "ssh -i /home/ubuntu/.ssh/aws-cdlee" \
  --rsync-path="sudo rsync" \
  /etc/vault.d/ ubuntu@10.0.128.5:/etc/vault.d/

On the target, ensure correct ownership:

sudo chown -R vault:vault /opt/vault/data /etc/vault.d

  1. Start Vault on the target and verify real secrets

On the target:

export VAULT_ADDR=http://127.0.0.1:8200

sudo systemctl restart vault
sudo systemctl status vault --no-pager

vault status

Verify your data is there:

vault secrets list
vault kv list secret/
vault kv get secret/backstage/your-real-key

  1. Update DNS: point vault.maksonlee.com to the target instance

Update routing so clients reach the target host:

  • Public DNS vault.maksonlee.com → target public IP/EIP (or your public entrypoint)
  • If you have split DNS for VPN/internal → update internal routing to 10.0.128.5

After DNS cutover, validate:

curl -vk https://vault.maksonlee.com/

  1. Decommission the old Vault

On the source (after you confirm the new endpoint works):

sudo systemctl disable vault
sudo systemctl stop vault

Done

You moved Vault from 10.0.128.6 to an existing host app.maksonlee.com (10.0.128.5) by:

  • Installing Vault on the target (without initializing a new cluster)
  • Issuing a DNS-01 certificate for vault.maksonlee.com
  • Copying the file backend (/opt/vault/data) and config (/etc/vault.d)
  • Starting Vault on the target and verifying real secrets
  • Updating DNS so vault.maksonlee.com points to the new host

Did this guide save you time?

Support this site

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top