A password manager is the one app you really don't want a third party to lose. Self-hosting gives you full control over where your vault lives, who can reach it, and how it's backed up.
Vaultwarden is a lightweight Bitwarden-compatible server written in Rust. It works with every official Bitwarden client (desktop, mobile, browser, CLI), uses a fraction of the resources of the official server, and runs happily on a cheap VPS.
This guide walks through a production-ready setup: Docker, Caddy for automatic HTTPS, admin panel, and automated backups.
vault.example.com to your serverdocker-compose.ymldata/ directoryTotal time: about 15 minutes.
80 and 443 open to the internet (required by Let's Encrypt)If you don't have a VPS yet, any small Linux plan works. Vaultwarden idles at around 30 MB of RAM.
In your DNS provider, create an A record:
vault.example.com → YOUR_VPS_IPV4
If you use IPv6, add an AAAA record too. Wait a minute or two, then verify:
dig +short vault.example.com
The output should match your VPS IP. DNS needs to resolve before Caddy can issue an SSL certificate.
On a fresh Ubuntu box:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Verify:
docker --version
docker compose version
If you use UFW:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Caddy needs 80 for the ACME HTTP challenge and 443 for HTTPS.
sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwarden
sudo mkdir -p data caddy-data caddy-config
All persistent state lives under /opt/vaultwarden. This is the one path you need to back up.
The admin panel lets you manage users, invitations, and settings. It's gated by a hashed token.
docker run --rm -it vaultwarden/server \
/vaultwarden hash
Enter a long random password when prompted (a 32+ character string from your existing password manager works well). Copy the full $argon2id$... string that is printed. You'll paste it into the compose file in the next step.
Create /opt/vaultwarden/docker-compose.yml:
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://vault.example.com"
SIGNUPS_ALLOWED: "true"
ADMIN_TOKEN: "$argon2id$v=19$m=65540,t=3,p=4$PASTE_YOUR_HASH_HERE"
ROCKET_PORT: "8080"
volumes:
- ./data:/data
networks:
- vaultnet
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
networks:
- vaultnet
networks:
vaultnet:
A few notes:
SIGNUPS_ALLOWED=true for now. You will create your own account, then flip it off.ADMIN_TOKEN value must be the full Argon2 hash from the previous step, with the leading $argon2id$.... Wrap it in double quotes.8080 on the host. Caddy reaches it over the internal Docker network.Create /opt/vaultwarden/Caddyfile:
vault.example.com {
encode zstd gzip
reverse_proxy vaultwarden:8080
}
Caddy will automatically request a Let's Encrypt certificate for vault.example.com on first boot. No configuration needed.
cd /opt/vaultwarden
sudo docker compose up -d
sudo docker compose logs -f
Watch the Caddy logs until you see the certificate being issued. Then open https://vault.example.com in a browser. You should land on the Bitwarden web vault login page.
Click Create Account and sign up. This first account will be the owner.
Bots scan the internet for open Vaultwarden instances. Flip signups off now that you have your account:
/opt/vaultwarden/docker-compose.ymlSIGNUPS_ALLOWED: "true" to SIGNUPS_ALLOWED: "false"sudo docker compose up -d
To add family or teammates later, use the admin panel to send invitations instead.
Visit https://vault.example.com/admin, paste the plaintext admin password you generated in Step 5 (not the hash), and you will see the admin UI.
From there you can:
Every official Bitwarden client supports self-hosted servers.
Before signing in on a client, change the server URL:
Self-hosted, set Server URL to https://vault.example.com.Self-hosted environment, fill in the same URL.bw config server https://vault.example.com
bw login [email protected]
If a client refuses to connect, the most common cause is a wrong DOMAIN value in docker-compose.yml. It must match exactly, including https://.
The entire state lives in /opt/vaultwarden/data. The SQLite database is hot-copy-safe using the Vaultwarden backup command.
Create /usr/local/bin/vaultwarden-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/vaultwarden"
DATE="$(date +%F)"
mkdir -p "$BACKUP_DIR"
docker exec vaultwarden /usr/bin/sqlite3 /data/db.sqlite3 ".backup '/data/db.backup.sqlite3'"
tar -czf "$BACKUP_DIR/vaultwarden-$DATE.tar.gz" -C /opt/vaultwarden data
find "$BACKUP_DIR" -name "vaultwarden-*.tar.gz" -mtime +14 -delete
Make it executable and schedule a daily run:
sudo chmod +x /usr/local/bin/vaultwarden-backup.sh
echo "15 3 * * * root /usr/local/bin/vaultwarden-backup.sh" | \
sudo tee /etc/cron.d/vaultwarden-backup
For off-site safety, sync the backup directory to object storage (S3, Backblaze B2, etc.) on the same schedule.
Publishing a password manager to the open internet is fine when it's patched and signups are off, but private-network-only is even safer. Pair this with our Tailscale guide to make vault.example.com reachable only over your tailnet.
Caddy cannot issue a certificate. DNS hasn't propagated or ports 80/443 are blocked. Double-check with dig and your provider firewall.
Clients log in but syncing fails. The DOMAIN environment variable doesn't match the URL the client uses. It must include https:// and the exact hostname.
WebSocket notifications don't work. Recent Vaultwarden versions handle WebSockets on the same port as HTTP, so the Caddyfile above is enough. If you forked an older config, remove any separate /notifications/hub block.
Admin panel won't accept the token. You pasted the hash into the login form. The login prompt expects the plaintext password you typed during /vaultwarden hash. The compose file stores the hash.
data/ directory means losing every password.That's it. A self-hosted vault on a VPS gives you the convenience of Bitwarden without handing the encrypted blob to anyone else.
Need a VPS that's ready for Docker workloads like this? Our Linux plans include fast NVMe storage and IPv6 out of the box. See the options.