Skip to content

Vaultwarden

Vaultwarden is a self-hosted password manager compatible with Bitwarden clients — browser extensions, mobile apps, and the desktop client. It can hold access credentials to everything else you run; treat setup and backup accordingly.

Written against 1.37.0 · no verification recorded against that version

Not exercised: a restore. The database, the RSA signing keys and the attachments have to come back from the same point in time, and that has not been performed here.

Needs: Traefik running, a domain, and SMTP credentials (email verification is on by default).

From apps/vaultwarden/:

  1. Terminal window
    cp .env.example .env
  2. Set your domain in .env:

    APP_TRAEFIK_HOST=vault.example.com
  3. Set SMTP — required before the first account can verify its email:

    VW_SMTP_HOST=smtp.example.com
    VW_SMTP_FROM=vault@example.com
    VW_SMTP_PORT=587
    VW_SMTP_SECURITY=starttls
    VW_SMTP_USERNAME=your-smtp-user
    VW_SMTP_PASSWORD=your-smtp-password
  4. Generate database passwords. Hex is a convention here, not a requirement: the password ends up inside a connection URL, and hex avoids having to URL-encode characters that would otherwise break it.

    Terminal window
    sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -hex 32)|" .env
    sed -i "s|^DB_ROOT_PASSWORD=.*|DB_ROOT_PASSWORD=$(openssl rand -hex 32)|" .env
  5. Generate the admin token. Vaultwarden accepts a plain-text token as well; this setup deliberately does not, because a plain token in .env is readable by anything that can read the file or inspect the container:

    Terminal window
    docker compose run --rm --no-deps app /vaultwarden hash

    Copy the full $argon2id$... output, then paste it into .env with every $ doubled (Compose escaping):

    VW_ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$...your-hash...
  6. Allow signups for your first account — you’ll turn this off again right after:

    VW_SIGNUPS_ALLOWED=true
  7. Start:

    Terminal window
    docker compose up -d
    docker compose logs -f

    Wait for Starting Vaultwarden with no errors — MariaDB has a health check Vaultwarden waits on.

Terminal window
docker compose ps # both containers healthy
curl -sI https://your-domain/alive # expect 200 OK

Open https://your-domain — the login page should load over TLS.

  1. Click Create Account, register, and verify your email.

  2. In .env, turn signups back off:

    VW_SIGNUPS_ALLOWED=false
    Terminal window
    docker compose up -d --force-recreate app

    Confirm it took effect against the container rather than against the page:

    Terminal window
    docker compose exec app env | grep SIGNUPS_ALLOWED # expect false

    Then reload the login page — “Create Account” should be gone.

  3. At https://your-domain/admin, set:

    Setting Value
    Allow new signups false
    Allow password hints false
    Block non-global IPs true
    Disable 2FA remember true
  4. Enable 2FA on your account: Settings → Security → Two-step login.

The installation above uses the default, APP_TRAEFIK_ACCESS=acc-tailscale — the vault is reachable only over Tailscale/VPN. Setting APP_TRAEFIK_ACCESS=acc-public makes it reachable from the open internet instead. That’s a deliberate change to your attack surface, not a default — decide it before exposing real credentials.

Do not add real credentials before a backup exists.

What to back up: volumes/mysql/ (database), volumes/data/attachments/, volumes/data/rsa_key.* (signing keys — without these, sessions break on restore even with an intact database), volumes/data/sends/.

Terminal window
docker exec -e MYSQL_PWD="$(grep DB_ROOT_PASSWORD .env | cut -d= -f2)" \
vaultwarden-db mariadb-dump -u root vaultwarden > backup-$(date +%Y%m%d).sql

The command above is a one-off dump. To have it run on a schedule alongside the data directories, and to hold the archives somewhere the server cannot reach, see backup and restore — Borgmatic on the host, with the database dump wired in as a pre-backup hook.

  1. Check the release notes for breaking changes.
  2. Back up all three parts together — the database dump, volumes/data/rsa_key.* and volumes/data/attachments/. A database-only snapshot is not something you can roll back to: restoring it without the matching signing keys breaks every session even though the vault contents are intact.
  3. Update APP_TAG in .env.
  4. Terminal window
    docker compose pull
    docker compose up -d
  5. Verify /alive and log in.

Email verification not arriving: send a test email from /admin → Settings, and check your SMTP provider’s outbound log if it fails.