All articles
TutorialsApr 17, 2026 · 13 min read

Self-Host Vaultwarden on a VPS with Docker

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.

Before switching, export your existing vault from Bitwarden or wherever you store passwords today. You can import it into Vaultwarden after signup.

TL;DR

  • Install Docker and Docker Compose on a fresh VPS
  • Point a subdomain like vault.example.com to 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 80 and 443 open 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=true for now. You will create your own account, then flip it off.
  • The ADMIN_TOKEN value must be the full Argon2 hash from the previous step, with the leading $argon2id$.... Wrap it in double quotes.
  • Don't expose port 8080 on 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:

  1. Edit /opt/vaultwarden/docker-compose.yml
  2. Change SIGNUPS_ALLOWED: "true" to SIGNUPS_ALLOWED: "false"
  3. 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.)
Treat the admin password like a root password. Store it in your vault immediately after first login.

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, set Server URL to https://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.