Complete Vaultwarden Guide: Self-Host Your Password Manager

Why Self-Host a Password Manager with Vaultwarden

Password managers are essential for digital security, but cloud-based services introduce trust dependencies. Vaultwarden, a lightweight, compatible rewrite of Bitwarden’s server in Rust, offers complete data sovereignty. It consumes under 256MB of RAM on a Raspberry Pi, supports unlimited users, and integrates seamlessly with all official Bitwarden clients—mobile, desktop, browser extensions, and CLI. Unlike Bitwarden’s official server, which requires MSSQL and 2GB+ RAM, Vaultwarden runs on SQLite or MariaDB, making it ideal for low-resource environments like a $5 VPS or a home NAS. This guide covers installation, configuration, backups, hardening, and advanced features.

Prerequisites and Server Requirements

Minimum Hardware

  • CPU: 1 core (ARM or x86)
  • RAM: 512MB (256MB usable after OS overhead)
  • Storage: 1GB for Vaultwarden + database, plus space for attachments (if enabled)
  • Network: Stable internet connection; public IP or dynamic DNS if remote access is needed

Software Requirements

  • OS: Debian 12, Ubuntu 22.04+, or Alpine Linux (recommended for minimal footprint)
  • Docker and docker-compose (or Podman with podman-compose)
  • Reverse Proxy: Nginx or Caddy (for HTTPS with Let’s Encrypt)
  • Domain or Subdomain: vault.yourdomain.com (or a DuckDNS free domain)

Installing Vaultwarden with Docker

Step 1: Prepare the Environment

sudo apt update && sudo apt upgrade -y
sudo apt install docker.io docker-compose -y
sudo systemctl enable --now docker

Step 2: Create Directory Structure

mkdir -p /opt/vaultwarden && cd /opt/vaultwarden
mkdir -p data backups

Step 3: Write docker-compose.yml

version: '3.8'
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      - SIGNUPS_ALLOWED=false
      - DOMAIN=https://vault.yourdomain.com
      - LOG_FILE=/data/vaultwarden.log
      - LOG_LEVEL=warn
      - EXTENDED_LOGGING=false
    volumes:
      - ./data:/data
    ports:
      - "127.0.0.1:8080:80"
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true

Critical Environment Variables:

  • SIGNUPS_ALLOWED=false – disable open registration once admin account is created
  • DOMAIN – must match your reverse proxy domain exactly; affects WebSocket and attachment links
  • LOG_FILE – enable logging to file for troubleshooting
  • IP_HEADER – set to X-Forwarded-For when behind a proxy

Step 4: Deploy the Container

docker-compose pull
docker-compose up -d
docker logs vaultwarden -f  # verify startup

Reverse Proxy Configuration with Nginx

Install Nginx and Certbot

sudo apt install nginx certbot python3-certbot-nginx -y

Create Nginx Site Configuration

server {
    listen 80;
    server_name vault.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name vault.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/vault.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vault.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

    client_max_body_size 128M;

    location / {
        proxy_pass http://127.0.0.1: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;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /notifications/hub {
        proxy_pass http://127.0.0.1:3012;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /notifications/hub/negotiate {
        proxy_pass http://127.0.0.1:8080;
    }
}

Note: The /notifications/hub block enables real-time sync across devices. Without it, clients poll every 30 seconds. Vaultwarden exposes a separate WebSocket port (3012 by default) – add WEBSOCKET_PORT=3012 to the environment if needed.

Obtain SSL Certificate

sudo certbot --nginx -d vault.yourdomain.com --non-interactive --agree-tos -m youremail@domain.com

Enable Auto-Renewal

sudo systemctl enable certbot.timer

Initial Setup and Admin Account

Access the Web Vault

Navigate to https://vault.yourdomain.com. Create the first account. This automatically becomes an admin if no other users exist.

Enable Admin Panel

Add to docker-compose.yml environment:

- ADMIN_TOKEN=YourSuperSecureRandomToken

Generate a strong token: openssl rand -base64 48

Restart Vaultwarden: docker-compose down && docker-compose up -d

Access admin panel at https://vault.yourdomain.com/admin. From here you can:

  • Disable new signups permanently
  • View and delete users
  • Enable or disable 2FA per user
  • Export a JSON backup of all user data
  • Configure SMTP for password resets and invitations

Mail Configuration for Invitations and Notifications

Without SMTP, users cannot receive invitations or reset passwords. Add to docker-compose.yml environment:

- SMTP_HOST=smtp.gmail.com
- SMTP_PORT=587
- SMTP_SSL=true
- SMTP_FROM=vaultwarden@yourdomain.com
- SMTP_USERNAME=your_app_email@gmail.com
- SMTP_PASSWORD=your_app_specific_password
- SMTP_AUTH_METHOD=PLAIN

For Gmail, use an App Password (enable 2FA on the Google account first). For sendgrid or mailgun, adjust SMTP_HOST accordingly. Restart container after changes.

Backup Strategies

Full Backup Script (Run Daily via Cron)

#!/bin/bash
BACKUP_DIR="/opt/vaultwarden/backups"
DATE=$(date +%Y-%m-%d)

# Stop container for consistency
docker stop vaultwarden

# Backup database and config
tar -czf "$BACKUP_DIR/vaultwarden-$DATE.tar.gz" 
  -C /opt/vaultwarden data/db.sqlite3  
  data/attachments 
  data/config.json 
  docker-compose.yml

# Restart container
docker start vaultwarden

# Encrypt backup with GPG (optional)
gpg --output "$BACKUP_DIR/vaultwarden-$DATE.tar.gz.gpg" 
  --symmetric --cipher-algo AES256 "$BACKUP_DIR/vaultwarden-$DATE.tar.gz"
rm "$BACKUP_DIR/vaultwarden-$DATE.tar.gz"

# Keep backups for 30 days
find "$BACKUP_DIR" -name "*.gpg" -type f -mtime +30 -delete

Offsite Backup with Rclone

rclone sync /opt/vaultwarden/backups remote:my-bucket/vaultwarden --verbose

Set up rclone with your cloud provider (GDrive, S3, B2) using rclone config.

Automated Backup in docker-compose

Add a backup service:

backup:
  image: alpine:latest
  volumes:
    - ./data:/data
    - ./backups:/backups
  command: |
    sh -c "apk add sqlite && sqlite3 /data/db.sqlite3 '.backup /backups/db.sqlite3' && tar czf /backups/fullbackup-$(date +%Y%m%d).tar.gz -C /data ."
  cron: "0 3 * * *"

Hardening for Production

Enable Two-Factor Authentication (2FA)

From admin panel or each user’s account settings: enable TOTP (Google Authenticator) or FIDO2 WebAuthn (YubiKey). Users should enforce this.

# In environment
- REQUIRE_2FA=true

Use Fail2ban with Vaultwarden Logs

sudo apt install fail2ban -y
sudo nano /etc/fail2ban/jail.local

Add:

[vaultwarden]
enabled = true
port = http,https
filter = vaultwarden
logpath = /opt/vaultwarden/data/vaultwarden.log
maxretry = 5
bantime = 3600

Create filter:

sudo nano /etc/fail2ban/filter.d/vaultwarden.conf

Content:

[Definition]
failregex = ^.*Invalid password for user.*$
ignoreregex =

Restart fail2ban: sudo systemctl restart fail2ban

Network Security

  • Bind Vaultwarden only to 127.0.0.1 (as shown in docker-compose), never expose port 80 directly
  • Use ufw: sudo ufw allow 22/tcp && sudo ufw allow 443/tcp && sudo ufw enable
  • Enable automatic security updates: sudo dpkg-reconfigure --priority=low unattended-upgrades

Advanced Features

Organization and Sharing

Users can create Organizations within the web vault. Enable administrators, owners, managers, and users with granular permissions. Use Collections to group passwords logically (e.g., “IT Team,” “Marketing”).

Emergency Access

Vaultwarden supports Bitwarden’s emergency access feature. One user can designate another as a trusted emergency contact with a configurable activation delay (1–30 days). Enable in admin panel or user settings.

Self-Hosted Attachments

By default, file attachments are stored in /data/attachments. To limit size, set FILE_UPLOAD_LIMIT_MB=100 in environment. For larger attachments, consider an external object store (S3-compatible) via the ATTACHMENT_STORAGE_BACKEND experimental variable.

Disable Unused Features

Reduce attack surface:

- SHOW_PASSWORD_HINT=false
- DISABLE_ICON_DOWNLOAD=true  # saves bandwidth
- PASSWORD_ITERATIONS=100000  # higher = more secure but slower

Monitoring and Alerts

Health Check Endpoint

Vaultwarden exposes a health check at /alive. Set up monitoring with UptimeRobot or a local script:

curl -f https://vault.yourdomain.com/alive || notify-send "Vaultwarden down!"

Logging

  • Enable structured JSON logging: LOG_FORMAT=json
  • Stream logs to graylog or papertrail: SYSLOG_HOST=my.syslog.server, SYSLOG_PORT=514

Performance Tuning

  • For 100+ users, switch from SQLite to MariaDB:
    services:
      db:
        image: mariadb:10
        environment:
          MYSQL_ROOT_PASSWORD: strong_password
          MYSQL_DATABASE: vaultwarden
          MYSQL_USER: vaultwarden
          MYSQL_PASSWORD: another_password
      vaultwarden:
        environment:
          DATABASE_URL=mysql://vaultwarden:another_password@db:3306/vaultwarden
  • Enable database connection pooling: DB_CONNECTION_RETIRES=5

Migrating from Bitwarden Cloud to Vaultwarden

  1. Export data from Bitwarden: Settings → Account → Export → JSON (unencrypted)
  2. Temporarily enable signups on Vaultwarden: set SIGNUPS_ALLOWED=true in environment, restart
  3. Create a new empty account on Vaultwarden with the same email as your Bitwarden account
  4. Use the Vaultwarden CLI or web vault’s import feature: Tools → Import Data → Bitwarden (JSON)
  5. Delete duplicate organizations if any
  6. Re-disable signups
  7. Test login and sync across devices

Updating Vaultwarden

cd /opt/vaultwarden
docker-compose pull vaultwarden
docker-compose up -d
docker image prune -f  # remove old images

Check release notes for breaking changes. Subscribe to the GitHub releases RSS feed.

Common Troubleshooting

Issue Solution
WebSocket not syncing Verify Nginx WebSocket proxy block; ensure WEBSOCKET_PORT=3012 is set
“Invalid domain” error Set DOMAIN env var exactly as accessed in browser (with trailing slash removed)
Cannot send email Check SMTP logs: docker logs vaultwarden; test with telnet
High memory usage Set DB_CONNECTION_POOL_SIZE=5; reduce PASSWORD_ITERATIONS
Admin panel 404 Ensure ADMIN_TOKEN is set and container restarted; access /admin not /admin/

Leave a Comment