Why Self-Host Your Passwords
In December 2022, LastPass disclosed that attackers stole encrypted password vaults for every customer. The encryption is only as strong as each user's master password — and many users had weak ones. Those vaults are being cracked today.
This isn't just a LastPass problem. Any cloud password manager is a target because one breach exposes millions of vaults. When you self-host:
- Your vault exists only on your hardware. An attacker has to breach YOUR server, not a company with millions of users.
- You control the encryption, the storage, the backups, and the access.
- The Bitwarden apps and browser extensions work identically — same UX, same autofill, same TOTP support.
- Vaultwarden uses minimal resources: ~50MB RAM, negligible CPU. It runs happily on the smallest VM you have.
Vaultwarden is NOT official Bitwarden. It's a community-built, Rust-based implementation of the Bitwarden API. It's lighter, faster, and gives you premium features (TOTP, file attachments, org support) for free. The Bitwarden client apps don't know the difference.
What We're Building
By the end of this guide:
1. Vaultwarden running in Docker on your LAN
2. HTTPS access through your reverse proxy
3. Your admin account created
4. Public registration disabled (so nobody else can create accounts)
5. Admin panel enabled for server management
6. Bitwarden browser extension and/or mobile app connected
7. Two-factor authentication enabled on your vault
8. A backup strategy for your vault data
Step 1: Create the Project Directory
Create a dedicated directory for Vaultwarden:
mkdir -p ~/docker/vaultwarden && cd ~/docker/vaultwardenStep 2: Generate an Admin Token
The admin token gives you access to the Vaultwarden admin panel at /admin. Generate a strong random token. SAVE THIS — you'll need it to access the admin panel:
This generates a 48-character random string:
openssl rand -base64 48 | tr -d '\n' | tee ~/docker/vaultwarden/admin-token.txt && echo ''Important: Secure the Token
The admin token is stored in ~/docker/vaultwarden/admin-token.txt for reference. Move it to your password manager as soon as you have Vaultwarden running. Anyone with this token has full admin access to your vault server.
Do NOT commit this file to git. Do NOT share it.
Step 3: Write the Docker Compose File
Create the compose file. Replace PASTE_YOUR_ADMIN_TOKEN_HERE with the token from Step 2:
The DOMAIN variable tells Vaultwarden its public URL — needed for email links and WebSocket connections:
cat > ~/docker/vaultwarden/docker-compose.yml << 'COMPOSE'
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
- DOMAIN=https://vault.yourdomain.com # CHANGE THIS to your actual domain
- ADMIN_TOKEN=PASTE_YOUR_ADMIN_TOKEN_HERE # CHANGE THIS to token from Step 2
- SIGNUPS_ALLOWED=true # We'll disable this after creating our account
- WEBSOCKET_ENABLED=true
- LOG_LEVEL=info
volumes:
- vw-data:/data
ports:
- "8080:80"
- "3012:3012" # WebSocket for live sync
networks:
- vw-net
networks:
vw-net:
driver: bridge
volumes:
vw-data:
COMPOSEStep 3b: Update the Compose File
You MUST change two values before starting:
1. DOMAIN — set to your actual HTTPS URL (e.g., https://vault.homelab.local or https://vault.yourdomain.com)
2. ADMIN_TOKEN — paste the token from Step 2
Open the file in your editor and make the changes:
nano ~/docker/vaultwarden/docker-compose.yml
Or use sed to replace the admin token:
TOKEN=$(cat ~/docker/vaultwarden/admin-token.txt)
sed -i "s/PASTE_YOUR_ADMIN_TOKEN_HERE/$TOKEN/" ~/docker/vaultwarden/docker-compose.yml
Step 4: Add Vaultwarden to Your Reverse Proxy
Before starting Vaultwarden, add it to your Caddy config. Vaultwarden REQUIRES HTTPS — the browser extensions refuse to connect over plain HTTP.
Add this to your Caddyfile (adjust the domain and IP):
For internal-only (self-signed cert):
vault.homelab.local {
tls internal
reverse_proxy 192.168.1.x:8080
# WebSocket support for live sync
reverse_proxy /notifications/hub 192.168.1.x:3012
}
For public domain (automatic Let's Encrypt cert):
vault.yourdomain.com {
reverse_proxy 192.168.1.x:8080
reverse_proxy /notifications/hub 192.168.1.x:3012
}
Replace 192.168.1.x with the IP of the machine running Vaultwarden.
Reload Caddy after editing:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
Step 5: Start Vaultwarden
Bring up the container:
cd ~/docker/vaultwarden && docker compose up -dStep 5b: Verify It's Running
Check the container and test the endpoint:
docker ps --filter name=vaultwarden --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
echo '---'
curl -kI https://vault.homelab.local 2>/dev/null | head -5 || curl -I http://localhost:8080 2>/dev/null | head -5Expected Output
Container should show "Up" status.
curl should return HTTP/2 200 (through the proxy) or HTTP/1.1 200 (direct).
If the container is restarting:
docker logs vaultwarden
Common errors:
- Port 8080 in use: change the host port in docker-compose.yml (e.g., 8081:80)
- "Invalid admin token": check for extra whitespace or newlines in the token
- Database errors: the data volume might have bad permissions. Check: docker exec vaultwarden ls -la /data/
Step 6: Create Your Account
Open your browser and navigate to your Vaultwarden URL:
https://vault.yourdomain.com (or https://vault.homelab.local)
Click "Create Account" and fill in:
- Email: your actual email (used for vault recovery)
- Name: whatever you want
- Master Password: make this STRONG. Minimum 16 characters, mix of everything. This is the key to your entire digital life. Use a passphrase if you want something memorable: correct-horse-battery-staple-style but longer.
- Master Password Hint: leave blank or use something only you'd understand
After creating your account, log in to verify it works.
Step 7: Disable Public Registration
Now that your account exists, disable signups so nobody else can register. Update the environment variable in docker-compose.yml:
cd ~/docker/vaultwarden && sed -i 's/SIGNUPS_ALLOWED=true/SIGNUPS_ALLOWED=false/' docker-compose.yml && docker compose up -dValidate: Registration Is Disabled
Open the Vaultwarden URL in a private/incognito browser window and try to click "Create Account". You should see a message that registration is disabled.
If registration is still available, check the compose file:
grep SIGNUPS_ALLOWED ~/docker/vaultwarden/docker-compose.yml
It should show: SIGNUPS_ALLOWED=false
If it does and registration still works, recreate the container:
docker compose down && docker compose up -d
Step 8: Access the Admin Panel
The admin panel lets you manage users, view diagnostics, and configure server settings:
https://vault.yourdomain.com/admin
Enter the admin token from Step 2. If you lost it:
cat ~/docker/vaultwarden/admin-token.txt
The admin panel shows:
- All registered users
- Server diagnostics
- SMTP configuration (for email notifications)
- Organization settings
- Backup status
Step 9: Connect Bitwarden Apps
Vaultwarden is API-compatible with Bitwarden. Use the official Bitwarden apps — they don't know the difference.
Browser Extension (Chrome, Firefox, Edge, Safari):
1. Install the Bitwarden extension from your browser's extension store
2. Before logging in, click the gear icon (Settings)
3. Under "Self-hosted Environment", set Server URL to your Vaultwarden URL (e.g., https://vault.yourdomain.com)
4. Save and log in with the account you created
Mobile App (iOS, Android):
1. Install Bitwarden from the App Store / Play Store
2. On the login screen, tap the gear icon or "Self-hosted"
3. Enter your Server URL
4. Log in
Desktop App (Windows, macOS, Linux):
1. Download from bitwarden.com/download
2. Before logging in, go to Settings > Self-hosted
3. Enter your Server URL
4. Log in
IMPORTANT: If you're using tls internal (self-signed certs), mobile apps may refuse to connect. You'll need to install the Caddy root CA certificate on your phone (see the reverse proxy guide).
Step 10: Enable Two-Factor Authentication
Your vault is only as secure as your login. Enable 2FA immediately.
1. Log into the web vault (https://vault.yourdomain.com)
2. Go to Settings > Security > Two-step Login
3. Choose Authenticator App (TOTP)
4. Scan the QR code with your authenticator app (Google Authenticator, Authy, etc.)
5. Enter the 6-digit code to verify
6. SAVE the recovery code somewhere safe — printed paper in a physical safe is ideal
From now on, logging into your vault requires your master password AND a 6-digit code from your authenticator app. Even if someone gets your master password, they can't access your vault.
Step 11: Back Up Your Vault
The /data volume contains your entire vault — all passwords, TOTP secrets, file attachments, and the SQLite database. Losing this means losing everything.
Export method (manual, do this immediately):
1. Log into the web vault
2. Go to Tools > Export Vault
3. Choose JSON format (encrypted)
4. Enter your master password
5. Save the file to a USB drive or secure storage
Server-side backup (automated):
# Back up the Docker volume daily
docker run --rm -v vw-data:/data -v ~/backups:/backup \
alpine tar czf /backup/vaultwarden-$(date +%Y%m%d).tar.gz -C /data .
Add this to cron for nightly backups:
(crontab -l 2>/dev/null; echo '30 2 * * * docker run --rm -v vw-data:/data -v ~/backups:/backup alpine tar czf /backup/vaultwarden-$(date +\%Y\%m\%d).tar.gz -C /data .') | crontab -
IMPORTANT: Store backups on a DIFFERENT machine. If the host dies and your backups are on the same disk, you've lost everything. rsync them to a NAS, another server, or an offsite location.
How to Undo Everything
To remove Vaultwarden:
1. FIRST: Export your vault (Tools > Export) so you don't lose your passwords
2. Stop and remove the container:
cd ~/docker/vaultwarden && docker compose down
3. Remove the data volume (THIS DELETES YOUR VAULT):
docker volume rm vaultwarden_vw-data
4. Remove the Caddyfile entry and reload Caddy
5. Reset Bitwarden apps: Settings > Log Out, then clear the Self-hosted URL
To migrate to a different host:
1. Export the vault AND back up the data volume
2. Set up Vaultwarden on the new host
3. Restore the data volume from backup
4. Update DNS / Caddy to point to the new host
5. No changes needed in the apps — they follow the domain name