GuidesBusiness and monitoringSecrets with OpenBao

Keep your team's secrets in OpenBao with rootless Podman

OpenBao on Debian 13, the open-source fork of HashiCorp Vault, in rootless Podman under a user of its own behind Caddy, with logins for each person, a password an app fetches by itself, an audit log and tested backups.

Tested on OpenBao 2.7.1 on Debian 13 (trixie) on a Melonslab server Updated October 14, 2026

Recommended server for this guide

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy · 2 vCPU · 8 GB Memory · 60 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€8.99/mo

Deploy now
On this page

What you will set up

OpenBao is a secrets manager: one place for the passwords, API keys and certificates that your servers and apps need, instead of spreading them across config files, chat messages and spreadsheets. Each person and each app logs in, and a policy decides which secrets they can read. Every secret keeps its old versions, and an audit log records who read or changed what.

Everything OpenBao stores is encrypted with a key that is not on the disk. After every start, OpenBao is sealed: it knows where its data is, but cannot read it until enough people have entered their part of the key. That is the price of the design, and step 6 shows how it works in practice.

OpenBao is a fork of HashiCorp Vault, started in 2023 from Vault 1.14, the last version under an open-source licence. As of 14 October 2026:

  • Licence and owner. OpenBao is open source under the Mozilla Public License 2.0, and is run by its community as a project of the Linux Foundation, under OpenSSF. Vault has been under the Business Source License 1.1 since version 1.15. That allows production use, but not offering Vault to others as a competing hosted or embedded product, and it is not an open-source licence. HashiCorp, and with it Vault, has belonged to IBM since February 2025.
  • Versions. The latest are OpenBao 2.7.1, from 1 October 2026, and Vault 2.1.2, from 7 October 2026. Both have moved on since the fork, so they are no longer the same program.
  • Features. OpenBao has namespaces, which divide one server between several teams or customers, at no cost; in Vault they are an Enterprise feature. Vault Enterprise has replication between data centres and Sentinel policies, which OpenBao lacks. OpenBao has moved the cloud integrations, for AWS, Azure and Google Cloud, and since 2.7 also LDAP, Kerberos and RADIUS logins, out of the main program into plugins you install separately.
  • Compatibility. OpenBao keeps Vault's API, down to the X-Vault-Token header, and its CLI is called bao instead of vault.

Here OpenBao runs from its official image, in Podman under a user of its own called openbao, with its data in a volume, behind Caddy from the Podman guide, which handles HTTPS.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • OpenBao 2.7.1 was initialised with three key shares, two of which unseal it, and was unsealed both on the server and in the web UI after every restart and reboot.
  • An admin account and a team account were created with their own policies, and the root token was revoked. The team account could read and change the team's secrets, and got "permission denied" anywhere else.
  • A secret got three versions, was rolled back to the first, deleted and restored. Old versions were kept, up to ten per secret.
  • A PostgreSQL container fetched its password from OpenBao through AppRole every time it started. After a reboot, with OpenBao still sealed, it started with the password from last time. The app's login was refused from any address but the server's own.
  • The audit log recorded every request with the visitor's real address, over IPv4 and IPv6, and with secret values replaced by hashes.
  • Automatic unsealing with a static key was switched on, survived a reboot, and was switched off again.
  • A backup was restored into an empty instance, and the secrets, users and policies came back. An update from OpenBao 2.6.4 to 2.7.1 kept all data.

OpenBao used between 20 and 40 MB of memory.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • an A record and an AAAA record for bao.example.com pointing at your server;
  • two or three people who will each keep one key share. Read step 5 before you start, so they are ready.

The examples use bao.example.com for OpenBao, 203.0.113.10 and 2001:db8::10 for your server's IPv4 and IPv6 addresses, which ip -brief address show eth0 shows, and anna and erik for two people on your team. Replace them throughout.

1. Create the user

As root:

useradd -m -s /bin/bash openbao
loginctl enable-linger openbao
machinectl shell openbao@

Everything up to step 4 runs as openbao.

2. Write the configuration

Create ~/openbao.hcl:

ui = true
api_addr = "https://bao.example.com"
cluster_addr = "https://127.0.0.1:8201"
disable_mlock = true

storage "raft" {
  path = "/openbao/data"
  node_id = "bao1"
}

listener "tcp" {
  address = "0.0.0.0:8200"
  # Caddy handles HTTPS. The port is only reachable from this server.
  tls_disable = true
  x_forwarded_for_authorized_addrs = "203.0.113.10"
}

audit "file" "file" {
  options {
    file_path = "/openbao/logs/audit.log"
  }
}

What the settings do:

  • ui = true switches on the web interface, at https://bao.example.com/ui/.
  • storage "raft" is OpenBao's built-in storage, called integrated storage. It keeps everything, encrypted, in one directory, here in a volume, and needs no database. More servers can join it later for high availability.
  • api_addr is the address OpenBao gives clients for itself, so it is the public https:// address. cluster_addr is where servers in a cluster talk to each other, on port 8201. Integrated storage needs it even with one server, and with one server it only talks to itself, so 127.0.0.1 inside the container is right. Port 8201 is not published.
  • tls_disable is safe here because OpenBao only listens on 127.0.0.1 on the server (step 3), and Caddy encrypts everything that leaves it. x_forwarded_for_authorized_addrs trusts Caddy to tell OpenBao each visitor's real address. Caddy's connections reach the container from the server's own IPv4 address, as the Podman guide explains, so that is the one to name. Without it, the audit log shows 203.0.113.10 for everyone.
  • disable_mlock is what OpenBao recommends with integrated storage. It lets the operating system swap OpenBao's memory to disk, which matters only if the server has swap.
  • The audit block writes the audit log (step 11). In OpenBao, audit logs are set in this file, not with a command.

3. Define the container

mkdir -p ~/.config/containers/systemd

Create ~/.config/containers/systemd/openbao.container:

[Unit]
Description=OpenBao

[Container]
ContainerName=openbao
Image=quay.io/openbao/openbao:2.7.1
Exec=server
Volume=%h/openbao.hcl:/openbao/config/openbao.hcl:ro
Volume=openbao-data:/openbao/data:U
Volume=openbao-audit:/openbao/logs:U
Environment=BAO_ADDR=http://127.0.0.1:8200
# Only Caddy, on this server, can reach OpenBao: the port is not open to the internet.
PublishPort=127.0.0.1:8174:8200

[Service]
Restart=always

[Install]
WantedBy=default.target
  • quay.io/openbao/openbao is the image the OpenBao project publishes. It runs OpenBao as an ordinary user inside the container.
  • Exec=server starts a real server with the configuration from step 2. Without it, the image starts a development server that keeps everything in memory and loses it when it stops.
  • :U gives the volumes to OpenBao's user inside the container.
  • BAO_ADDR lets the bao command inside the container find the server.
  • There is no AutoUpdate line on purpose: every restart seals OpenBao, so updates are done by hand (step 14).

Start it:

systemctl --user daemon-reload
systemctl --user start openbao
podman exec openbao bao status

Initialized false and Sealed true mean it is running and waiting for step 5. The image is about 190 MB.

To run bao commands without typing podman exec each time, add a shortcut:

echo "alias bao='podman exec -i -e BAO_TOKEN openbao bao'" >> ~/.bashrc
source ~/.bashrc

-e BAO_TOKEN passes your login, which you keep in the variable BAO_TOKEN in your own shell, to each command.

4. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

bao.example.com {
    reverse_proxy 127.0.0.1:8174
}

Restart Caddy with systemctl --user restart caddy. https://bao.example.com/ui/ now shows a page to set up OpenBao. Don't use it: do step 5 on the server instead, right away, so nobody else gets there first.

5. Initialise: key shares and the root token

Go back to root with exit, and switch to machinectl shell openbao@ again. Then:

podman exec -it openbao bao operator init -key-shares=3 -key-threshold=2

OpenBao creates the key that encrypts everything, splits it into three key shares, any two of which can unseal it, and prints them with a root token:

Unseal Key 1: U6jUw5pF...
Unseal Key 2: KzPB2nCU...
Unseal Key 3: vLL92MWG...

Initial Root Token: s.1eTbozfb...

This is the only time they are shown. OpenBao does not keep the key itself, so if you lose enough shares, nobody can read your secrets again, not even from a backup.

Where to keep them:

  • Give each share to a different person, who keeps it in their own password manager. Two of three means any two of you can unseal OpenBao, one share can be lost or one person away without trouble, and nobody can do it alone.
  • Never keep the shares in OpenBao itself, since you need them before OpenBao can be read, and never all of them in the same place, such as one shared folder or one email.
  • If you are the only admin, still keep the shares apart: one in your password manager, one printed in a safe, one with someone you trust.
  • Keep the root token only until step 8, in a place where you can delete it afterwards.

The root token can do anything, never expires, and bypasses all policies. Everything it does is logged as "root", not as a person. Step 8 creates admin accounts in people's own names and then revokes it. If you ever need a root token again, the share holders can make a new one together, as described under Troubleshooting.

6. Unseal it, after every start

Unseal OpenBao now, by entering two of the shares:

podman exec -it openbao bao operator unseal
podman exec -it openbao bao operator unseal

Each command asks for one share without showing it. After the second, Sealed is false.

OpenBao is sealed again every time it starts: after a reboot, a restart of the container and an update. Until two share holders have unsealed it, https://bao.example.com shows only the unseal page, and apps that ask for secrets get an error. Share holders don't need SSH: they can each enter their share on that page, under Unseal Key Portion, with Unseal, from their own computers. When the last share is in, the page changes to the login.

If restarts happen when nobody is around, step 12 shows how OpenBao can unseal itself, and what that costs.

7. Store secrets

Log in with the root token, which read -rs takes without showing it, and turn on a store for secrets:

read -rs BAO_TOKEN && export BAO_TOKEN
bao secrets enable -path=secret kv-v2

kv-v2 is OpenBao's key/value store with versions. Each secret has a path, such as team/wifi, and holds one or more fields:

bao kv put -mount=secret team/wifi ssid=Office password=first-pass
bao kv put -mount=secret team/wifi ssid=Office password=second-pass
bao kv get -mount=secret team/wifi

Each put makes a new version, and OpenBao keeps the last ten. To read an old version, and to make it the current one again:

bao kv get -mount=secret -version=1 team/wifi
bao kv rollback -mount=secret -version=1 team/wifi

rollback copies version 1 into a new version 3, so nothing is lost. bao kv delete hides the current version, and bao kv undelete -versions=3 brings it back. Only bao kv destroy, and bao kv metadata delete for every version at once, removes data for good.

Putting a password in a command saves it in your shell history. For real passwords, use the web UI (step 9), or let OpenBao make the value, as step 10 does.

8. Policies and people

A policy lists the paths a login may use, and what it may do there. Create one for the team, which may read and change everything under team/:

bao policy write team - <<'EOF'
path "secret/data/team/*" {
  capabilities = ["create", "read", "update", "patch", "delete"]
}
path "secret/metadata/team/*" {
  capabilities = ["read", "list"]
}
path "secret/undelete/team/*" {
  capabilities = ["update"]
}
path "secret/metadata/" {
  capabilities = ["list"]
}
EOF

And one for admins, which may do everything, like the root token, but under their own names:

bao policy write admin - <<'EOF'
path "*" {
  capabilities = ["create", "read", "update", "patch", "delete", "list", "sudo"]
}
EOF

People log in with a username and password, through the userpass method. Turn it on, with logins that last eight hours:

bao auth enable userpass
bao auth tune -default-lease-ttl=8h -max-lease-ttl=24h userpass/

Then create an account for each person, with a random password you give them:

PW=$(openssl rand -base64 18); echo "$PW"
printf %s "$PW" | bao write auth/userpass/users/anna token_policies=admin password=-
PW=$(openssl rand -base64 18); echo "$PW"
printf %s "$PW" | bao write auth/userpass/users/erik token_policies=team password=-

Make at least two admins, so that one forgotten password doesn't lock you out. To change a password later, run the same bao write line for that user with a new one.

Now log in as an admin, in place of the root token:

read -rsp 'Password: ' PW; echo
export BAO_TOKEN=$(printf %s "$PW" | bao login -method=userpass -token-only -no-store username=anna password=-)
bao token lookup

display_name shows userpass-anna. Then revoke the root token. Paste it after the first line:

read -rs ROOT
BAO_TOKEN=$ROOT bao token revoke -self
unset ROOT PW

Delete the root token from wherever you kept it in step 5. From now on, everything in the audit log has a person's name on it.

OpenBao can also leave logins to a single sign-on service, such as authentik, over OpenID Connect, with bao auth enable oidc. We did not test that here.

9. The web UI

Open https://bao.example.com/ui/. Under Method, choose Username, and log in with your username and password. You see the stores your policy allows, here secret/.

Open secret/, then team/ and wifi. The values are hidden until you choose the eye next to them. Create new version changes the secret, and the version menu, Version 3 here, has View version history and View diff. Create secret in a folder adds a new one, which is the best way to type a real password, since it does not end up in a shell history.

A team member who opens a secret outside team/ sees "You do not have permission to read this secret." The interface is only in English.

People who prefer the command line can install bao on their own computers, and log in from there:

export BAO_ADDR=https://bao.example.com
bao login -method=userpass username=erik

10. Let an app fetch its password

Apps log in with AppRole, a method for machines: each app gets a role ID, like a username, and a secret ID, like a password. Here a PostgreSQL database for a web shop fetches its password from OpenBao every time it starts, so the password is kept and changed in one place.

As openbao, logged in as an admin, create a policy that lets the shop read only its own secrets, store a random database password, and create the role:

bao policy write shop - <<'EOF'
path "secret/data/apps/shop/*" {
  capabilities = ["read"]
}
EOF
bao kv put -mount=secret apps/shop/db password="$(openssl rand -hex 24)"
bao auth enable approle
bao write auth/approle/role/shop token_policies=shop token_ttl=5m token_max_ttl=10m \
  secret_id_bound_cidrs=203.0.113.10/32,2001:db8::10/128
bao read -field=role_id auth/approle/role/shop/role-id; echo
bao write -f -field=secret_id auth/approle/role/shop/secret-id; echo

The last two lines print the role ID and the secret ID; keep them for a moment. secret_id_bound_cidrs accepts the secret ID only from the shop's server, here this one, over IPv4 and IPv6: a copied secret ID is refused from anywhere else with "unauthorized by CIDR restrictions". If the app runs on another server, use its addresses. The login it gets lasts five minutes.

The app needs jq to read OpenBao's answers. As root, install it, and create a user for the shop:

apt install -y jq
useradd -m -s /bin/bash shop
loginctl enable-linger shop
machinectl shell shop@

As shop, save the two IDs. Run each cat line, paste the ID, press Enter and then Ctrl-D:

mkdir -p ~/.config/bao ~/.config/containers/systemd
chmod 700 ~/.config/bao
cat > ~/.config/bao/role-id
cat > ~/.config/bao/secret-id

Create ~/fetch-db-password, which logs in, fetches the password, stores it as a Podman secret and logs out:

#!/bin/sh
# Log in to OpenBao as the shop, and store its database password as a Podman secret.
set -e
BAO=https://bao.example.com
TOKEN=$(jq -n --arg r "$(cat ~/.config/bao/role-id)" --arg s "$(cat ~/.config/bao/secret-id)" '{role_id: $r, secret_id: $s}' \
  | curl -sf -d @- $BAO/v1/auth/approle/login | jq -r .auth.client_token)
curl -sf -H "X-Vault-Token: $TOKEN" $BAO/v1/secret/data/apps/shop/db \
  | jq -j .data.data.password | podman secret create --replace shop-db-password -
curl -sf -X POST -H "X-Vault-Token: $TOKEN" $BAO/v1/auth/token/revoke-self
chmod 700 ~/fetch-db-password

And ~/.config/containers/systemd/shop-db.container:

[Unit]
Description=The shop's database

[Container]
ContainerName=shop-db
Image=docker.io/library/postgres:17
Volume=shop-db:/var/lib/postgresql/data
Environment=POSTGRES_USER=shop POSTGRES_DB=shop
Secret=shop-db-password,type=env,target=POSTGRES_PASSWORD

[Service]
# Fetch the password from OpenBao first. If that fails, the password from last time is used.
ExecStartPre=-%h/fetch-db-password
Restart=always

[Install]
WantedBy=default.target

Start it:

systemctl --user daemon-reload
systemctl --user start shop-db

The first start downloads PostgreSQL and sets up the database, which takes a few seconds. Then check that the password from OpenBao works:

podman exec shop-db sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$(hostname -i)" -U shop -c "select current_user"'

current_user is shop. The check connects to the container's network address, because PostgreSQL lets connections from 127.0.0.1 inside its own container in without a password. With a wrong password, the same command gets "password authentication failed". The app itself, in a container next to the database, gets the same password with the same Secret= line, such as DB_PASSWORD in the BookStack guide.

The - in front of ExecStartPre matters: after a reboot, OpenBao is sealed until someone unseals it, and the fetch fails. The database then starts with the password it got last time, which Podman keeps in the user's home directory. Without the -, the shop would stay down until OpenBao was unsealed.

To change the password, put a new one in OpenBao, change it in the database too, and restart:

podman exec shop-db psql -U shop -c "ALTER USER shop PASSWORD 'the-new-password'"
systemctl --user restart shop-db

PostgreSQL only reads POSTGRES_PASSWORD when it creates the database, so the ALTER USER is needed. OpenBao can also create database logins by itself, a new one for each app start, with its database secrets engine. We did not test that here.

11. The audit log

The audit log from step 2 records every request and every answer, one JSON line each: the time, the path, who asked, from which address, and whether it was allowed. As openbao:

podman exec openbao tail -n 2 /openbao/logs/audit.log

Secret values and tokens appear as hmac-sha256: hashes, never in plain text, so the log is safe to read and to send elsewhere. The remote_address is the visitor's real address, over IPv4 and IPv6, thanks to the x_forwarded_for_authorized_addrs line.

OpenBao refuses requests it cannot write to the audit log, so a full disk stops it. The log grows by one or two kilobytes per request. To start a new file, move the old one and tell OpenBao to reopen it:

podman exec openbao mv /openbao/logs/audit.log /openbao/logs/audit.log.1
podman kill --signal HUP openbao

12. Unseal automatically (optional)

Instead of people with key shares, OpenBao can get its key from somewhere else at every start, which is called auto-unseal. The choices:

  • Transit: another OpenBao server holds the key, and unseals this one. The key is not on this server, but the other one must be running and unsealed first.
  • KMS: a key service, such as AWS KMS, Azure Key Vault or an HSM, through a plugin. Again the key is not on this server.
  • Static key: a key you give OpenBao yourself, such as a file. Simple, but the key is on the same server as the data.

We tested the static key. Read what it trades away first: whoever can read the key file can decrypt everything, so anyone who gets root on this server, or a full copy of its disk, from a disk snapshot or a careless backup of the home directory, can read all your secrets. With shares in people's heads and password managers, a copied disk is useless. The static key protects only against someone who takes the data without the key, and it makes restarts and automatic updates hands-off. It suits a server where the convenience is worth more than that difference.

As openbao, create the key as a Podman secret:

openssl rand 32 | podman secret create openbao-unseal-key -

Add Secret=openbao-unseal-key under [Container] in openbao.container, and this block at the end of ~/openbao.hcl:

seal "static" {
  current_key_id = "key-1"
  current_key = "file:///run/secrets/openbao-unseal-key"
}

Restart, and move OpenBao to the new seal with two of your shares:

systemctl --user daemon-reload
systemctl --user restart openbao
podman exec -it openbao bao operator unseal -migrate
podman exec -it openbao bao operator unseal -migrate

From now on bao status shows Seal Type static, and OpenBao unseals itself when it starts. The shares are now recovery keys: they no longer unseal anything, but you still need them to make a new root token, so keep them.

To go back, add disabled = "true" to the seal "static" block, restart, run the two unseal -migrate commands again with the shares, and then remove the block, the Secret= line and the secret.

13. Back up

A backup of OpenBao is a snapshot of its integrated storage: every secret, version, user, policy and AppRole. The secrets in it stay encrypted, and can only be read with your key shares, so a snapshot is no use without them, and the shares are kept separately anyway.

As openbao, logged in as an admin, create a policy and a login for backups, which may take snapshots and nothing else:

bao policy write backup - <<'EOF'
path "sys/storage/raft/snapshot" {
  capabilities = ["read"]
}
EOF
umask 077
bao token create -policy=backup -orphan -period=720h -display-name=backup -field=token > ~/.bao-backup-token

Then, to back up:

umask 077
mkdir -p ~/backup
curl -sf -X POST -H "X-Vault-Token: $(cat ~/.bao-backup-token)" http://127.0.0.1:8174/v1/auth/token/renew-self > /dev/null
curl -sf -H "X-Vault-Token: $(cat ~/.bao-backup-token)" -o ~/backup/openbao.snap http://127.0.0.1:8174/v1/sys/storage/raft/snapshot
cp ~/openbao.hcl ~/.config/containers/systemd/openbao.container ~/backup/

The backup login expires if it is not renewed within 30 days, which the first curl line does, so run these lines at least that often, for example daily. Copy ~/backup to another machine, for example with restic. The snapshot only works while OpenBao is unsealed.

To restore on a new server, set up OpenBao as in steps 1 to 4, with the files from the backup. Then, as openbao, initialise the empty instance with a temporary key, which only exists until the restore:

podman exec openbao bao operator init -key-shares=1 -key-threshold=1
podman exec -it openbao bao operator unseal
read -rs BAO_TOKEN && export BAO_TOKEN
curl -sf -X POST -H "X-Vault-Token: $BAO_TOKEN" --data-binary @$HOME/backup/openbao.snap http://127.0.0.1:8174/v1/sys/storage/raft/snapshot-force
unset BAO_TOKEN

Unseal it with the temporary key, and paste the temporary root token at read. After the restore, OpenBao is sealed again, now with your old key: unseal it with two of your own shares as in step 6, and log in with your usual accounts. The temporary key and token are no longer valid.

14. Update

The version is fixed in openbao.container, because every update restarts OpenBao, and every restart seals it. Update when someone can unseal it right after. Read the release notes on OpenBao's GitHub first; security fixes are marked there.

As openbao, take a backup as in step 13, then change the version in the Image= line, for example to quay.io/openbao/openbao:2.7.2, and restart:

systemctl --user daemon-reload
systemctl --user restart openbao

Then unseal it as in step 6. OpenBao updates its data by itself when it starts. bao status shows the new version.

With a static key from step 12, OpenBao unseals itself after updates. You could then add AutoUpdate=registry with a tag such as 2.7, which follows the 2.7 releases, and switch on podman-auto-update.timer as in the Podman guide.

Troubleshooting

Requests fail with "Vault is sealed", or the site shows the unseal page. OpenBao has restarted, after a reboot or an update. Unseal it as in step 6.

Every audit log entry has the server's own address. x_forwarded_for_authorized_addrs in ~/openbao.hcl is missing or wrong. It must be the server's IPv4 address. Fix it, restart OpenBao and unseal it.

"cannot enable audit device via API". OpenBao only takes audit devices from its configuration file. Add an audit block as in step 2, and restart.

The app's fetch fails with "unauthorized by CIDR restrictions". The app connects from an address that secret_id_bound_cidrs does not list. Over IPv6, the server uses its IPv6 address, so list both. Fix it with the same bao write auth/approle/role/shop line, which keeps the role ID, and make a new secret ID.

bao says "permission denied" for everything. BAO_TOKEN is empty or expired, after eight hours for a person. Log in again as in step 8.

Every admin has lost their password. The share holders can make a new root token together. This needs an older way that OpenBao switches off by default, since anyone could otherwise start or cancel it. Add disable_unauthed_generate_root_endpoints = false to the listener block in ~/openbao.hcl, restart OpenBao and unseal it. Then, as openbao:

OTP=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 26)
NONCE=$(curl -s -X PUT -d "{\"otp\":\"$OTP\"}" http://127.0.0.1:8174/v1/sys/generate-root/attempt | jq -r .nonce)
read -rs KEY; curl -s -X PUT -d "{\"key\":\"$KEY\",\"nonce\":\"$NONCE\"}" http://127.0.0.1:8174/v1/sys/generate-root/update | jq -r .encoded_token
read -rs KEY; ENC=$(curl -s -X PUT -d "{\"key\":\"$KEY\",\"nonce\":\"$NONCE\"}" http://127.0.0.1:8174/v1/sys/generate-root/update | jq -r .encoded_token)
python3 -c 'import base64,sys; e=base64.b64decode(sys.argv[1]+"=="); print(bytes(a^b for a,b in zip(e,sys.argv[2].encode())).decode())' "$ENC" "$OTP"

At each read, paste one share. The last line prints a new root token. Use it to give someone a new password as in step 8, revoke it, then remove the line from ~/openbao.hcl again, and restart. jq is installed in step 10. The 26 is the length of an OpenBao token, such as the root token from step 5.

Run it on your own server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy

€8.99/mo

vCPU
2
Memory
8 GB
Storage
60 GB
Transfer
10 TB
Performance
SSD · RAID 1
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides