Deployment guide

OpenBao 2.7.2

Overview

OpenBao is an open source secrets manager, maintained under the Linux Foundation and API-compatible with HashiCorp Vault. It stores and versions secrets (the KV engine), issues short-lived tokens bound to policies, and records every request in an audit log. Applications fetch their database passwords and API keys from one place instead of carrying them in files.

This guide covers the OpenBao 2.7.2 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud identity, secrets and PKI listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.

A stock OpenBao server starts uninitialised: whoever first calls sys/init receives the unseal keys and the root token. On a public address, anyone can win that race. This image closes the gap and makes the server safe to run on the internet.

What you get:

  • No takeover window. First boot initialises OpenBao on the instance itself, over loopback, before any public port opens. nginx starts only after OpenBao reports initialized: true, and it never forwards a write to sys/init. The image ships with no storage, no keys, no token and no certificate.
  • Keys unique to your instance. First boot creates five unseal keys (any three unseal the vault) and a root token. It writes them to one file, /root/openbao-credentials.txt, which only root can read. The OpenBao service account cannot read it. Keys and tokens are never on a command line, in a service's environment or in a log.
  • HTTPS from the first boot. nginx serves the web UI (/ui/) and the API (/v1/) on port 443. The certificate is generated on your instance's first boot, as an end-entity certificate that names the instance's DNS names and addresses. Port 80 only redirects to HTTPS. OpenBao listens on 127.0.0.1:8200 and cannot be reached from the network.
  • Auto-unseal at boot, your choice. While the unseal keys are on the instance, openbao-unseal.service unseals OpenBao after every reboot, so applications keep working unattended. Step 7 moves the keys off the instance and switches auto-unseal off.
  • Audit log on. A file audit device records every request in /var/log/openbao/audit.log, readable by the service only. Tokens and secret values are replaced by HMACs. The log is rotated daily and kept for 14 days.
  • Dangerous endpoints closed. The raw storage endpoint is off, and metrics and profiling need a token. From the network, nginx refuses sys/raw, raft join and bootstrap, root token generation and rekey, so run those on the instance. Requests are logged with the real client address, and a forged X-Forwarded-For header is ignored.
  • Metadata service blocked. OpenBao, nginx and the first boot and unseal services are blocked from the instance metadata service, and the AMI requires IMDSv2. No secrets engine that fetches URLs is enabled by default.
  • Pinned and patched. OpenBao 2.7.2 is the official release package, verified against the OpenBao release signing key and pinned by its SHA-256. Ubuntu 24.04 LTS is fully patched at build time, with unattended security upgrades switched on.
  • On the image: OpenBao 2.7.2 (/usr/bin/bao, the official openbao package), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
  • Defaults: default user ubuntu, architecture x86_64, and recommended instance type m5.large (t3.medium is enough for a small team).
  • 24/7 SecureAnyCloud support by email and live chat

Prerequisites

You need an AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.

Create a security group that allows TCP 22 (SSH) from your administrators. Also allow TCP 443 (the UI and the API) from your administrators and from the hosts whose applications read secrets. Port 80 is optional, because it only redirects to HTTPS.

Never open ports 8200 or 8201. They listen on loopback only and refuse connections from the network, even if a security group allows them.

1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick OpenBao 2.7.2 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2. Select your instance type, key pair, subnet and the security group from the prerequisites.

To launch with the AWS CLI instead, replace the values in angle brackets:

aws ec2 create-security-group --region <region> --vpc-id <your-vpc> \
  --group-name openbao --description "OpenBao: SSH and HTTPS from admins and applications"
aws ec2 authorize-security-group-ingress --region <region> --group-id <sg-id> --ip-permissions \
  'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=<your-admin-cidr>}]' \
  'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=<your-client-cidr>}]'
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <sg-id> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=openbao}]'

First boot takes well under a minute. It names the server after the instance's public DNS name, or its private DNS name when the instance has no public one.

To use your own name from the first boot, add user data that writes the name before first boot runs. For example, use #!/bin/sh followed by mkdir -p /etc/sac && echo vault.example.com > /etc/sac/openbao-domain.preset. You can also change the name later with openbao-set-domain (Step 12).

2

Connect to your instance

For the OpenBao 2.7.2 on Ubuntu 24.04 version, the SSH user is ubuntu. Connect with your key pair:

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot creates the certificate, initialises and unseals OpenBao on loopback, and only then lets nginx start. Check that all three services are active and that first boot has finished:

systemctl is-active openbao openbao-unseal nginx
sudo test -f /var/lib/sac/data/openbao-firstboot.done && echo FIRSTBOOT_DONE
active active active FIRSTBOOT_DONE

The first boot log shows the order. OpenBao was initialised before nginx started:

sudo journalctl -b -o short-iso -u openbao-firstboot -u nginx | grep -E 'OPENBAO_INITIALISED|Start(ed|ing) nginx' | cut -d' ' -f3-
openbao-firstboot.sh[1012]: [2026-11-07T22:30:41+0000] openbao-firstboot: OPENBAO_INITIALISED 2026-11-07T22:30:41.512893004Z systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server... systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.

Only SSH and nginx listen on the network. OpenBao (port 8200) and its cluster port (8201) are on loopback:

sudo ss -tlnH | awk '{print $4}' | sort -u
0.0.0.0:22 0.0.0.0:443 0.0.0.0:80 127.0.0.1:8200 127.0.0.1:8201 127.0.0.53%lo:53 127.0.0.54:53 [::]:22 [::]:443 [::]:80

The bao command line talks to the local listener:

export BAO_ADDR=http://127.0.0.1:8200 bao status -format=json | jq -c '{initialized, sealed, t, n, version, storage_type}'
{"initialized":true,"sealed":false,"t":3,"n":5,"version":"2.7.2","storage_type":"raft"}

Run the self-test. It checks OpenBao on loopback and through nginx over HTTPS, confirms that a request without a token is refused, and writes, reads and deletes a value with the root token:

sudo openbao-selftest
openbao-selftest: cubbyhole round trip over https://ec2-203-0-113-10.compute-1.amazonaws.com/ OK OPENBAO_SELFTEST_OK
4

Read your instance's details

The root-only file /root/openbao-credentials.txt holds the URLs, the certificate fingerprint, the root token (OPENBAO_ROOT_TOKEN) and the five unseal keys (OPENBAO_UNSEAL_KEY_1 to _5). Show everything except the secrets:

sudo grep -vE '^OPENBAO_(ROOT_TOKEN|UNSEAL_KEY_[0-9])=' /root/openbao-credentials.txt
# OpenBao 2.7.2 - written on first boot by openbao-firstboot.service. Unique to this instance. Keep it secret. OPENBAO_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com OPENBAO_UI_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/ui/ OPENBAO_TLS_SHA256=3F:9A:...:C2 BAO_ADDR=http://127.0.0.1:8200 OPENBAO_UNSEAL_THRESHOLD=3
#
# Sign in to the UI (https://ec2-203-0-113-10.compute-1.amazonaws.com/ui/, method Token) with OPENBAO_ROOT_TOKEN, or on this instance:
# export BAO_ADDR=http://127.0.0.1:8200
# export BAO_TOKEN="$(sudo sed -n 's/^OPENBAO_ROOT_TOKEN=//p' /root/openbao-credentials.txt)"
# FIRST STEPS (see the guide): create an admin policy + token, then revoke this root token; copy the five unseal
# keys to separate safe places OFF this instance and delete them from this file. While the keys are here,
# openbao-unseal.service unseals OpenBao on every boot (auto-unseal); without them, unseal with: bao operator unseal
# The certificate is self-signed and unique to this instance (fingerprint above). Your own name:
#
sudo openbao-set-domain vault.example.com Self-test:
sudo openbao-selftest
# User guide: https://secureanycloud.com/stacks/openbao-2-7-2/

To see the root token itself, which starts with s., run sudo sed -n 's/^OPENBAO_ROOT_TOKEN=//p' /root/openbao-credentials.txt. To see the full file, including the unseal keys, run sudo cat /root/openbao-credentials.txt.

5

Sign in to the web UI

Open OPENBAO_UI_URL from Step 4 in your browser. The certificate is self-signed and unique to your instance, so the browser warns about it once. Compare the fingerprint it shows with OPENBAO_TLS_SHA256 before you continue, or replace the certificate (Step 12). Do not add the certificate to your operating system's trust store.

On the sign-in page, select the Token method, paste the root token and choose Sign In.

The UI opens on Secrets Engines. Because you signed in with a root token, the UI warns that it will not keep the token in your browser, so you sign in again after a refresh or in a new window. A fresh instance has no secrets engine of its own yet (the screenshot was taken after Step 8 enabled secret/). Policies lists the ACL policies, and Raft Storage shows the single storage node.

The UI signs in by sending the token to auth/token/lookup-self over HTTPS. You can send the same request on the instance, through nginx, to see what the root token is. The token is read into a header file, never onto a command line.

The certificate's directory also holds its private key and is readable by root only. First copy the public certificate to your home directory. Your applications need the same file:

D=$(cat /etc/sac/openbao-domain)
sudo cat /etc/ssl/openbao/openbao.crt > ~/openbao.crt umask 077
sudo sed -n 's/^OPENBAO_ROOT_TOKEN=/X-Vault-Token: /p' /root/openbao-credentials.txt > ~/root.h curl -s --cacert ~/openbao.crt --connect-to ::127.0.0.1:443 -H @"$HOME/root.h" \
  "https://$D/v1/auth/token/lookup-self" | jq -c '{policies: .data.policies, ttl: .data.ttl}' curl -s -o /dev/null -w 'without a token: %{http_code}\n' --cacert ~/openbao.crt \
  --connect-to ::127.0.0.1:443 "https://$D/v1/auth/token/lookup-self" shred -u ~/root.h
{"policies":["root"],"ttl":0} without a token: 403
6

Create an admin policy and token

The root token can do anything and never expires. Use it once to create an administrator policy and a token for day-to-day work, then revoke it (Step 7). The admin token is written to ~/.openbao-admin-token, readable by you only, and is never printed:

export BAO_ADDR=http://127.0.0.1:8200
export BAO_TOKEN="$(sudo sed -n 's/^OPENBAO_ROOT_TOKEN=//p' /root/openbao-credentials.txt)" bao policy write admin - <<'EOF' path "*" { capabilities = ["create", "read", "update", "patch", "delete", "list", "sudo"] } EOF umask 077 bao token create -orphan -policy=admin -ttl=768h -renewable=true -display-name=admin -field=token > ~/.openbao-admin-token unset BAO_TOKEN BAO_TOKEN="$(cat ~/.openbao-admin-token)" bao token lookup -format=json | jq -c '{policies: .data.policies, orphan: .data.orphan, renewable: .data.renewable}'
Success! Uploaded policy: admin {"policies":["admin","default"],"orphan":true,"renewable":true}

The admin token is created as an orphan (-orphan). A token created without that flag is a child of the token that created it, so revoking the root token in Step 7 would revoke the admin token with it.

The admin token lasts 32 days. Renew it with bao token renew before then, or sign in with a longer-term auth method (userpass, OIDC or AppRole). From here on, the guide uses the admin token:

export BAO_ADDR=http://127.0.0.1:8200
export BAO_TOKEN="$(cat ~/.openbao-admin-token)" bao token capabilities sys/policies/acl/admin
create, delete, list, patch, read, sudo, update
7

Secure the unseal keys and the root token

The unseal keys are stored on the instance so that it can unseal itself after a reboot. Anyone who obtains a copy of the instance's disk, such as a snapshot or a backup, also has the keys. Move the keys off the instance with the three actions below.

Copy the five unseal keys to five separate safe places off the instance, for example a password manager entry for each of five key holders. Any three keys unseal the vault. From your workstation, run:

bash ssh -i <your-key.pem> ubuntu@<public-ip> "sudo grep '^OPENBAO_UNSEAL_KEY_' /root/openbao-credentials.txt"

Revoke the root token once your admin token works (Step 6). You can create a new root token later with three unseal keys (bao operator generate-root on the instance), so keep the keys safe. Run:

bash export BAO_ADDR=http://127.0.0.1:8200 BAO_TOKEN="$(sudo sed -n 's/^OPENBAO_ROOT_TOKEN=//p' /root/openbao-credentials.txt)" bao token revoke -self sudo sed -i '/^OPENBAO_ROOT_TOKEN=/d' /root/openbao-credentials.txt

Switch auto-unseal off by deleting the keys from the instance. From the next reboot, OpenBao stays sealed until three key holders unseal it (Step 10). Run:

bash sudo sed -i '/^OPENBAO_UNSEAL_KEY_/d' /root/openbao-credentials.txt sudo systemctl restart openbao-unseal sudo journalctl -u openbao-unseal -o cat | grep -o 'openbao-unseal: .*' | tail -n 1

text openbao-unseal: auto-unseal is OFF (no unseal keys in /root/openbao-credentials.txt): OpenBao stays sealed; unseal it with 'bao operator unseal' or the UI

To switch auto-unseal back on, add three of the key lines (OPENBAO_UNSEAL_KEY_1=...) back to the file.

If you keep auto-unseal on, restrict who can snapshot the volume, and keep the root volume encrypted with a KMS key that only your administrators can use.

8

Store and read a secret

Enable a KV version 2 secrets engine at secret/, write a secret and read it back:

export BAO_ADDR=http://127.0.0.1:8200
export BAO_TOKEN="$(cat ~/.openbao-admin-token)" bao secrets enable -path=secret kv-v2 bao kv put -mount=secret myapp/database username=appuser password="$(openssl rand -hex 16)" > /dev/null bao kv put -mount=secret billing/stripe api_key="$(openssl rand -hex 16)" > /dev/null bao kv get -mount=secret -field=username myapp/database; echo bao kv metadata get -mount=secret -format=json myapp/database | jq -c '{current_version: .data.current_version}'
Success! Enabled the kv-v2 secrets engine at: secret/ appuser {"current_version":1}

In the web UI, open the secret under Secrets engines > secret/ > myapp/ > database. Values are masked until you choose the eye icon. Metadata and Version show the secret's history.

9

Give an application a read-only token

A policy grants access to paths, and a token carries policies. The following policy lets an application read the secrets under secret/myapp/ and nothing else:

export BAO_ADDR=http://127.0.0.1:8200
export BAO_TOKEN="$(cat ~/.openbao-admin-token)" bao policy write myapp-read - <<'EOF' path "secret/data/myapp/*" { capabilities = ["read"] } path "secret/metadata/myapp/*" { capabilities = ["read", "list"] } EOF umask 077 bao token create -policy=myapp-read -ttl=24h -display-name=myapp -field=token > ~/.myapp-token
export BAO_TOKEN="$(cat ~/.myapp-token)" echo "read: $(bao kv get -mount=secret -field=username myapp/database)" bao kv put -mount=secret myapp/database username=changed > /dev/null 2>&1 && echo "write: ALLOWED" || echo "write: refused" bao kv get -mount=secret billing/stripe > /dev/null 2>&1 && echo "other path: ALLOWED" || echo "other path: refused" bao policy list > /dev/null 2>&1 && echo "sys: ALLOWED" || echo "sys: refused"
Success! Uploaded policy: myapp-read read: appuser write: refused other path: refused sys: refused

An application on another host reads the secret over HTTPS with the same token. The server's certificate fingerprint is in Step 4. To try this on the instance, through nginx, run:

D=$(cat /etc/sac/openbao-domain) printf 'X-Vault-Token: %s\n' "$(cat ~/.myapp-token)" > ~/.myapp.h curl -s --cacert ~/openbao.crt --connect-to ::127.0.0.1:443 -H @"$HOME/.myapp.h" \
  "https://$D/v1/secret/data/myapp/database" | jq -r '.data.data.username' curl -s -o /dev/null -w 'write over HTTPS: %{http_code}\n' --cacert ~/openbao.crt --connect-to ::127.0.0.1:443 \
  -H @"$HOME/.myapp.h" -H 'Content-Type: application/json' -d '{"data":{"username":"changed"}}' "https://$D/v1/secret/data/myapp/database" shred -u ~/.myapp.h
appuser write over HTTPS: 403

The policy is also listed under Policies > ACL Policies > myapp-read in the web UI, where you can edit or download it.

From your workstation, check the certificate fingerprint first. Then use the API or the bao CLI with BAO_ADDR=https://<openbao-host> and BAO_CACERT pointing at a copy of the certificate (~/openbao.crt from Step 5):

openssl s_client -connect <openbao-host>:443 -servername <openbao-host> </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256
10

Reboot and unseal

OpenBao seals itself whenever it stops. With the unseal keys still on the instance, openbao-unseal.service unseals it at boot and nginx starts after it. Your secrets are kept in OpenBao's integrated storage on the root volume. Reboot the instance:

sudo reboot

After the reboot, confirm that OpenBao is unsealed and that your secret is still readable:

export BAO_ADDR=http://127.0.0.1:8200 bao status -format=json | jq -c '{sealed}' BAO_TOKEN="$(cat ~/.openbao-admin-token)" bao kv get -mount=secret -field=username myapp/database; echo
sudo journalctl -b -u openbao-unseal -o cat | grep -o 'openbao-unseal: .*' | tail -n 1
{"sealed":false} appuser openbao-unseal: unsealed

With auto-unseal switched off (Step 7), OpenBao is sealed after a reboot, and the API answers 503 until three key holders unseal it. They can unseal it on the instance or in the web UI. bao operator unseal prompts for one key at a time without echoing it:

export BAO_ADDR=http://127.0.0.1:8200 bao operator unseal bao operator unseal bao operator unseal
11

Audit log

Every request is logged in /var/log/openbao/audit.log (owner openbao, mode 0600) as one JSON object per line. Tokens and secret values appear only as HMACs, and the client address is the real TCP peer:

sudo tail -n 1 /var/log/openbao/audit.log | jq -c '{type, path: .request.path, token: (.auth.client_token // "" | .[0:12]), remote: .request.remote_address}'
{"type":"response","path":"secret/data/myapp/database","token":"hmac-sha256:","remote":"127.0.0.1"}

The path is that of the last request. The token field always starts with hmac-sha256:, never with s..

To check whether a token you hold appears in the log, compute its HMAC with bao audit hash instead of searching for the token itself. logrotate rotates the file daily and keeps 14 days, and OpenBao reopens the file on rotation.

12

Use your own name and certificate

Point a DNS name at the instance. An Elastic IP keeps the address stable. Then make that name the server's name.

openbao-set-domain renders nginx and OpenBao's API address for the new name, makes a new self-signed certificate for it, and updates the URLs in /root/openbao-credentials.txt. It restarts OpenBao, which seals it. With auto-unseal on, OpenBao is unsealed again at once:

sudo openbao-set-domain vault.example.com

The new certificate has a new fingerprint, shown as OPENBAO_TLS_SHA256 in the credentials file. Copy the certificate again with sudo cat /etc/ssl/openbao/openbao.crt > ~/openbao.crt and give your applications the new copy.

To use a certificate from a public certificate authority instead, run openbao-set-domain first. Then install your certificate and key as /etc/ssl/openbao/openbao.crt and /etc/ssl/openbao/openbao.key (root only) and run sudo systemctl reload nginx. Clients then no longer need BAO_CACERT.

13

Operate and harden

Metadata service. OpenBao, nginx and the first boot and unseal services are blocked from the instance metadata service:

systemctl show -p IPAddressDeny openbao nginx openbao-unseal openbao-firstboot
IPAddressDeny=fd00:ec2::254/128 ::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32 IPAddressDeny=fd00:ec2::254/128 ::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32 IPAddressDeny=fd00:ec2::254/128 ::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32 IPAddressDeny=fd00:ec2::254/128 ::ffff:169.254.169.254/128 169.254.169.254/32 168.63.129.16/32

The order of the entries varies between boots.

Endpoints refused from the network. Without a token, every API path except health, seal status, leader, init status and sign-in answers 403. nginx also refuses the raw storage, raft join, root token generation and rekey endpoints, and any write to sys/init, even with a valid token. Run those operations on the instance against http://127.0.0.1:8200:

D=$(cat /etc/sac/openbao-domain) for p in sys/mounts sys/raw/core sys/generate-root/attempt; do printf '%s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' --cacert ~/openbao.crt --connect-to ::127.0.0.1:443 "https://$D/v1/$p")" done printf 'PUT sys/init %s\n' "$(curl -s -o /dev/null -w '%{http_code}' -X PUT -d '{}' --cacert ~/openbao.crt --connect-to ::127.0.0.1:443 "https://$D/v1/sys/init")" curl -s --cacert ~/openbao.crt --connect-to ::127.0.0.1:443 "https://$D/v1/sys/init"
sys/mounts 403 sys/raw/core 403 sys/generate-root/attempt 403 PUT sys/init 403 {"initialized":true}

Backups. Take a snapshot of the integrated storage with the admin token, and copy it off the instance. OpenBao encrypts the snapshot, and restoring it needs the unseal keys of the vault it came from:

export BAO_ADDR=http://127.0.0.1:8200 BAO_TOKEN="$(cat ~/.openbao-admin-token)" bao operator raft snapshot save ~/openbao-$(date +%F).snap ls -l ~/openbao-*.snap | awk '{print $1}'
-rw-------

Logs. Read the service logs with sudo journalctl -u openbao -u openbao-unseal -u openbao-firstboot. nginx keeps its access log in /var/log/nginx without query strings and logs errors only at crit, because request lines can carry tokens.

Security group. Allow TCP 443 only from your administrators and the hosts whose applications read secrets. Allow TCP 22 only from your administrators.

Updates. Unattended security upgrades stay on for Ubuntu and nginx. OpenBao is pinned to the release package this image was built with, and new OpenBao releases arrive as new versions of this listing. To move to a new version, take a snapshot, launch the new version, and restore the snapshot into it with bao operator raft snapshot restore -force. You need the original unseal keys.

Support and licensing

OpenBao is developed by the OpenBao project under the Linux Foundation and distributed under the Mozilla Public License 2.0. The licence is in /usr/share/doc/openbao/copyright:

dpkg-query -W -f='${Package} ${Version}\n' openbao head -n 3 /usr/share/doc/openbao/copyright
openbao 2.7.2 Copyright (c) 2015 HashiCorp, Inc. Mozilla Public License, version 2.0

OpenBao is a fork of HashiCorp Vault 1.14, so the licence file keeps the original copyright line.

SecureAnyCloud provides the packaging, the first-boot automation, the HTTPS front end and the hardening, with 24/7 support by email and live chat at sales@secureanycloud.com.

All product names are trademarks of their respective owners. SecureAnyCloud is not affiliated with or endorsed by the OpenBao project or the Linux Foundation.

Deploy on AWS

Launch OpenBao 2.7.2 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS