Skip to content
Serverküche
Search

Loading search … (only available on the published site).

Applications Difficulty: Intermediate

Vaultwarden: your own password manager behind Traefik

Self-host Vaultwarden: a lean, Bitwarden-compatible password manager behind Traefik with HTTPS, an admin panel and an encrypted backup.

· 10 min read ·Duration: approx. 45 minutes
Table of contents

Passwords belong in a password manager – but do they have to live in the cloud of some third-party provider? With Vaultwarden you host your own, encrypted and under your control. It speaks the Bitwarden language, so you use the familiar Bitwarden apps and browser extensions – they just point at your server.

What are we building?

By the end, Vaultwarden 1.36 runs in a container behind your Traefik proxy, reachable at https://vault.YOUR_DOMAIN with a valid HTTPS certificate. Vaultwarden is a lean reimplementation of the Bitwarden server written in Rust: API-compatible, but frugal enough to run on a small VPS. You connect the official Bitwarden clients (browser extension, phone app, desktop) with your server, store your credentials – end-to-end encrypted – and manage the service via a secured admin panel.

Your passwords thus live on your server, in your country, under your backup – and you pay no subscription fee for features like 2FA or attachments.

Vaultwarden ≠ Bitwarden

Vaultwarden is an unofficial, community-driven project that reimplements the Bitwarden server API. It’s not affiliated with Bitwarden Inc. For private and small-team use it’s excellent; the clients (apps, extensions) are the real, official ones from Bitwarden.

Prerequisites

  • A running Traefik reverse proxy with the shared proxy network and the Let’s Encrypt resolver le – exactly the setup from the tutorial reverse proxy with Traefik. Without Traefik this recipe doesn’t work: Vaultwarden requires HTTPS.
  • A subdomain, e.g. vault.YOUR_DOMAIN, whose DNS record (A/AAAA) points to your server IP – see connecting a domain to your server.
  • A working backup of your server. A password manager is the place where data loss hurts most – if you haven’t yet, first set up backups with Restic.

If you want to combine several services later, the server calculator helps with the right server size.

🍳 Recommendation Ad

VPS 1000 G12

4 vCores · 8 GB RAM · 256 GB NVMe

from €10.36/month

Vaultwarden is so frugal that even the smallest VPS is easily enough.

Go to netcup →

💶 5 € voucher for new netcup customers: 36nc17844976032 (new customers only, no domains)

Step by step

Step 1: Generate the ADMIN_TOKEN

Vaultwarden has an admin panel at /admin. Access to it is protected by an ADMIN_TOKEN. You should not store it in plaintext, but as a hash – then your panel password appears nowhere readable in the Compose file.

Vaultwarden brings its own command for this. We run it briefly in a throwaway container:

Terminal
docker run --rm -it vaultwarden/server:1.36.0 /vaultwarden hash --preset owasp

The command asks you twice for a password (input stays invisible) and then outputs an Argon2id hash – a long string that starts like this:

Ausgabe
Generate an Argon2id PHC string using the 'owasp' preset.

Password:
Confirm Password:

ADMIN_TOKEN='$argon2id$v=19$m=19456,t=2,p=1$FkxFEQ64Wy4zlQOWMI1fJ...$TxULe6MSND3By6GPVPKB1...'

Copy out the complete string between the quotes (including the $argon2id$… parts) – you need it in a moment. The password you entered here is your admin panel password; store it in your password manager (in the old one, for now).

Why the detour via the hash?

You could also set the ADMIN_TOKEN as a plaintext password. The hash is safer, though: even someone who gets hold of your compose.yaml can’t compute your panel password back from it. --preset owasp chooses Argon2 parameters per the current OWASP recommendation.

Step 2: Create the compose.yaml

Create a dedicated folder and change into it:

Terminal
mkdir -p ~/vaultwarden && cd ~/vaultwarden

Create the compose.yaml. Replace vault.YOUR_DOMAIN with your real subdomain and the ADMIN_TOKEN with the hash from step 1:

YAML
services:
  vaultwarden:
    image: vaultwarden/server:1.36.0
    volumes:
      - ./vw-data:/data
    environment:
      DOMAIN: "https://vault.YOUR_DOMAIN"
      SIGNUPS_ALLOWED: "true"
      ADMIN_TOKEN: "$$argon2id$$v=19$$m=19456,t=2,p=1$$FkxFE...$$TxULe..."
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vaultwarden.rule=Host(`vault.YOUR_DOMAIN`)"
      - "traefik.http.routers.vaultwarden.entrypoints=websecure"
      - "traefik.http.routers.vaultwarden.tls.certresolver=le"
      - "traefik.http.services.vaultwarden.loadbalancer.server.port=80"
    networks:
      - proxy
    restart: unless-stopped

networks:
  proxy:
    external: true

The most important lines in detail:

  • image: vaultwarden/server:1.36.0 – we deliberately pin the version instead of taking latest. That way you update in a controlled manner (see “Maintenance”). For an even smaller image there’s also :1.36.0-alpine.
  • volumes: ./vw-data:/data – this is where your database (db.sqlite3), the encryption keys and the attachments live. This volume is mandatory. Without a volume Vaultwarden deliberately refuses to start, so you don’t lose your data in an ephemeral container.
  • DOMAIN – the full HTTPS URL. Vaultwarden needs it for WebAuthn/2FA and invitation links, among other things. Must match your router rule exactly.
  • SIGNUPS_ALLOWED: "true" – allows registration for now so you can create your first account. We turn that off again shortly.
  • ADMIN_TOKEN – your hash from step 1. Important: in a Compose file every dollar sign must be doubled ($$$), otherwise Compose tries to interpret it as a variable. So $argon2id$… becomes $$argon2id$$….
  • loadbalancer.server.port=80 – Vaultwarden listens on port 80 in the container. This line tells Traefik explicitly where to forward internally, instead of guessing the target port from the image – the same pattern as with every app behind Traefik.
  • No ports: – as with every app behind Traefik, Vaultwarden is only reachable via the proxy, never directly from outside.

Step 3: Start it and the first call

Start the container:

Terminal
docker compose up -d

On the first start, Vaultwarden creates the data directory and initializes the database. Take a look at the log:

Terminal
docker compose logs -f vaultwarden

At the end you should see this line – it confirms the service is running:

Ausgabe
[INFO] Rocket has launched from http://0.0.0.0:80

With Ctrl+C you leave the log view again (the container keeps running). Now open https://vault.YOUR_DOMAIN in the browser. On the first access Traefik fetches the Let’s Encrypt certificate – that can take a few seconds. After that the Bitwarden web interface appears.

Click Create account and create your first account – with your email address and a name:

The Vaultwarden web interface shows the form to create a new account with email address and name
Create account – Vaultwarden uses the official Bitwarden web interface

In the next step you set your master password. That’s the one key that unlocks your entire vault:

The Vaultwarden page for setting a strong master password with a password field and strength indicator
The master password encrypts your entire vault – choose it carefully

The master password is not recoverable

Your master password is never transmitted to the server; it decrypts your vault locally. If you forget it, all passwords stored in it are lost – no one, not even you, can recover them. Choose a long, unique passphrase and keep it in a safe place (e.g. printed out in a safe).

Step 4: Close registration again

Once your account is set up, you want to prevent strangers from registering too – your Vaultwarden is, after all, open on the internet. Set SIGNUPS_ALLOWED to false in the compose.yaml:

YAML
    environment:
      DOMAIN: "https://vault.YOUR_DOMAIN"
      SIGNUPS_ALLOWED: "false"
      ADMIN_TOKEN: "$$argon2id$$..."

And apply the change:

Terminal
docker compose up -d

Compose detects the changed environment variable and restarts the container. From now on the login page rejects new registrations. Further users (e.g. for the family) you invite specifically via the admin panel when needed – more on that shortly.

Don't forget

An open SIGNUPS_ALLOWED: "true" is the most common misconfiguration with self-hosted Vaultwarden. Anyone who knows your domain could otherwise create an account. This step is mandatory, not an extra.

Step 5: The admin panel

Open https://vault.YOUR_DOMAIN/admin and log in with the password you entered in step 1 when generating the hash (not with the hash itself). You land in the management interface:

The Vaultwarden admin panel with the areas General, SMTP Email and Backup Database
The admin panel – here you manage users, SMTP and global settings

Here you control the service centrally:

  • Users – view existing users, invite new ones (even with SIGNUPS_ALLOWED=false), deactivate accounts.
  • Settings → SMTP Email Settings – enter your mail server’s credentials here so Vaultwarden can send invitations and 2FA codes by email. Without SMTP, invitation by link works too, but email-based 2FA doesn’t.
  • Diagnostics – shows you whether your DOMAIN is set correctly and whether Vaultwarden is reachable from outside – handy for troubleshooting.

Settings in the panel vs. environment variables

What you save in the admin panel is stored in vw-data/config.json and overrides the environment variables from the compose.yaml. Decide per setting on one way to avoid confusion. For the basic configuration (domain, token, signups) we deliberately stick with the Compose file – it’s versionable and reproducible.

Step 6: Connect the clients

Now comes the real benefit. Install the official Bitwarden app or browser extension (from the respective app or add-on store). Before you log in, you switch the server:

  1. In the app/extension, before logging in, open the settings for the self-hosted environment (gear icon or “Region: Self-hosted”).
  2. Enter https://vault.YOUR_DOMAIN as the server URL and save.
  3. Now log in with your email and your master password – the app talks to your server from now on.

The nice part: it’s the same mature clients as with commercial Bitwarden. Autofill, password generator, secure notes, attachments and 2FA storage work the same way – only the data lives on your server.

Import existing passwords: if you’re switching from another manager (or the browser’s password store), you don’t have to retype everything. Export your entries there as CSV or JSON and import them via the web interface under Tools → Import data. Vaultwarden understands the export formats of the common managers (KeePass, LastPass, 1Password, Chrome/Firefox, etc.) directly.

Securely delete export files afterwards

An export CSV contains all passwords in plaintext. Delete the file immediately after a successful import – and empty the trash. Never leave it lying in Downloads or a cloud folder.

Enable two-factor authentication

In your vault’s account settings, enable 2FA (e.g. via an authenticator app). That way, even with a cracked master password, your vault isn’t immediately open. For email-based 2FA, SMTP must be set up in the admin panel first.

When things go wrong

The container won’t start, the log says something like Running without a persistent volume is not recommended. The volumes: mapping is missing. Vaultwarden deliberately refuses to start without a data directory so your passwords don’t end up in an ephemeral container. Add ./vw-data:/data as in step 2 and restart.

The web interface shows “You need to enable HTTPS!” or the login fails with crypto errors. Vaultwarden uses the browser’s Web Crypto API, which is only available in a secure context (real HTTPS). You opened the page over http:// or with an invalid certificate. Make sure Traefik fetched a valid Let’s Encrypt certificate (check the Traefik log) and that you reach the page over https://. The DOMAIN variable must also start with https://.

The admin panel rejects your password, even though it’s correct. Probably the dollar signs in the ADMIN_TOKEN aren’t doubled. In the compose.yaml every $ must become $$. Check with docker compose config how the token actually arrives (Compose shows the resolved value there). Also remember: at login you enter the password, not the hash.

The phone app can’t find the server or reports “Server URL invalid”. The server URL must be the full https:// address without a trailing path (https://vault.YOUR_DOMAIN). Also check whether the domain is reachable from outside via a browser and the certificate is valid – apps are stricter about certificate errors than browsers.

Despite SIGNUPS_ALLOWED=false, someone was able to register. The setting was probably set in the admin panel and overrides the environment variable, or the container wasn’t restarted after the change. Check the value under Settings → General settings in the panel and restart with docker compose up -d.

Maintenance & backups

  • Backups are non-negotiable for a password manager. Your entire vault is in the vw-data/ directory (SQLite database, keys, attachments). Back it up encrypted and off-site with Restic – add the folder ~/vaultwarden/vw-data to your backup sources. For a consistent database state, briefly run docker compose stop before the backup, or use the “Backup Database” function in the admin panel. A mere file copy while the container is running can catch an inconsistent state (SQLite writes into WAL files) – with a password manager that’s not a risk worth taking.

  • Apply updates in a controlled way. Because we pinned the version, you update deliberately: before the switch read the release notes, then raise the tag in the compose.yaml (e.g. to the next 1.x) and pull anew:

    Terminal
    docker compose pull && docker compose up -d

    Afterwards check the Rocket has launched line in the log again and test a login.

  • Keep registration closed. Occasionally check that SIGNUPS_ALLOWED is still false – always invite new users specifically via the admin panel.

  • Rotate the ADMIN_TOKEN if it might be compromised: generate a new hash with the hash command from step 1, replace it in the compose.yaml, restart.

  • Never lose the master password. There is no recovery. In a team/family setup, an emergency access can make sense – you set that up per vault in the account settings.

You might also like