Overview
Password reuse remains one of the biggest security risks. A password manager solves this, and self-hosting gives you complete control over your sensitive data.
Who Should Use This Guide
- Privacy-conscious individuals wanting data ownership
- Small teams needing shared credential management
- Home lab enthusiasts self-hosting services
- Organizations with strict data residency requirements
Why Self-Host
| Benefit | Description |
|---|---|
| Data Ownership | Passwords never leave your infrastructure |
| No Subscription | One-time setup, ongoing value |
| Full Control | Customize to your specific needs |
| Learning | Understand password manager internals |
Vaultwarden Overview
Vaultwarden is an unofficial Bitwarden server implementation written in Rust. It's lightweight and compatible with all official Bitwarden clients.
| Feature | Support |
|---|---|
| Bitwarden Apps | Full compatibility |
| Organizations | Supported |
| Two-Factor Auth | TOTP, WebAuthn, Email |
| Emergency Access | Supported |
| Send | Secure file/text sharing |
Requirements
System Requirements
| Component | Requirement |
|---|---|
| Operating System | Linux (Docker-capable) |
| RAM | 512MB minimum |
| Storage | 1GB+ for vault data |
| Network | HTTPS access (SSL required) |
Prerequisites
| Prerequisite | Purpose |
|---|---|
| Docker + Docker Compose | Container runtime |
| Domain name | SSL certificate (optional for LAN) |
| Reverse proxy | SSL termination, security headers |
Process
Step 1: Create Project Directory
Set up the directory structure for Vaultwarden.
Commands:
mkdir -p /opt/vaultwarden
cd /opt/vaultwardenVerification:
pwdExpected Output: /opt/vaultwarden
Step 2: Generate Admin Token
Create a secure token for administrative access.
Generate Token:
openssl rand -base64 48Create Environment File:
echo "ADMIN_TOKEN=<your-generated-token>" > .env
chmod 600 .envVerification:
ls -la .env
cat .envExpected Result: .env file exists with 600 permissions; contains ADMIN_TOKEN.
Step 3: Create Docker Compose Configuration
Define the Vaultwarden container setup.
Create docker-compose.yml:
version: '3.8'
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
volumes:
- ./data:/data
environment:
- DOMAIN=https://<your-domain>
- SIGNUPS_ALLOWED=false
- INVITATIONS_ALLOWED=true
- ADMIN_TOKEN=${ADMIN_TOKEN}
- WEBSOCKET_ENABLED=true
- LOG_FILE=/data/vaultwarden.log
- LOG_LEVEL=warn
ports:
- "127.0.0.1:8080:80"
- "127.0.0.1:3012:3012"Note: Binding to 127.0.0.1 ensures traffic goes through reverse proxy.
Step 4: Start the Container
Deploy Vaultwarden using Docker Compose.
Start Container:
docker-compose up -dVerification:
docker ps | grep vaultwarden
docker logs vaultwardenExpected Result: Container running; no errors in logs.
Step 5: Configure Reverse Proxy
Set up SSL termination and security headers.
Nginx Configuration:
upstream vaultwarden-default {
zone vaultwarden-default 64k;
server 127.0.0.1:8080;
keepalive 2;
}
upstream vaultwarden-ws {
zone vaultwarden-ws 64k;
server 127.0.0.1:3012;
keepalive 2;
}
server {
listen 443 ssl http2;
server_name <your-domain>;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
client_max_body_size 525M;
location / {
proxy_pass http://vaultwarden-default;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /notifications/hub {
proxy_pass http://vaultwarden-ws;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /notifications/hub/negotiate {
proxy_pass http://vaultwarden-default;
}
}
server {
listen 80;
server_name <your-domain>;
return 301 https://$server_name$request_uri;
}Alternative - Caddy (Simpler):
<your-domain> {
encode gzip
# Websockets
reverse_proxy /notifications/hub 127.0.0.1:3012
# Everything else
reverse_proxy 127.0.0.1:8080 {
header_up X-Real-IP {remote_host}
}
}Verification:
curl -I https://<your-domain>Expected Result: HTTP 200 with security headers present.
Step 6: Create Admin Account
Set up the initial administrator account.
Process:
- Navigate to
https://<your-domain> - Click "Create Account"
- Enter email and strong master password
- Complete registration
Disable Public Registration:
After creating your account, update docker-compose.yml:
environment:
- SIGNUPS_ALLOWED=falseRestart Container:
docker-compose down && docker-compose up -dStep 7: Enable Two-Factor Authentication
Add additional security to your account.
Process:
- Log into your vault
- Navigate to Settings > Two-step Login
- Choose authentication method:
- TOTP (Authenticator app) - Recommended
- WebAuthn (Hardware key) - Most secure
- Email - Backup option
Verification: Test login requires second factor.
Step 8: Configure Automated Backups
Set up encrypted backup procedures.
Create Backup Script (/opt/vaultwarden/backup.sh):
#!/bin/bash
# Automated Vaultwarden Backup Script
BACKUP_DIR="/backup/vaultwarden"
DATE=$(date +%Y%m%d_%H%M%S)
DATA_DIR="/opt/vaultwarden/data"
mkdir -p "$BACKUP_DIR"
# Stop container for consistent backup
docker-compose -f /opt/vaultwarden/docker-compose.yml stop
# Create encrypted backup
tar czf - -C "$DATA_DIR" . | \
gpg --symmetric --cipher-algo AES256 \
--output "$BACKUP_DIR/vaultwarden_$DATE.tar.gz.gpg"
# Start container
docker-compose -f /opt/vaultwarden/docker-compose.yml start
# Keep only last 7 backups
ls -t "$BACKUP_DIR"/*.gpg | tail -n +8 | xargs -r rm
echo "Backup completed: vaultwarden_$DATE.tar.gz.gpg"Set Permissions and Schedule:
chmod 700 /opt/vaultwarden/backup.sh
# Add to crontab (daily at 3 AM)
echo "0 3 * * * /opt/vaultwarden/backup.sh >> /var/log/vaultwarden-backup.log 2>&1" | crontab -Verification:
/opt/vaultwarden/backup.sh
ls -la /backup/vaultwarden/Expected Result: Encrypted backup file created.
Step 9: Configure Fail2Ban
Protect against brute force attacks.
Create Filter (/etc/fail2ban/filter.d/vaultwarden.conf):
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <HOST>\..*$
ignoreregex =Create Jail (/etc/fail2ban/jail.d/vaultwarden.local):
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
logpath = /opt/vaultwarden/data/vaultwarden.log
maxretry = 5
bantime = 1h
findtime = 15mEnable and Restart:
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwardenExpected Output: Jail active with filter loaded.
Step 10: Configure Clients
Set up Bitwarden clients to use your server.
Client Configuration:
- Open Bitwarden app/extension
- Click settings gear (before login)
- Enter server URL:
https://<your-domain> - Log in with your credentials
Supported Clients:
| Platform | Application |
|---|---|
| Desktop | Windows, macOS, Linux |
| Browser | Chrome, Firefox, Safari, Edge |
| Mobile | iOS, Android |
| CLI | All platforms |
SMTP Configuration (Optional)
Enable email for account recovery and notifications.
Add to docker-compose.yml:
environment:
- SMTP_HOST=<smtp-server>
- SMTP_FROM=vault@<your-domain>
- SMTP_PORT=587
- SMTP_SECURITY=starttls
- SMTP_USERNAME=<smtp-username>
- SMTP_PASSWORD=<smtp-password>Troubleshooting
| Symptom | Possible Cause | Solution |
|---|---|---|
| Can't connect from clients | DNS/SSL issue | Verify certificate and DNS resolution |
| Websocket notifications not working | Reverse proxy config | Check websocket proxy settings |
| Admin panel inaccessible | ADMIN_TOKEN not set | Verify .env file and restart |
| Login fails after 2FA setup | Time sync issue | Verify server time is accurate |
| Backup fails | Permission denied | Check script permissions and paths |
Verification Checklist
Deployment
- Container running and healthy
- SSL/TLS properly configured
- Security headers present
Security
- Public registration disabled
- Two-factor authentication enabled
- Admin panel secured
- Fail2Ban configured
Operations
- Automated backups running
- Backup restoration tested
- Offsite backup configured
- All clients connecting via HTTPS
Restore Procedure
If you need to restore from backup:
# Decrypt backup
gpg --decrypt vaultwarden_backup.tar.gz.gpg | tar xzf - -C /opt/vaultwarden/data/
# Restart container
docker-compose up -dReferences
Last Updated: January 2026