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.comcurrently 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.com → 10.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)
- 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: fileSeal Type: awskms- storage path (example):
path = "/opt/vault/data"
- 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.
- 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
- 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
- Stop Vault on the source (downtime begins)
On the source host:
sudo systemctl stop vault
sudo systemctl status vault --no-pager
- 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
- 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
- Update DNS: point
vault.maksonlee.comto 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/
- 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.compoints to the new host
Did this guide save you time?
Support this site