Self-Host Vaultwarden on a VPS with Docker
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.
TL;DR
- Install Docker and Docker Compose on a fresh VPS
- Point a subdomain like
vault.example.comto your server - Run Vaultwarden and Caddy behind a single
docker-compose.yml - Create your account, then disable open signups
- Enable the admin panel with a hashed token
- Schedule a daily backup of the
data/directory
Total time: about 15 minutes.
What You Need
- A VPS with at least 512 MB RAM (1 GB recommended) running Ubuntu 22.04 or 24.04
- A domain name you can add DNS records to
- Ports
80and443open to the internet (required by Let's Encrypt) - Root or sudo access
If you don't have a VPS yet, any small Linux plan works. Vaultwarden idles at around 30 MB of RAM.
Step 1: Point a Subdomain at Your VPS
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.
Step 2: Install Docker and Docker Compose
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
Step 3: Open the Firewall
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.
Step 4: Create the Project Directory
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.
Step 5: Generate an Admin Token
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.
Step 6: Write the Compose File
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:
- Keep
SIGNUPS_ALLOWED=truefor now. You will create your own account, then flip it off. - The
ADMIN_TOKENvalue must be the full Argon2 hash from the previous step, with the leading$argon2id$.... Wrap it in double quotes. - Don't expose port
8080on the host. Caddy reaches it over the internal Docker network.
Step 7: Write the Caddyfile
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.
Step 8: Start the Stack
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.
Step 9: Lock Down Signups
Bots scan the internet for open Vaultwarden instances. Flip signups off now that you have your account:
- Edit
/opt/vaultwarden/docker-compose.yml - Change
SIGNUPS_ALLOWED: "true"toSIGNUPS_ALLOWED: "false" - Restart the container:
sudo docker compose up -d
To add family or teammates later, use the admin panel to send invitations instead.
Step 10: Use the Admin Panel
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:
- Invite new users
- Configure SMTP so password-reset and invite emails work
- Monitor active users and sessions
- Tweak feature flags (emergency access, organizations, sends, etc.)
Step 11: Install Clients
Every official Bitwarden client supports self-hosted servers.
Before signing in on a client, change the server URL:
- Desktop / Browser: click the server icon on the login screen, select
Self-hosted, setServer URLtohttps://vault.example.com. - Mobile (iOS/Android): on the first screen, tap the gear icon, choose
Self-hosted environment, fill in the same URL. - CLI:
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://.
Step 12: Automate Backups
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.
Optional: Put Everything Behind a VPN
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.
Troubleshooting
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.
Going Further
- Enforce 2FA on every account from the admin panel.
- Add Fail2ban to throttle brute-force login attempts.
- Switch to PostgreSQL if your vault grows past a few hundred users.
- Consider a dedicated backup disk - losing the
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.