Vaultwarden is a Rust-based reimplementation of the Bitwarden server, API-compatible with all official Bitwarden clients (browser extensions, mobile, desktop, CLI) while running comfortably on a 1 vCPU VPS with 1 GB of RAM. Self-hosting gives you full control over your password vault, removes any third-party dependency for credential storage, and keeps the data inside the EU jurisdiction of your choice. This tutorial walks through a production-grade setup using Docker Compose, an automatic HTTPS reverse proxy (Caddy first, with an Nginx alternative), the admin panel, and a backup strategy that you can actually trust.

What You Will Build

By the end of this guide, you will have:

  1. A Vaultwarden instance running in Docker with persistent storage
  2. Automatic HTTPS via Let's Encrypt
  3. Admin panel access protected by an Argon2 token
  4. Fail2ban protection against brute-force login attempts
  5. Encrypted daily backups uploaded to off-server storage

Prerequisites

  • A VPS running Debian 12 or Ubuntu 24.04 (we use Debian on our Cloud VPS by default)
  • A registered domain with an A/AAAA record pointing at the VPS public IP
  • Open TCP ports 80 and 443 in your firewall
  • SSH root access

Sizing guidance: for a family or small team (under 25 users), 1 vCPU and 1 GB of RAM is plenty. SQLite is the default backend and handles thousands of vault items without issue.

Step 1: Install Docker and Docker Compose

apt update && apt upgrade -y
apt install -y ca-certificates curl gnupg ufw fail2ban
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
  -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
  > /etc/apt/sources.list.d/docker.list
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker

Verify with docker compose version.

Step 2: Generate an Admin Token

Vaultwarden's admin panel must be protected by an Argon2-hashed token, not a plain string:

docker run --rm -it vaultwarden/server:latest /vaultwarden hash

Enter a strong password (at least 32 characters) twice. The output starts with $argon2id$.... Save the full hash; you will paste it into the environment file shortly. Store the plain password in your existing password manager or a sealed offline note.

Step 3: Create the Directory Structure

mkdir -p /opt/vaultwarden/{data,backups}
mkdir -p /opt/caddy/{data,config}
cd /opt/vaultwarden

Step 4: Write the Compose File

Create /opt/vaultwarden/docker-compose.yml:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./data:/data
    networks:
      - web

  caddy:
    image: caddy:2-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - /opt/caddy/data:/data
      - /opt/caddy/config:/config
    networks:
      - web

networks:
  web:
    driver: bridge

Step 5: Write the Environment File

Create /opt/vaultwarden/.env:

DOMAIN=https://vault.example.com
SIGNUPS_ALLOWED=false
INVITATIONS_ALLOWED=true
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$...your_full_hash_here...'
SENDS_ALLOWED=true
EMERGENCY_ACCESS_ALLOWED=true
WEB_VAULT_ENABLED=true
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=warn

SMTP_HOST=smtp.example.com
SMTP_FROM=vault@example.com
SMTP_PORT=587
SMTP_SECURITY=starttls
SMTP_USERNAME=vault@example.com
SMTP_PASSWORD=your_smtp_password

The single quotes around ADMIN_TOKEN matter: the Argon2 hash contains $ characters that the shell would otherwise interpret.

Setting SIGNUPS_ALLOWED=false after your initial registration prevents random visitors from creating accounts on your instance. You will create your admin account once, then disable signups.

Step 6: Write the Caddyfile

Create /opt/vaultwarden/Caddyfile:

vault.example.com {
    encode gzip

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "no-referrer"
    }

    reverse_proxy /notifications/hub vaultwarden:3012
    reverse_proxy vaultwarden:80
}

Caddy obtains and renews Let's Encrypt certificates automatically. No manual certbot setup is required.

Step 7: Start the Stack

cd /opt/vaultwarden
docker compose up -d
docker compose logs -f

Watch for the line confirming Vaultwarden is listening on port 80, and Caddy logs showing successful certificate issuance. If the certificate step fails, the most common cause is the domain not yet resolving to the VPS, or ports 80 and 443 being blocked.

Step 8: First Login and Locking Down

  1. Visit https://vault.example.com
  2. Register your first account (this becomes the owner account)
  3. Verify the email with the link delivered to your inbox
  4. Edit .env to set SIGNUPS_ALLOWED=false
  5. Restart: docker compose restart vaultwarden

To invite additional family or team members, visit https://vault.example.com/admin, authenticate with the plain admin password (Vaultwarden hashes it and compares to the Argon2 token), and use the invite feature.

Nginx Alternative

If you prefer Nginx, replace the Caddy service with:

  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    networks:
      - web

Use certbot --nginx on the host to obtain certificates, then proxy to vaultwarden:80 and vaultwarden:3012 for the WebSocket endpoint. Caddy is simpler for this exact use case, which is why we lead with it.

Step 9: Firewall Rules

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 443/udp
ufw enable

Step 10: Fail2ban for Vaultwarden

Vaultwarden writes failed authentication attempts to its log. Create /etc/fail2ban/filter.d/vaultwarden.conf:

[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\..*$
ignoreregex =

Create /etc/fail2ban/jail.d/vaultwarden.conf:

[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
logpath = /opt/vaultwarden/data/vaultwarden.log
maxretry = 5
bantime = 86400
findtime = 600

Reload with systemctl reload fail2ban. Five failed logins from the same IP within ten minutes earn a 24-hour ban.

Apply the same pattern to a separate jail for the admin endpoint with a stricter maxretry = 3.

Backup Strategy

Vaultwarden's /data directory contains everything: the SQLite database, attachments, sends, icons, and configuration. Backing it up correctly means using the SQLite online backup API (a file-level copy of an in-use SQLite database can be inconsistent).

Create /opt/vaultwarden/backup.sh:

#!/bin/bash
set -euo pipefail

BACKUP_DIR=/opt/vaultwarden/backups
DATE=$(date +%Y%m%d-%H%M%S)
DATA_DIR=/opt/vaultwarden/data

mkdir -p "$BACKUP_DIR"

# Online SQLite backup
docker exec vaultwarden sqlite3 /data/db.sqlite3 \
  ".backup '/data/db-backup.sqlite3'"

# Tar the data directory excluding the live SQLite
tar --exclude='db.sqlite3' \
    --exclude='db.sqlite3-wal' \
    --exclude='db.sqlite3-shm' \
    --exclude='icon_cache' \
    -czf "$BACKUP_DIR/vw-$DATE.tar.gz" \
    -C "$DATA_DIR" .

# Encrypt
gpg --batch --yes --passphrase-file /root/.backup-pass \
    --symmetric --cipher-algo AES256 \
    -o "$BACKUP_DIR/vw-$DATE.tar.gz.gpg" \
    "$BACKUP_DIR/vw-$DATE.tar.gz"

rm "$BACKUP_DIR/vw-$DATE.tar.gz"

# Keep 14 days locally
find "$BACKUP_DIR" -name "vw-*.tar.gz.gpg" -mtime +14 -delete

# Upload to off-site storage (rclone, restic, or rsync)
rclone copy "$BACKUP_DIR/vw-$DATE.tar.gz.gpg" remote:vaultwarden-backups/

Schedule it daily:

chmod +x /opt/vaultwarden/backup.sh
echo "0 3 * * * root /opt/vaultwarden/backup.sh >> /var/log/vw-backup.log 2>&1" \
  > /etc/cron.d/vaultwarden-backup

Test restoration to a different host at least once before you trust this system. A backup you have never restored is a guess, not a backup.

Updating Vaultwarden

cd /opt/vaultwarden
docker compose pull
docker compose up -d

Vaultwarden upgrades are usually painless. Watch the release notes for breaking changes to environment variables.

FAQ

Is Vaultwarden compatible with official Bitwarden clients? Yes. The API surface is fully compatible with the official browser extensions, mobile apps, desktop clients, and CLI. Point the client at your self-hosted URL in the settings before login.

How much RAM does Vaultwarden actually use? A typical idle instance uses around 30 to 50 MB of RAM. Even with hundreds of users it rarely exceeds 200 MB.

Can I use MariaDB or PostgreSQL instead of SQLite? Yes, by setting DATABASE_URL in the environment. For most self-hosters, SQLite is faster and simpler, with no operational downside until you reach thousands of active users.

What happens if Vaultwarden goes down? Bitwarden clients keep an encrypted local cache and continue functioning for password retrieval. Sync, new entries, and cross-device updates pause until the server is back. This is one reason a redundant backup matters more than instance redundancy for small deployments.

Do I need the admin panel? You need it for initial setup and occasional administration (inviting users, viewing diagnostics). Once configured, you can disable it by removing ADMIN_TOKEN from .env and restarting.

Where to Host It

Vaultwarden's compute requirements are modest, but the latency between your devices and the server matters for everyday use. An EU-based VPS gives most European users single-digit millisecond response times. Our Cloud VPS runs on Ceph-backed storage with included Proxmox Backup Server, which complements the local backup script above with deduplicated off-cluster snapshots. For a vault you may rely on for years, predictable infrastructure and quick operator response matter more than headline benchmarks.

Hai trovato utile questa risposta? 0 Utenti hanno trovato utile questa risposta (0 Voti)