Zum Inhalt

Self-Hosting Password Manager: Vaultwarden on Debian Linux (Rust Bitwarden Server, Argon2id & Hardening)

Executive Summary: Official Bitwarden self-hosted deployments require a heavy multi-container Microsoft .NET and MSSQL stack consuming several gigabytes of RAM. Vaultwarden (formerly bitwarden_rs) is an independent, community-audited Rust implementation of the Bitwarden Client API that consumes < 25 MB of RAM, supports all official Bitwarden Desktop, Browser, iOS, and Android clients (including Organizations, Emergency Access, Send, and FIDO2 WebAuthn / Passkeys), and stores 100% zero-knowledge client-encrypted (AES-256-CBC + PBKDF2/Argon2id) vaults.

  • 20 MB RAM Rust Engine


    Runs on a compiled Rust + Rocket backend with embedded SQLite (WAL mode)—requiring 100× less RAM than official MSSQL Bitwarden.

  • Zero-Knowledge Client Cryptography


    Your Master Password never leaves your device; key stretching (Argon2id) and AES-256 encryption happen strictly inside the client app.

  • WebAuthn FIDO2 & YubiKey 2FA


    Enforce hardware security keys (YubiKey / Passkeys) or TOTP 2FA on every vault login.

  • Hardened Admin & Signup Lock


    Disable /admin or hash the admin token with PHC Argon2id, lock SIGNUPS_ALLOWED=false, and ban brute-force IPs via Fail2ban.


1. Zero-Knowledge Cryptographic Architecture

Why is self-hosting a password manager safe if you follow strict hardening rules? Because Vaultwarden never receives your plaintext passwords or your Master Password:

%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
    CLI["💻 Bitwarden Client App<br/>Master Password -> Argon2id<br/>Derives Local Symmetric Key"]
    TLS["🔒 Reverse Proxy (TLS 1.3)<br/>vault.nejat-hakan.de<br/>(Blocks /admin from WAN!)"]
    VW["🦀 Vaultwarden (Rust)<br/>127.0.0.1:8222<br/>(Stores Ciphertext Blobs Only)"]
    DB["🗄️ /data/db.sqlite3 (WAL)<br/>+ RSA Key + Attachments"]

    CLI ==>|"HTTPS (Encrypted JSON + Auth Hash)"| TLS
    TLS ==> VW
    VW <--> DB

2. Generating an Argon2id-Hashed ADMIN_TOKEN

Never store a plaintext ADMIN_TOKEN in your compose.yaml file. Use Vaultwarden's built-in hash command to generate a salted PHC Argon2id string (escaping each $ as $$ for Docker Compose interpolation!):

# File: /opt/vaultwarden-ops/01_generate_argon2_token.sh
docker run --rm -it docker.io/vaultwarden/server:latest /vaultwarden hash --preset owasp
# Copy the resulting $argon2id$v=19$m=65540,t=3,p=4$... string into .env!

3. Production Container Configuration (compose.yaml)

Deploy vaultwarden/server:latest bound strictly to 127.0.0.1:8222 (HTTPS is mandatory for WebCrypto APIs in browsers—terminate TLS 1.3 in front of 127.0.0.1:8222 via Caddy, Traefik, or Nginx):

# File: /srv/containers/vaultwarden/compose.yaml
services:
  vaultwarden:
    image: docker.io/vaultwarden/server:latest
    container_name: vaultwarden
    user: "1000:1000"
    restart: unless-stopped
    ports:
      - "127.0.0.1:8222:80/tcp"
    environment:
      DOMAIN: "https://vault.nejat-hakan.de"
      TZ: "Europe/Berlin"
      # 1. CRITICAL: Disable open public registration after creating your own account!
      SIGNUPS_ALLOWED: "false"
      INVITATIONS_ALLOWED: "true"
      SHOW_PASSWORD_HINT: "false"
      # 2. Enable WebSocket live push sync to desktop/browser clients
      WEBSOCKET_ENABLED: "true"
      # 3. Argon2id PHC string from '/vaultwarden hash' (or leave unset to disable /admin completely!)
      ADMIN_TOKEN: "${VAULTWARDEN_ARGON2_ADMIN_TOKEN}"
      # 4. Logging for Fail2ban intrusion prevention
      LOG_FILE: "/data/vaultwarden.log"
      LOG_LEVEL: "info"
      EXTENDED_LOGGING: "true"
    volumes:
      - /srv/containers/vaultwarden/data:/data:rw

4. Blocking /admin at the Reverse Proxy & Setting High-Memory Client KDF

  1. Switch Your Account KDF from PBKDF2 to Argon2id: Inside the Vaultwarden Web Vault, navigate to Settings → Security → Keys → KDF Algorithm and select Argon2id (64 MiB memory, 3 iterations, 4 parallelism). This makes offline GPU brute-force attacks against a stolen database thousands of times harder than default PBKDF2.
  2. Restrict /admin at Your Reverse Proxy: Even with an Argon2id ADMIN_TOKEN, block public internet access to /admin at your reverse proxy so it is reachable only from your internal LAN or WireGuard VPN (10.10.0.0/16):
# File: /etc/caddy/Caddyfile
vault.nejat-hakan.de {
    encode zstd gzip

    @admin_external {
        path /admin*
        not remote_ip 127.0.0.1 192.168.1.0/24 10.10.0.0/16
    }
    respond @admin_external "Forbidden" 403

    reverse_proxy 127.0.0.1:8222 {
        header_up X-Real-IP {remote_host}
    }
}

5. Automated Hot Backup of SQLite (sqlite3 .backup)

Because Vaultwarden uses SQLite in WAL mode (db.sqlite3, db.sqlite3-wal, db.sqlite3-shm), create atomic online snapshots using sqlite3 /data/db.sqlite3 ".backup ..." before running BorgBackup or Restic:

# File: /opt/vaultwarden-ops/backup_vaultwarden.sh
#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="/var/backups/vaultwarden/$(date +%Y-%m-%d)"
sudo mkdir -p "$BACKUP_DIR"

# 1. Atomic hot SQLite backup
sudo sqlite3 /srv/containers/vaultwarden/data/db.sqlite3 ".backup '${BACKUP_DIR}/db.sqlite3'"

# 2. Copy RSA keypair and encrypted attachments
sudo cp -a /srv/containers/vaultwarden/data/rsa_key* "$BACKUP_DIR/"
sudo cp -a /srv/containers/vaultwarden/data/attachments "$BACKUP_DIR/" 2>/dev/null || true
echo "[OK] Vaultwarden snapshot saved to $BACKUP_DIR"

Where to Go Next

Protect your Vaultwarden instance with WireGuard VPN access, Caddy TLS, and encrypted off-site backups:

  • WireGuard VPN Server


    Access your /admin panel and private services exclusively over a kernel-level WireGuard tunnel.

    WireGuard Guide

  • BorgBackup + Borgmatic


    Automate daily client-side encrypted off-site backups of /var/backups/vaultwarden.

    BorgBackup Guide

  • Caddy Reverse Proxy


    Terminate TLS 1.3 and enforce IP access controls in front of Vaultwarden.

    Caddy Guide