Press ESC to close Press / to search

Vaultwarden: Self-Hosted Bitwarden Password Manager Complete Setup Guide

🎯 Key Takeaways

  • Table of Contents
  • Why Self-Host Your Password Manager
  • Vaultwarden vs Official Bitwarden Server
  • Prerequisites
  • Deploying Vaultwarden with Docker Compose

πŸ“‘ Table of Contents

Vaultwarden is a lightweight, open-source reimplementation of the Bitwarden server, written in Rust and designed to run on minimal hardware. Where the official Bitwarden server requires multiple Docker containers and several gigabytes of RAM, Vaultwarden runs in a single container using under 10 MB of memory at idle. Every official Bitwarden client β€” browser extensions, desktop apps, iOS, and Android β€” connects to Vaultwarden exactly as it does to Bitwarden’s hosted service. This guide covers deploying Vaultwarden on Linux, configuring it for production use, connecting clients, enabling two-factor authentication, and hardening it for team use.

Table of Contents

Why Self-Host Your Password Manager

Password managers are among the most security-sensitive applications in an organization. Self-hosting Vaultwarden keeps your encrypted vault on infrastructure you control, with no third-party service involved in storing or transmitting your credentials. Practical reasons to self-host:

  • Data sovereignty: Your encrypted vault never touches an external server. Even in the event of a third-party breach, your passwords stay on your infrastructure.
  • Cost: Bitwarden’s hosted Teams plan costs $4/user/month. Vaultwarden runs on a $5 VPS with unlimited users at no per-seat cost.
  • Offline access: Vaultwarden works on an internal LAN with no internet access required β€” useful for air-gapped or restricted environments.
  • All Bitwarden features included: Organizations, shared collections, emergency access, and TOTP β€” all enabled by default in Vaultwarden.

Vaultwarden vs Official Bitwarden Server

Feature Vaultwarden Official Bitwarden Server
RAM at idle <10 MB ~2 GB (multiple services)
Language Rust (single binary) .NET + multiple containers
Database SQLite (default) or PostgreSQL/MySQL MSSQL
Client compatibility All official Bitwarden clients All official Bitwarden clients
Organizations/sharing Yes (all tiers) Yes (requires paid plan for teams)
TOTP authenticator Yes Requires paid plan
SSO (SAML/OIDC) Not supported Enterprise plan only
Official support Community Bitwarden Inc.

The primary limitation is SSO support β€” if your organization requires SAML or OIDC integration with Keycloak or Okta, the official Bitwarden server is the better choice. For all other use cases, Vaultwarden is a drop-in replacement.

Prerequisites

  • A Linux server with Docker and Docker Compose installed
  • A domain name pointing to the server (e.g., vault.example.com)
  • Port 443 open in your firewall
  • An SMTP server or relay for email notifications (optional but recommended)

Deploying Vaultwarden with Docker Compose

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

cat > docker-compose.yml << 'COMPOSE'
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: https://vault.example.com
      SIGNUPS_ALLOWED: "false"          # Disable open registration after first user
      INVITATIONS_ALLOWED: "true"       # Allow admin to invite users
      ADMIN_TOKEN: ""                   # Set securely β€” see hardening section
      WEBSOCKET_ENABLED: "true"
      SENDS_ALLOWED: "true"
      EMERGENCY_ACCESS_ALLOWED: "true"

      # Database (SQLite is fine for up to ~100 users)
      DATABASE_URL: /data/vaultwarden.db

      # Email β€” fill in with your SMTP details
      SMTP_HOST: smtp.example.com
      SMTP_PORT: "587"
      SMTP_SECURITY: starttls
      SMTP_USERNAME: vault@example.com
      SMTP_PASSWORD: smtp_password_here
      SMTP_FROM: vault@example.com
      SMTP_FROM_NAME: Vaultwarden

      LOG_LEVEL: warn
      LOG_FILE: /data/vaultwarden.log
    volumes:
      - ./data:/data
    ports:
      - "127.0.0.1:8080:80"
      - "127.0.0.1:3012:3012"   # WebSocket notifications
COMPOSE

docker compose up -d
docker compose logs -f vaultwarden

The data directory (./data) contains the SQLite database and all vault data. Back this up regularly β€” it is the single most important file on the server.

Using PostgreSQL for Larger Deployments

cat >> docker-compose.yml << 'COMPOSE'

  vaultwarden-db:
    image: postgres:16-alpine
    container_name: vaultwarden-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: changeme_strong_password
    volumes:
      - ./postgres:/var/lib/postgresql/data
COMPOSE

# Update DATABASE_URL in the vaultwarden service:
# DATABASE_URL: postgresql://vaultwarden:changeme_strong_password@vaultwarden-db/vaultwarden

Nginx Reverse Proxy with TLS

certbot certonly --standalone -d vault.example.com \
  --email admin@example.com --agree-tos -n

cat > /etc/nginx/sites-available/vaultwarden << 'NGINX'
server {
    listen 80;
    server_name vault.example.com;
    return 301 https://$host$request_uri;
}

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

    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;
    ssl_prefer_server_ciphers on;

    client_max_body_size 128m;  # Allow large file attachments

    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;
    }

    # WebSocket for real-time vault notifications
    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;
    }
}
NGINX

ln -s /etc/nginx/sites-available/vaultwarden /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Initial Setup: Admin Panel and First User

# Generate a secure admin token
openssl rand -base64 48
# Copy this value and set it as ADMIN_TOKEN in docker-compose.yml
# Then recreate the container:
docker compose up -d

# Access the admin panel at: https://vault.example.com/admin
# Login with the ADMIN_TOKEN value

# The admin panel lets you:
# - Create and manage user accounts
# - Send invitations
# - View diagnostics and server info
# - Enable/disable features
# - Force 2FA for all users

Create the first admin user via the admin panel or by temporarily enabling SIGNUPS_ALLOWED: "true", registering, then disabling signups again. The admin panel user management page is the preferred approach for team deployments.

Connecting Bitwarden Clients

# All official Bitwarden clients support custom server URLs

# Browser extension (Chrome, Firefox, Edge, Safari):
# Click the extension β†’ Settings icon β†’ Self-hosted server
# Server URL: https://vault.example.com
# (Leave API and Identity URL blank β€” Vaultwarden auto-detects them)

# Desktop app (Windows, macOS, Linux):
# Open Bitwarden β†’ Settings β†’ Server URL β†’ https://vault.example.com

# Mobile (iOS and Android):
# Tap the region selector on the login screen
# β†’ Self-hosted β†’ https://vault.example.com

# CLI (bitwarden-cli):
bw config server https://vault.example.com
bw login
bw sync

Configuring Email (SMTP) for Invitations and Alerts

# Email is required for:
# - User registration invitations
# - Email verification (if enabled)
# - Emergency access requests
# - 2FA login codes (if using email 2FA)

# Test email configuration from the admin panel:
# https://vault.example.com/admin/diagnostics β†’ Send test email

# Common SMTP configurations:
# Gmail/Google Workspace:
# SMTP_HOST: smtp.gmail.com
# SMTP_PORT: 587
# SMTP_SECURITY: starttls
# SMTP_USERNAME: your@gmail.com
# SMTP_PASSWORD: app-specific-password (generate at myaccount.google.com)

# Mailgun:
# SMTP_HOST: smtp.mailgun.org
# SMTP_PORT: 587
# SMTP_USERNAME: postmaster@mg.example.com
# SMTP_PASSWORD: mailgun-smtp-password

Enabling Two-Factor Authentication

# Users enable 2FA per-account in Bitwarden's web vault:
# https://vault.example.com β†’ Account Settings β†’ Security β†’ Two-step Login

# Supported 2FA methods in Vaultwarden:
# - Authenticator app (TOTP β€” Google Authenticator, Aegis, etc.)
# - Email code
# - Duo Security
# - YubiKey OTP
# - FIDO2/WebAuthn hardware keys

# Force 2FA for all users (admin panel):
# Admin Panel β†’ Users β†’ select user β†’ "Deauthorize Sessions"
# Then set policy via: Admin Panel β†’ Settings β†’ Require 2FA = On

# Users who haven't set up 2FA will be blocked from logging in
# until they configure it

Organizations and Shared Collections for Teams

# Organizations allow teams to share passwords securely
# Create an organization: Web Vault β†’ New Organization

# Collection structure example:
# Organization: "ACME Corp"
# β”œβ”€β”€ Collection: Infrastructure Passwords
# β”‚   β”œβ”€β”€ Server root credentials
# β”‚   └── Database passwords
# β”œβ”€β”€ Collection: Web Services
# β”‚   β”œβ”€β”€ AWS IAM keys
# β”‚   └── GitHub tokens
# └── Collection: HR Department (restricted)
#     └── Payroll system credentials

# Invite users to the organization via email
# Assign roles: Owner, Admin, Manager, User, Custom

# Members see only collections they're granted access to
# Even Vaultwarden admins cannot read encrypted vault contents

Backup and Restore Strategy

# SQLite database backup (single file β€” the entire vault)
# Stop Vaultwarden before backup for consistency
docker compose stop vaultwarden
cp /opt/vaultwarden/data/vaultwarden.db /backups/vaultwarden-$(date +%Y%m%d).db
docker compose start vaultwarden

# Online backup using SQLite's backup API (no downtime)
sqlite3 /opt/vaultwarden/data/vaultwarden.db ".backup '/backups/vaultwarden-$(date +%Y%m%d).db'"

# Automate with a systemd timer or cron
cat > /etc/cron.d/vaultwarden-backup << 'CRON'
0 2 * * * root sqlite3 /opt/vaultwarden/data/vaultwarden.db ".backup '/backups/vaultwarden-$(date +\%Y\%m\%d).db'" && find /backups -name "vaultwarden-*.db" -mtime +30 -delete
CRON

# Restore
docker compose stop vaultwarden
cp /backups/vaultwarden-20260430.db /opt/vaultwarden/data/vaultwarden.db
docker compose start vaultwarden

Security Hardening

# 1. Use a hashed admin token (prevents token exposure in process list)
# Generate a PHC string:
echo -n "your-admin-password" | argon2 --iterations 3 --memory 65536 --parallelism 4 -id -e
# Set ADMIN_TOKEN to the $argon2id$... output

# 2. Disable user signups permanently
SIGNUPS_ALLOWED: "false"

# 3. Restrict to specific email domains only
SIGNUPS_DOMAINS_WHITELIST: "example.com"

# 4. Disable the admin panel entirely when not in use
# Comment out ADMIN_TOKEN β€” panel becomes inaccessible

# 5. Set login attempt limits
LOGIN_RATELIMIT_MAX_BURST: "10"
LOGIN_RATELIMIT_SECONDS: "60"

# 6. Restrict Vaultwarden to internal network only
# In docker-compose.yml, bind only to internal interface:
# ports:
#   - "10.0.0.1:8080:80"

# 7. Enable HSTS in nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Upgrading Vaultwarden

# Always backup the database before upgrading
sqlite3 /opt/vaultwarden/data/vaultwarden.db ".backup '/backups/pre-upgrade-$(date +%Y%m%d).db'"

# Pull the latest image and recreate the container
cd /opt/vaultwarden
docker compose pull
docker compose up -d

# Verify the new version
docker compose logs vaultwarden | head -20
curl -s https://vault.example.com/api/config | jq .version

# Vaultwarden automatically runs database migrations on startup
# No manual schema updates required

Conclusion

Vaultwarden gives individuals and teams a fully-featured, self-hosted password manager that is indistinguishable from Bitwarden's commercial service from the perspective of every client application. The combination of extremely low resource requirements, SQLite simplicity for small teams, and complete compatibility with the mature Bitwarden client ecosystem makes Vaultwarden the best password management option for anyone running their own infrastructure. Configure SMTP early so invitations and 2FA codes work from day one, enable mandatory 2FA for all organization members, and set up an automated nightly database backup β€” those three steps cover the vast majority of operational concerns. Your passwords stay encrypted on your hardware, your team gets the same polished Bitwarden experience across all their devices, and you pay nothing per seat.

Was this article helpful?

Advertisement
🏷️ Tags: 2FA bitwarden docker open source password manager secrets management self-hosted password manager team password sharing vaultwarden
R

About Ramesh Sundararamaiah

Red Hat Certified Architect

Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.

🐧 Stay Updated with Linux Tips

Get the latest tutorials, news, and guides delivered to your inbox weekly.

Advertisement

Add Comment


↑