Use case · Password manager

Vaultwarden self-hosted on a Cloud VPS

A self-hosted Vaultwarden is one container, vaultwarden/server, holding a SQLite database in a mounted /data folder, behind a reverse proxy that terminates HTTPS. Vaultwarden is not Bitwarden: it is an unofficial server implementation of the Bitwarden client API, written in Rust, that the official Bitwarden apps and browser extensions can log in to. The project states it has no association with Bitwarden, Inc. This page covers the Docker Compose setup from the Vaultwarden wiki, the three settings that matter for security, and the backup of the data folder.

See the VPS flavors

What Vaultwarden is, and what it is not

Vaultwarden reimplements the server side of the Bitwarden client API. Its wiki calls it an unofficial Bitwarden server implementation, aimed at deployments where the official, resource-heavy service is undesirable. The README lists the personal vault, Send, attachments, organizations with collections and groups, two-step login (authenticator, email, FIDO2 WebAuthn, YubiKey, Duo), emergency access and an admin backend. The web vault ships inside the container, modified for Vaultwarden.

Because it is a separate project, problems go to Vaultwarden's own issue tracker, discussions or Matrix room. The README tells users not to take them to Bitwarden's support channels, whichever client they use.

One consequence is maintenance. Bitwarden ships the clients, and mobile apps and browser extensions update themselves. When Bitwarden changes a client in a way the server must follow, Vaultwarden releases a matching version, and the wiki warns that a server left behind can break suddenly. A self-hosted vault is a box you keep current.

What the deployment needs

PieceWhat the Vaultwarden wiki says
Imagevaultwarden/server:latest on Docker Hub, also on ghcr.io and quay.io; latest-alpine is the same build on Alpine
DatabaseSQLite by default, the most widely used and tested backend; MySQL and PostgreSQL are supported
HTTPSRequired: the web vault uses the Web Crypto API, which browsers expose only in a secure context
TLS terminationA reverse proxy is recommended; Rocket's built-in TLS is not
CertificateLet's Encrypt via an ACME client; Caddy obtains one by itself
EmailOptional SMTP, for invitations and email two-step login

The wiki publishes no CPU or memory figure, so this page gives none. What it does make clear is that the service is one process with a file database, which is a small, steady job: the kind a virtual machine is sized for.

Why a Cloud VPS fits it

A Cloud VPS is a KVM guest on OpenStack hosts Server Room operates in New York. You get root over SSH with the keys you pasted at order time, a noVNC console from the dashboard for the day SSH does not answer, and reinstalls from the official cloud images of four Linux distributions. The Docker one-click app installs Docker Engine and the Compose plugin from Docker's own repository on first boot, which is everything the wiki's setup assumes.

  • New machines block outbound TCP 25 but leave 465 and 587 open, and 587 with STARTTLS is Vaultwarden's SMTP default.
  • A flavor can be grown later through support: one restart, addresses and data kept. It never shrinks, because a disk cannot.
  • Egress counts against a monthly allowance, ingress does not. A password vault sends very little.
  • vCPUs are shared, not pinned. Vaultwarden is mostly idle, so this matters less here than for a build server.

Setting it up with Docker Compose and Caddy

  1. Order the VPS with the Docker app and point a DNS A record, such as vault.example.com, at its address.
  2. Make sure ports 80/tcp (for the ACME HTTP-01 challenge), 443/tcp and 443/udp (HTTP/3) can reach the machine. If you have added a host firewall of your own, open them there.
  3. In a new directory, write the compose.yaml below and the Caddyfile after it. Only Caddy publishes ports; Vaultwarden is reachable only on the Compose network.
  4. Run docker compose up -d, open the domain over HTTPS and create your account, then everyone else's.
  5. Set SIGNUPS_ALLOWED to "false" and run docker compose up -d again. Environment values are bound to the container, so a plain restart does not apply them.
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: always
    environment:
      DOMAIN: "https://vault.example.com"
      SIGNUPS_ALLOWED: "true"
    volumes:
      - ./vw-data:/data

  caddy:
    image: caddy:2
    container_name: caddy
    restart: always
    ports:
      - 80:80
      - 443:443
      - 443:443/udp
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-config:/config
      - ./caddy-data:/data
    environment:
      DOMAIN: "https://vault.example.com"
      EMAIL: "admin@example.com"
      LOG_FILE: "/data/access.log"
{$DOMAIN} {
  log {
    level INFO
    output file {$LOG_FILE} {
      roll_size 10MB
      roll_keep 10
    }
  }
  tls {$EMAIL}
  encode zstd gzip
  reverse_proxy vaultwarden:80 {
    header_up X-Real-IP {remote_host}
  }
}

DOMAIN must carry the https:// scheme: Vaultwarden needs it for attachments and for the links in invitation emails.

The admin page, sign-ups and invitations

The /admin panel exists only when ADMIN_TOKEN is set, and whoever holds that token can delete users and remove their two-step login. Do not store it in plain text. Generate an Argon2id PHC string with the hasher built into the image, docker exec -it vaultwarden /vaultwarden hash, which asks for a password twice and prints a string starting $argon2id$. You log in to /admin with the password, not the string.

  • In compose.yaml, write each of the five $ signs in that string as $$, or Compose treats them as variables and sets a blank token. In a .env file, keep single $ signs inside single quotes.
  • With SIGNUPS_ALLOWED=false, organization owners and admins can still invite people. INVITATIONS_ALLOWED=false stops that; the admin page can always invite.
  • Vaultwarden can show password hints on the login page for servers without SMTP, which helps anyone guessing passwords. Set SHOW_PASSWORD_HINT: "false" in the Compose environment and run docker compose up -d.
  • Without two-step login, master passwords can be guessed online. The hardening guide suggests fail2ban, which is why the Caddyfile passes the real client address.

What to back up from /data

Path in the data folderWiki's verdict
db.sqlite3 (and -wal)Required: almost all vault state
attachments/Required: file attachments live only here
config.jsonRecommended if you used the admin page; holds secrets in plain text
rsa_key*Recommended: they sign login tokens; losing them logs everyone out
sends/, icon_cache/Optional

Copying a live SQLite file can catch it mid-write. Since 1.32.1 the image has its own consistent backup command, and the image has no cron, so the line below is the one for the host's crontab, run at least daily. Each run writes a timestamped db_*.sqlite3 into ./vw-data; copy that file and the other paths off the machine, encrypted. The wiki also advises against relying on filesystem or VM snapshots.

docker exec vaultwarden /vaultwarden backup

To restore, stop Vaultwarden, copy the backup file back as db.sqlite3 with attachments/, the rsa_key* files and config.json (if you have one), and delete any db.sqlite3-wal beside it, since a stale WAL can corrupt the restored database. Rehearse a restore now and then.

Updating it

docker compose pull then docker compose up -d replaces the container and keeps ./vw-data. Stay on latest rather than a pinned version unless you have a reason; the clients will not wait for you. The web vault is bundled with the image, so it always matches the server.

When a VPS is the wrong home for it

A VPS shares its physical host with other customers. If policy says a password store must sit on hardware nobody else uses, put the same Compose file on a dedicated server instead: root, an unmetered port and, on rack servers and blades, an out-of-band console. If the same machine is also meant to hold your files or run automations, the Nextcloud and n8n pages size those jobs, and a vault is easier to reason about on a box of its own. Either way, a vault reachable from the internet is only as safe as its master passwords, its second factors and a closed registration.

Questions

Is Vaultwarden the official Bitwarden server?

No. It is an independent, unofficial implementation of the Bitwarden client API, licensed AGPL-3.0, and the project says it is not associated with Bitwarden, Inc. The official Bitwarden clients can log in to it. Bugs go to Vaultwarden, not to Bitwarden support.

Can I reach it by IP address over plain HTTP?

The server will answer, but the web vault will not work: browsers only expose the Web Crypto API it relies on over HTTPS. Clients also tend to fail on self-signed certificates, which some platforms reject. Give it a DNS name and a Let's Encrypt certificate through a reverse proxy such as Caddy.

Do I need email set up?

No, but invitations, email two-step login and verification need it. On this VPS, outbound port 25 is closed and 465 and 587 are open, so point SMTP_HOST at a mail provider on 587 with SMTP_SECURITY=starttls, or on 465 with force_tls.

How large a VPS does Vaultwarden need?

The Vaultwarden wiki gives no CPU or memory requirement, so start with a small flavor and watch it. The flavor can be grown later through support without losing data or addresses, though it cannot be shrunk.

What happens if the VPS is lost?

The server's database, attachments and organization data exist only in /data. That is why the backup has to leave the machine: restore db.sqlite3, attachments/, the rsa_key files and config.json (if you have one) onto a new VPS and the vault comes back.

Is the admin page safe to leave on?

Only with a strong token stored as an Argon2id hash, over HTTPS. Admin sessions last 20 minutes by default, and changing the token does not end sessions already open. If you do not use it, unset ADMIN_TOKEN, make sure config.json holds no admin_token key, and recreate the container.

Run your own vault

Order a Cloud VPS with the Docker app, point a name at it, and the Compose file above does the rest.

See the VPS flavors

Related on Server Room

Other use cases