👍 What we like
- ✓Extremely lightweight resource footprint suitable for small VPS
- ✓Full compatibility with official Bitwarden applications and extensions
- ✓Self-hosted solution eliminates third-party cloud dependency
- ✓Automated HTTPS via Caddy reverse proxy with Let's Encrypt
👎 What to watch
- ✕Requires manual generation of Argon2 hashed admin tokens
- ✕Complex initial setup involving Docker, DNS, and firewall configuration
- ✕Admin token requires careful escaping of special characters in env files
📑 Contents ▾
- 01 Prerequisites
- 02 Step 1: Install Docker and Docker Compose
- 03 Step 2: Configure DNS
- 04 Step 3: Generate the Admin Token
- 05 Step 4: Create the Shared Docker Network
- 06 Step 5: The Vaultwarden docker-compose.yml
- 07 Step 6: The Caddyfile for Automatic HTTPS
- 08 Step 7: Start the Services
- 09 Step 8: Create Your Account and Invite Others
- 10 Step 9: Enable Two-Factor Authentication
- 11 Step 10: Automatic Encrypted Backups
- 12 Final Verification
- 13 Security: Hardening Not to Be Neglected
- 14 FAQ
- · Is Vaultwarden really compatible with Bitwarden apps?
- · Why are my signups blocked?
- · The /admin panel returns an error or doesn’t accept my token, why?
- · Let’s Encrypt fails to issue the certificate, what should I do?
- · Can I host Vaultwarden on my NAS instead of a VPS?
- · How do I migrate my passwords from another manager?
- 21 Related Topics
Do you want your own password manager, on your server, with no subscription and without entrusting your secrets to a third-party cloud? Vaultwarden is the ideal answer: it is a lightweight Rust reimplementation of the Bitwarden server, compatible with all official Bitwarden applications and extensions. It runs on less than 100 MB of RAM, making it deployable on the smallest VPS.
But a password manager is one of the most sensitive services you can host: it must enforce HTTPS, have a locked admin panel, and reliable backups. In this tutorial, we deploy Vaultwarden via Docker, behind a Caddy reverse proxy that handles HTTPS and automatic Let’s Encrypt certificates. Everything is in versioned, reproducible files, hardened for production.
Prerequisites
-
A secure VPS running Ubuntu 24.04 (or Debian 12). If you haven’t done so yet, follow our guide to install and secure an Ubuntu VPS first.
-
Docker and Docker Compose installed (installation command in step 1).
-
A domain name for which you control the DNS. We will use
vault.exemple.fras an example. -
Ports 80 and 443 open on your UFW firewall: essential for Let’s Encrypt validation and HTTPS traffic.
-
A few minutes and some rigor: this is a critical service, so don’t rush it.
Step 1: Install Docker and Docker Compose
If Docker is not present, install it from the official repository (Ubuntu repository versions are often outdated):
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
Add your user to the docker group (log out and back in afterwards to apply changes):
sudo usermod -aG docker $USER
Verify:
docker --version && docker compose version
Step 2: Configure DNS
Caddy needs your domain to point to the VPS IP to issue the certificate. At your DNS provider, create an A record (and AAAA if IPv6):
| Type | Name | Value |
| :--- | :--- | :--- |
| A | vault.exemple.fr | 203.0.113.10 |
Wait for propagation, then verify from your machine:
dig +short vault.exemple.fr
The command should return your VPS IP. Until it does, Let’s Encrypt will fail to issue the certificate.
Step 3: Generate the Admin Token
Vaultwarden exposes an admin panel at /admin, protected by a token. This token must be an Argon2 hash, not a plain string. Generate it directly using the Vaultwarden image:
docker run --rm -it vaultwarden/server:latest /vaultwarden hash
Enter a long, unique administrator password when prompted. It will display a string starting with $argon2id$.... Copy it carefully: this is what we will place in the configuration. Also note the corresponding plain-text password in your safe: this is the one you will type to access /admin.
Common Pitfall: If you paste the Argon2 hash into a
.envfile, the$sign must be escaped ($$) because Docker Compose interprets it as a variable. We handle this in the next step.
Step 4: Create the Shared Docker Network
For Caddy to reach the Vaultwarden container by its name, both must share a dedicated Docker network. Create it once:
docker network create web
If you are already following our Caddy reverse proxy tutorial, this network might already exist: the command will return an error which is harmless, just ignore it.
Step 5: The Vaultwarden docker-compose.yml
Create a working directory and the composition file:
mkdir -p ~/vaultwarden && cd ~/vaultwarden
nano docker-compose.yml
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
# Disable public signups: no one can create an account
SIGNUPS_ALLOWED: "false"
# Allow manual user invitations from /admin
INVITATIONS_ALLOWED: "true"
# Public service URL (HTTPS required)
DOMAIN: "https://vault.exemple.fr"
# Argon2 hash of the admin token (note $$ instead of $)
ADMIN_TOKEN: "${ADMIN_TOKEN}"
# WebSockets for real-time client sync
ENABLE_WEBSOCKET: "true"
volumes:
- ./vw-data:/data
networks:
- web
networks:
web:
external: true
Then create the .env file containing the token, by doubling every $ in the Argon2 hash:
nano .env
ADMIN_TOKEN='$$argon2id$$v=19$$m=65540,t=3,p=4$$YOUR_HASH_HERE'
Replace with the hash generated in step 3, replacing each $ with $$. Single quotes protect the string.
Note the absence of a ports: section: Vaultwarden is never exposed directly to the Internet. It is only accessible through Caddy, via the internal web network. This is the fundamental security principle of this deployment.
Step 6: The Caddyfile for Automatic HTTPS
If you already have Caddy in place (see our dedicated tutorial), simply add the site block. Otherwise, create a Caddy directory:
mkdir -p ~/caddy && cd ~/caddy
nano Caddyfile
{
# Email for Let's Encrypt notifications
email admin@exemple.fr
}
vault.exemple.fr {
# Let's Encrypt certificate and automatic HTTP -> HTTPS redirect
encode gzip zstd
# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
# Route all traffic to the Vaultwarden container
reverse_proxy vaultwarden:80
}
Then the Caddy docker-compose.yml in ~/caddy:
nano docker-compose.yml
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- web
volumes:
caddy_data:
caddy_config:
networks:
web:
external: true
The caddy_data volume is critical: it preserves your certificates. Without it, you would request a certificate on every restart and quickly hit Let’s Encrypt rate limits.
Step 7: Start the Services
Start Vaultwarden first, then Caddy:
cd ~/vaultwarden && docker compose up -d
cd ~/caddy && docker compose up -d
Follow Caddy logs to see the certificate issuance in real-time:
docker compose logs -f caddy
You should see a line like certificate obtained successfully for vault.exemple.fr. Then visit https://vault.exemple.fr: the Vaultwarden homepage will display with a valid padlock.
Step 8: Create Your Account and Invite Others
Since SIGNUPS_ALLOWED is set to false, no one can freely register. To create your first account, you have two options:
Option A (recommended): via the admin panel. Go to https://vault.exemple.fr/admin, enter the admin password (step 3), then use “Invite User” to invite yourself, as well as your family or team. Everyone receives a registration link.
Option B: temporarily open signups. Set SIGNUPS_ALLOWED to "true", restart, create accounts, then set it back to "false". Less clean, avoid this.
Once your account is created, install the Bitwarden browser extension or mobile app, and in the settings, configure the server URL to https://vault.exemple.fr before logging in. Everything else works exactly like Bitwarden.
Step 9: Enable Two-Factor Authentication
This is non-negotiable for a password vault. Once logged into the web interface:
-
Go to Settings → Security → Two-step login.
-
Enable TOTP (authenticator app) at a minimum, or ideally a WebAuthn/FIDO2 hardware key (YubiKey).
-
Save your recovery code in a secure location outside the vault.
Without 2FA, your only defense is the master password. With it, even a compromised password is not enough for an attacker.
Step 10: Automatic Encrypted Backups
A vault without backups means all your access disappears at the first disk failure. Vaultwarden stores everything in ~/vaultwarden/vw-data (SQLite database, attachments, keys). Here is a daily backup script:
nano ~/vaultwarden/backup.sh
#!/bin/bash
set -euo pipefail
DATA_DIR="$HOME/vaultwarden/vw-data"
BACKUP_DIR="$HOME/vaultwarden/backups"
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
# Consistent backup of the SQLite database (native sqlite3 command in the container)
docker compose -f "$HOME/vaultwarden/docker-compose.yml" exec -T vaultwarden \
/bin/sh -c "sqlite3 /data/db.sqlite3 '.backup /data/db-backup.sqlite3'"
# Archive all data
tar -czf "$BACKUP_DIR/vaultwarden-$STAMP.tar.gz" -C "$DATA_DIR" .
# Keep the last 14 days
find "$BACKUP_DIR" -name 'vaultwarden-*.tar.gz' -mtime +14 -delete
Make it executable and schedule it via cron (every day at 3 AM):
chmod +x ~/vaultwarden/backup.sh
(crontab -l 2>/dev/null; echo "0 3 * * * $HOME/vaultwarden/backup.sh") | crontab -
Important: A backup on the same server does not protect against fire, theft, or ransomware. Send these archives off-site and encrypted: our guide automatic backup with Restic and Backblaze shows how to replicate these backups to remote storage.
Final Verification
Check that everything is in place:
# Containers are running
docker ps --filter "name=vaultwarden" --filter "name=caddy"
# HTTP redirects to HTTPS (code 308)
curl -sI http://vault.exemple.fr | head -1
# HTTPS responds and the certificate is valid
curl -sI https://vault.exemple.fr | head -1
# Admin panel is NOT accessible without token (should ask for auth)
curl -sI https://vault.exemple.fr/admin | head -1
For a complete TLS audit, test your domain on SSL Labs: you should aim for A or A+ thanks to HSTS headers and Caddy’s modern protocols.
Security: Hardening Not to Be Neglected
Vaultwarden hosts your most sensitive secrets. Beyond HTTPS, apply these measures:
-
Very strong master password. This is the only key to your entire vault: a long, unique passphrase (minimum 4-5 random words). No encryption saves a vault protected by a weak password.
-
Locked /admin panel. The Argon2 token already protects it. To go further, restrict access to
/adminby IP in Caddy, or disable it (DISABLE_ADMIN_TOKEN: "true") once configuration is complete. -
Strict UFW firewall. Open only ports 80, 443, and SSH. Vaultwarden’s internal port 80 must never be exposed.
-
Fail2ban. Vaultwarden logs login attempts. Coupled with Fail2ban, you ban IPs attempting to brute-force an account. Enable logging (
LOG_FILE) and create a dedicated rule. -
Regular updates. Update the image (
docker compose pull && docker compose up -d) after every backup, never before. A failed migration without a backup can corrupt the database. -
Mandatory 2FA for all accounts (see step 9).
To go further on protecting an exposed VPS, consult our best practices in install and secure an Ubuntu VPS.
FAQ
Is Vaultwarden really compatible with Bitwarden apps?
Yes, completely. Vaultwarden implements the Bitwarden API, so the browser extension, iOS/Android mobile apps, desktop clients, and official CLI work without modification. You just need to configure the server URL to your Vaultwarden domain before logging in. The experience is identical to Bitwarden Cloud.
Why are my signups blocked?
This is intentional: SIGNUPS_ALLOWED: "false" prevents anyone from creating an account on your exposed instance. To add users, go through the /admin panel and use the invitation function. This is the recommended secure configuration for a public service.
The /admin panel returns an error or doesn’t accept my token, why?
In 90% of cases, it’s the Argon2 hash poorly escaped in the .env file: every $ in the hash must be doubled to $$ in Docker Compose. Also verify that you are entering the plain-text password (not the hash) in the /admin form. Regenerate the hash in step 3 if necessary.
Let’s Encrypt fails to issue the certificate, what should I do?
It’s almost always a DNS issue (the domain hasn’t pointed to the VPS yet) or a firewall issue (port 80 closed). Check dig +short vault.exemple.fr and sudo ufw status. Caddy logs (docker compose logs caddy) indicate the exact cause of the ACME rejection. During debugging, use Let’s Encrypt’s staging environment to avoid hitting rate limits.
Can I host Vaultwarden on my NAS instead of a VPS?
Yes, perfectly. Synology (Container Manager), QNAP (Container Station), and TrueNAS support Docker. The logic is identical: Vaultwarden container behind an HTTPS reverse proxy, never exposed directly. See our comparison Synology vs TrueNAS vs QNAP to choose the platform.
How do I migrate my passwords from another manager?
Export your data from your current manager (LastPass, 1Password, Bitwarden Cloud, KeePass…) in CSV or JSON format, then import them via the Bitwarden client connected to your Vaultwarden (Tools → Import Data). Then delete the unencrypted export file: it is a plain-text copy of all your secrets.
Related Topics
You now have a sovereign password manager, over HTTPS, backed up and hardened. It is one of the most useful services a homelab can offer. To follow Vaultwarden updates, security vulnerabilities, and self-hosting best practices, subscribe to our Telegram watch bot.