Vaultwarden Self-Hosted Deployment
Vaultwarden is a Rust reimplementation of the Bitwarden server API. Much lighter on resources than the official server, runs happily on a Pi, and has no paywall on features like 2FA, organisations, or file attachments.
Overview
Vaultwarden is an independent Rust implementation of the Bitwarden server API. It uses a fraction of the RAM and CPU the official server needs, which makes it well suited to self-hosting.
Repository
https://github.com/dani-garcia/vaultwarden
Key Differences from Official Bitwarden
| Feature | Vaultwarden | Official Bitwarden |
|---|---|---|
| Language | Rust | C# (.NET) |
| Resource Usage | ~10-20 MB RAM | ~2-4 GB RAM |
| Database | SQLite, MySQL, PostgreSQL | MS SQL Server, PostgreSQL |
| Containers | Single container | 8+ containers |
| License | GPL-3.0 | AGPL-3.0 + Proprietary |
| Premium Features | All free | Requires paid license |
| Official Support | Community | Bitwarden Inc. |
| Enterprise SSO | ✗ | ✓ |
| Complexity | Low | High |
When to Use Vaultwarden
✓ Good for:
- Personal use
- Small teams (< 50 users)
- Home labs / self-hosting
- Limited resources (Raspberry Pi, VPS)
- Free premium features
✗ Not ideal for:
- Enterprise deployments
- Regulatory compliance requirements
- Need for official support
- Enterprise SSO requirements
System Requirements
Minimum Requirements
- CPU: 1 core
- RAM: 256 MB
- Storage: 1 GB
- OS: Linux, Windows, macOS
- Docker: 20.10+
Recommended Requirements
- CPU: 2 cores
- RAM: 512 MB
- Storage: 5 GB SSD
- Reverse proxy: NGINX, Caddy, Traefik
- SSL Certificate: Let’s Encrypt or valid cert
Vaultwarden will run on something as small as a Pi Zero.
Installation Methods
Method 1: Docker (Recommended)
Basic Docker run:
docker run -d --name vaultwarden \
-e ROCKET_PORT=8080 \
-v /vw-data:/data \
-p 8080:8080 \
--restart unless-stopped \
vaultwarden/server:latest Docker Compose (Recommended):
version: '3'
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
- DOMAIN=https://vault.example.com
- SIGNUPS_ALLOWED=false
- INVITATIONS_ALLOWED=true
- SHOW_PASSWORD_HINT=false
- WEBSOCKET_ENABLED=true
- SMTP_HOST=smtp.example.com
- SMTP_FROM=vaultwarden@example.com
- SMTP_PORT=587
- SMTP_SECURITY=starttls
- SMTP_USERNAME=vaultwarden@example.com
- SMTP_PASSWORD=smtp_password
- ADMIN_TOKEN=your_secure_admin_token
- LOG_FILE=/data/vaultwarden.log
- LOG_LEVEL=info
volumes:
- ./vw-data:/data
ports:
- "8080:80"
- "3012:3012" # WebSocket port Start with Docker Compose:
docker compose up -d Method 2: Standalone Binary
Download latest release:
# Linux x86_64
wget https://github.com/dani-garcia/vaultwarden/releases/latest/download/vaultwarden-linux-x86_64.tar.gz
tar -xzf vaultwarden-linux-x86_64.tar.gz
# ARM (Raspberry Pi)
wget https://github.com/dani-garcia/vaultwarden/releases/latest/download/vaultwarden-linux-arm.tar.gz
tar -xzf vaultwarden-linux-arm.tar.gz Run Vaultwarden:
export ROCKET_PORT=8080
export DATA_FOLDER=/var/lib/vaultwarden
./vaultwarden Systemd service:
# /etc/systemd/system/vaultwarden.service
[Unit]
Description=Vaultwarden Server
After=network.target
[Service]
User=vaultwarden
Group=vaultwarden
ExecStart=/usr/local/bin/vaultwarden
Environment="DATA_FOLDER=/var/lib/vaultwarden"
Environment="ROCKET_PORT=8080"
Restart=always
[Install]
WantedBy=multi-user.target Enable and start:
sudo systemctl enable --now vaultwarden Configuration
Environment Variables
Core settings:
# Domain (REQUIRED for proper function)
DOMAIN=https://vault.example.com
# Database (defaults to SQLite)
# For SQLite (default)
DATABASE_URL=data/db.sqlite3
# For MySQL
DATABASE_URL=mysql://user:password@host:port/database
# For PostgreSQL
DATABASE_URL=postgresql://user:password@host:port/database
# Rocket (web server) settings
ROCKET_ADDRESS=0.0.0.0
ROCKET_PORT=8080
ROCKET_WORKERS=10
# WebSocket support (for live sync)
WEBSOCKET_ENABLED=true
WEBSOCKET_ADDRESS=0.0.0.0
WEBSOCKET_PORT=3012 Security settings:
# Disable new user registration (recommended)
SIGNUPS_ALLOWED=false
# Allow invitations from existing users
INVITATIONS_ALLOWED=true
# Require email verification
SIGNUPS_VERIFY=true
# Disable password hints
SHOW_PASSWORD_HINT=false
# Enforce 2FA for all users (optional)
REQUIRE_DEVICE_EMAIL=true
# Admin token (for /admin panel)
ADMIN_TOKEN=your_secure_random_token SMTP (Email) settings:
SMTP_HOST=smtp.example.com
SMTP_FROM=vaultwarden@example.com
SMTP_FROM_NAME=Vaultwarden
SMTP_PORT=587
SMTP_SECURITY=starttls # or 'force_tls' or 'off'
SMTP_USERNAME=vaultwarden@example.com
SMTP_PASSWORD=smtp_password
SMTP_TIMEOUT=15 Advanced settings:
# File upload size limit (in MB)
FILE_SIZE_LIMIT=512
# Attachment folder path
ATTACHMENTS_FOLDER=/data/attachments
# Icon service (for website favicons)
ICON_SERVICE=internal # or 'duckduckgo', 'google'
# Disable icon downloads
DISABLE_ICON_DOWNLOAD=false
# Enable U2F (for FIDO2 keys)
ENABLE_U2F=true
# Logging
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info # or debug, trace, warn, error
EXTENDED_LOGGING=true
# Emergency access
EMERGENCY_ACCESS_ALLOWED=true Generate Admin Token
Create secure admin token:
openssl rand -base64 48 Use this as ADMIN_TOKEN value.
Configuration File (Alternative)
Instead of environment variables, use config.json:
{
"domain": "https://vault.example.com",
"signups_allowed": false,
"invitations_allowed": true,
"show_password_hint": false,
"admin_token": "your_secure_token",
"smtp_host": "smtp.example.com",
"smtp_from": "vaultwarden@example.com",
"smtp_port": 587,
"smtp_security": "starttls",
"smtp_username": "vaultwarden@example.com",
"smtp_password": "smtp_password"
} Mount in Docker:
volumes:
- ./config.json:/data/config.json Reverse Proxy Configuration
NGINX
server {
listen 443 ssl http2;
server_name vault.example.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/vault.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vault.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
client_max_body_size 525M;
location / {
proxy_pass http://localhost:8080;
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;
}
# WebSocket support
location /notifications/hub {
proxy_pass http://localhost:3012;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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/negotiate {
proxy_pass http://localhost:8080;
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;
}
}
# HTTP redirect
server {
listen 80;
server_name vault.example.com;
return 301 https://$server_name$request_uri;
} Caddy (Simplest)
Caddyfile:
vault.example.com {
reverse_proxy localhost:8080 {
header_up X-Real-IP {remote_host}
}
reverse_proxy /notifications/hub localhost:3012
} Caddy automatically handles SSL with Let’s Encrypt!
Traefik (Docker)
docker-compose.yml with Traefik labels:
version: '3'
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
- DOMAIN=https://vault.example.com
- WEBSOCKET_ENABLED=true
volumes:
- ./vw-data:/data
labels:
- "traefik.enable=true"
- "traefik.http.routers.vaultwarden.rule=Host(`vault.example.com`)"
- "traefik.http.routers.vaultwarden.entrypoints=websecure"
- "traefik.http.routers.vaultwarden.tls.certresolver=letsencrypt"
- "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
# WebSocket
- "traefik.http.routers.vaultwarden-ws.rule=Host(`vault.example.com`) && Path(`/notifications/hub`)"
- "traefik.http.routers.vaultwarden-ws.entrypoints=websecure"
- "traefik.http.routers.vaultwarden-ws.tls.certresolver=letsencrypt"
- "traefik.http.services.vaultwarden-ws.loadbalancer.server.port=3012"
networks:
- traefik
networks:
traefik:
external: true Admin Panel
Access at: https://vault.example.com/admin
Login with admin token (set via ADMIN_TOKEN)
Admin Panel Features
- Users: View, delete, deauthorize users
- Invitations: View/delete pending invitations
- Organizations: View organizations
- Diagnostics: Check config, connectivity
- Settings: Modify runtime configuration
Disable Admin Panel (After Setup)
For security, disable after initial configuration:
# Remove or comment out ADMIN_TOKEN
# ADMIN_TOKEN= Or use fail2ban to limit access attempts.
Database Options
SQLite (Default)
Pros:
- Zero configuration
- Perfect for small deployments
- Single file backup
Cons:
- Not suitable for high concurrency
- Limited to single server
Location:
/data/db.sqlite3 MySQL / MariaDB
Setup:
Create database:
CREATE DATABASE vaultwarden CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'vaultwarden'@'%' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON vaultwarden.* TO 'vaultwarden'@'%'; FLUSH PRIVILEGES;Configure Vaultwarden:
DATABASE_URL=mysql://vaultwarden:password@mysql:3306/vaultwarden
PostgreSQL
Setup:
Create database:
CREATE DATABASE vaultwarden; CREATE USER vaultwarden WITH PASSWORD 'password'; GRANT ALL PRIVILEGES ON DATABASE vaultwarden TO vaultwarden;Configure Vaultwarden:
DATABASE_URL=postgresql://vaultwarden:password@postgres:5432/vaultwarden
Backup and Restore
Backup with SQLite
Manual backup:
# Stop Vaultwarden
docker stop vaultwarden
# Backup data directory
tar -czf vaultwarden-backup-$(date +%Y%m%d).tar.gz ./vw-data/
# Start Vaultwarden
docker start vaultwarden Automated backup script:
#!/bin/bash
# vaultwarden-backup.sh
BACKUP_DIR="/backups/vaultwarden"
DATA_DIR="./vw-data"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Stop container for consistent backup
docker stop vaultwarden
# Create backup
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/vaultwarden-$TIMESTAMP.tar.gz" "$DATA_DIR"
# Start container
docker start vaultwarden
# Keep last 30 backups
find "$BACKUP_DIR" -name "vaultwarden-*.tar.gz" -mtime +30 -delete
echo "Backup completed: $BACKUP_DIR/vaultwarden-$TIMESTAMP.tar.gz" Cron job (daily at 2 AM):
0 2 * * * /home/user/vaultwarden-backup.sh >> /var/log/vaultwarden-backup.log 2>&1 Restore from Backup
# Stop Vaultwarden
docker stop vaultwarden
# Remove existing data
rm -rf ./vw-data
# Extract backup
tar -xzf vaultwarden-20250106.tar.gz
# Start Vaultwarden
docker start vaultwarden Backup with MySQL/PostgreSQL
Use standard database backup tools:
MySQL:
mysqldump -u vaultwarden -p vaultwarden > vaultwarden-$(date +%Y%m%d).sql PostgreSQL:
pg_dump -U vaultwarden vaultwarden > vaultwarden-$(date +%Y%m%d).sql Security Hardening
1. Disable User Registration
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=true # Allow existing users to invite 2. Enforce 2FA
In admin panel:
- Access
/admin - Navigate to Settings
- Enable relevant 2FA options
3. Implement Fail2Ban
Create filter:
# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex = Create jail:
# /etc/fail2ban/jail.d/vaultwarden.conf
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
logpath = /path/to/vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400 4. Restrict Admin Panel
NGINX - Allow only from specific IP:
location /admin {
allow 192.168.1.0/24;
deny all;
proxy_pass http://localhost:8080;
# ... other proxy settings
} 5. Enable Extended Logging
LOG_LEVEL=info
EXTENDED_LOGGING=true Monitoring
Check Vaultwarden Status
Docker:
docker ps | grep vaultwarden
docker logs vaultwarden
docker stats vaultwarden Health Check
HTTP endpoint:
curl -I https://vault.example.com/alive Expected: HTTP/1.1 200 OK
Prometheus Monitoring
Vaultwarden doesn’t expose Prometheus metrics natively, but you can monitor via:
- Blackbox exporter - HTTP endpoint monitoring
- Docker exporter - Container metrics
- Log exporters - Parse logs for metrics
Updates
Update Docker Container
# Pull latest image
docker pull vaultwarden/server:latest
# Stop and remove old container
docker stop vaultwarden
docker rm vaultwarden
# Start with new image (using same docker run command)
docker run -d --name vaultwarden \
-v /vw-data:/data \
-p 8080:8080 \
vaultwarden/server:latest With Docker Compose:
docker compose pull
docker compose up -d Update Standalone Binary
# Download latest
wget https://github.com/dani-garcia/vaultwarden/releases/latest/download/vaultwarden-linux-x86_64.tar.gz
# Stop service
sudo systemctl stop vaultwarden
# Replace binary
sudo tar -xzf vaultwarden-linux-x86_64.tar.gz -C /usr/local/bin/
# Start service
sudo systemctl start vaultwarden Migrating from Official Bitwarden
Export from Bitwarden
- Login to Bitwarden web vault
- Tools → Export Vault
- Select format:
.json - Download export
Import to Vaultwarden
- Login to Vaultwarden web vault
- Tools → Import Data
- Select format:
Bitwarden (json) - Upload export file
Note: Organization data requires manual re-creation.
Troubleshooting
WebSocket Not Working
Symptoms: Live sync doesn’t work, requires manual refresh
Fix:
- Ensure
WEBSOCKET_ENABLED=true - Check reverse proxy WebSocket configuration
- Verify port 3012 is accessible
Email Not Sending
Check SMTP settings:
# Test SMTP from container
docker exec -it vaultwarden sh
telnet smtp.example.com 587 Common issues:
- Wrong SMTP port
SMTP_SECURITYmismatch- Firewall blocking outbound SMTP
- ISP blocking port 25
Database Locked (SQLite)
Cause: SQLite doesn’t handle concurrent writes well
Solutions:
- Migrate to MySQL/PostgreSQL
- Reduce concurrent user operations
- Increase
ROCKET_WORKERS
High Memory Usage
Normal memory usage:
- SQLite: 10-50 MB
- MySQL: 20-100 MB
- Heavy usage: Up to 200 MB
If excessive:
- Check for large attachments
- Review
FILE_SIZE_LIMIT - Monitor database growth
Comparison: Vaultwarden vs Official Bitwarden
| Feature | Vaultwarden | Official Bitwarden |
|---|---|---|
| 2FA | ✓ TOTP, YubiKey, Duo, Email | ✓ TOTP, YubiKey, Duo, Email |
| Organizations | ✓ Free | ✓ Paid ($3-5/user/month) |
| File Attachments | ✓ Free (1GB/user) | ✓ Paid (1GB-100GB) |
| Emergency Access | ✓ Free | ✓ Paid |
| Directory Sync | ✗ | ✓ Paid |
| Enterprise SSO | ✗ | ✓ Paid |
| Event Logs | ✓ Basic | ✓ Advanced (paid) |
| Support | Community | Official |
| Compliance Certs | ✗ | ✓ SOC 2, HIPAA |
Additional Resources
- Official Wiki: https://github.com/dani-garcia/vaultwarden/wiki
- Community Forum: https://github.com/dani-garcia/vaultwarden/discussions
- Docker Hub: https://hub.docker.com/r/vaultwarden/server
Key Topics
Core Configuration
- Docker Compose Configuration for Password Managers - Complete Docker setup patterns
- Password Manager Backup and Recovery Strategies - Backup automation and disaster recovery
- Database Options for Self-Hosted Applications - SQLite, MySQL, PostgreSQL comparison
- SMTP Email Configuration for Self-Hosted Services - Email delivery setup
- Reverse Proxy Configuration for Web Services - NGINX, Caddy, Traefik configurations
Related Services
- Bitwarden Self-Hosted Deployment - Official enterprise-grade alternative
- SSL Certificate Management - Certificate management and automation