[Vaultwarden](https://github.com/dani-garcia/vaultwarden) is an open-source, Rust-rewritten implementation of the Bitwarden server. It speaks the official Bitwarden protocol, so every Bitwarden client (web, desktop, mobile, browser extensions, CLI) works against it unchanged — but you run it on your own server, store your own data, and stop paying $10 a month for premium features that the official Bitwarden Free tier holds back.

This guide takes you from a fresh VPS to a hardened Vaultwarden instance running at `vault.yourdomain.com`, with TLS, an admin panel, push notifications for mobile clients, and a backup strategy that doesn't lose your entire vault on a bad disk. It is opinionated about the production setup the official quickstart leaves out.

## Why self-host Vaultwarden

The trade-off vs Bitwarden's hosted service is straightforward:

- **Cost.** Bitwarden Free is genuinely useful but capped (no TOTP, no organization sharing, no premium reports). Bitwarden Premium is $10/year per individual or $40/year for families. Vaultwarden gives you all of those features — TOTP, attachments, emergency access, organizations, premium reports — for the cost of a $5 VPS.
- **Sovereignty.** Your encrypted vault never leaves a server you control. Bitwarden's hosted service is highly trusted (zero-knowledge architecture, audited), but if no third party ever touches the encrypted blob is your bar, self-hosting is the only answer.
- **Performance and latency.** A small VPS in your region serves the API faster than Bitwarden's transatlantic infrastructure. Mobile autofill feels snappier.
- **Single source for your team.** Agencies and small companies often want a private vault without paying per-seat. Vaultwarden organizations cover that.

The trade-offs are also honest:

- **You operate it.** Bitwarden handles backups, uptime, security patches, and key rotation. With Vaultwarden, that is on you.
- **No iOS app review parity.** The mobile clients connect to your server fine, but you do not get Bitwarden's enterprise compliance attestations.
- **Backups are non-negotiable.** Lose the SQLite or Postgres data file without backups and every password is gone. There is no recovery.

If you can commit to backups and a 30-minute monthly upgrade window, Vaultwarden is one of the highest-leverage self-hosted services you can run.

## Why Docker

Vaultwarden ships an official Docker image (`vaultwarden/server:latest`) that the project itself recommends as the supported install path. The native binary install is documented but second-class — every release ships a Docker image first. The image is small (~50 MB), the data lives in a single mounted volume, and upgrades are a `docker compose pull && docker compose up -d` away.

If you have followed our [self-host Paperclip with Docker](https://www.deployhq.com/blog/self-host-paperclip-vps-docker-deployhq), [n8n on Alibaba Cloud Linux 3](https://www.deployhq.com/blog/deploying-n8n-on-alibaba-cloud-using-docker), or [self-host Nextcloud on a VPS](https://www.deployhq.com/blog/self-host-nextcloud-like-a-pro-deployhq-contabo-vps-tutorial) guides, the playbook here is identical — only the image and env vars change.

## Prerequisites

- A VPS with at least 1 vCPU and 1 GB RAM (Vaultwarden is genuinely lightweight). 25 GB SSD covers years of vault growth.
- A domain or subdomain (`vault.yourdomain.com`) with DNS pointing to the VPS.
- Docker Engine and the Compose plugin on the VPS — see [the easiest way to deploy on a VPS](https://www.deployhq.com/blog/what-s-the-easiest-way-to-deploy-on-a-vps) for the broader setup pattern.
- An SMTP service (Postmark, SendGrid, Amazon SES, or your provider) for invitations, password hints, and 2FA fallback emails. Free tiers are fine.
- A reverse proxy on the VPS — we use Caddy in this guide. Concepts are covered in our [reverse proxy 101](https://www.deployhq.com/blog/what-is-a-reverse-proxy-nginx-apache-and-caddy-explained).

Install Docker on a fresh Ubuntu VPS:

```
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
```

Log out and back in for the group change to apply.

## The Compose stack

Vaultwarden's official quickstart shows a one-line `docker run`. For production, the Compose file below adds Postgres (the SQLite default works but is not what you want for a multi-user setup), proper volume management, healthchecks, and an internal-only port binding so Caddy is the only thing on the public internet.

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

```
services:
  vaultwarden:
    image: vaultwarden/server:1.32-alpine
    container_name: vaultwarden
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    environment:
      DOMAIN: "https://vault.${DOMAIN_BASE}"
      DATABASE_URL: "postgresql://vault:${DB_PASSWORD}@db:5432/vaultwarden"
      ADMIN_TOKEN: "${ADMIN_TOKEN}"
      SIGNUPS_ALLOWED: "false"
      INVITATIONS_ALLOWED: "true"
      WEBSOCKET_ENABLED: "true"
      SENDS_ALLOWED: "true"
      EMERGENCY_ACCESS_ALLOWED: "true"
      SMTP_HOST: "${SMTP_HOST}"
      SMTP_FROM: "${SMTP_FROM}"
      SMTP_PORT: "587"
      SMTP_SECURITY: "starttls"
      SMTP_USERNAME: "${SMTP_USERNAME}"
      SMTP_PASSWORD: "${SMTP_PASSWORD}"
      LOG_LEVEL: "warn"
      EXTENDED_LOGGING: "true"
    volumes:
      - vw-data:/data
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17-alpine
    container_name: vaultwarden-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: vault
      POSTGRES_DB: vaultwarden
      POSTGRES_PASSWORD: "${DB_PASSWORD}"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U vault"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  vw-data:
  pgdata:
```

Three details worth flagging.

First, **port 80 binds to `127.0.0.1`, not `0.0.0.0`**. Without that prefix, Docker exposes Vaultwarden directly on the public internet, bypassing your `ufw`/`firewalld` rules. Caddy on the same host proxies inbound HTTPS to it.

Second, **the image is pinned to a version tag** (`1.32-alpine`), not `:latest`. With a password manager, the version that happened to be in the registry when Watchtower last ran is the wrong upgrade strategy. Pin and upgrade deliberately.

Third, **`SIGNUPS_ALLOWED: "false"`** is critical for any vault that lives on the public internet. Without it, anyone who finds the URL can register and start using your server's storage. Use `INVITATIONS_ALLOWED: "true"` and invite users via the admin panel.

Generate a strong `ADMIN_TOKEN` — this protects the admin panel at `/admin`:

```
openssl rand -hex 64
```

Vaultwarden recommends an Argon2-hashed token in 1.30+. Generate one with:

```
docker run --rm -it vaultwarden/server:1.32-alpine /vaultwarden hash
```

Paste the resulting `$argon2id$...` string into `ADMIN_TOKEN` (escape any `$` if you put it in a shell-evaluated `.env`).

Save the secrets to `/opt/vaultwarden/.env`:

```
DOMAIN_BASE=yourdomain.com
DB_PASSWORD=$(openssl rand -hex 32)
ADMIN_TOKEN=<the argon2 hash from above>
SMTP_HOST=smtp.postmarkapp.com
SMTP_FROM=vault@yourdomain.com
SMTP_USERNAME=<your postmark token>
SMTP_PASSWORD=<your postmark token>
```

`chmod 600 /opt/vaultwarden/.env`. This file is the keys to the kingdom.

## TLS via Caddy

Vaultwarden absolutely requires HTTPS. The Bitwarden clients refuse to connect over plain HTTP, and any password manager served over an unencrypted channel is malpractice anyway.

Install Caddy (see [our Caddy/Nginx/Apache comparison](https://www.deployhq.com/blog/nginx-vs-apache-vs-caddy-choosing-the-right-web-server) for why we usually pick Caddy) and write `/etc/caddy/Caddyfile`:

```
vault.yourdomain.com {
    reverse_proxy 127.0.0.1:8080

    # WebSocket endpoint for live notifications
    @websockets {
        path /notifications/hub
    }
    reverse_proxy @websockets 127.0.0.1:8080

    encode gzip
    log {
        output file /var/log/caddy/vaultwarden.log
        format json
    }

    header {
        Strict-Transport-Security "max-age=63072000"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "no-referrer"
    }
}
```

`sudo systemctl reload caddy`. Caddy fetches a Let's Encrypt cert on first request and renews automatically.

Start the stack:

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

Visit `https://vault.yourdomain.com`. You should see the Bitwarden login screen.

## First-run: create your account, lock the door

Because we set `SIGNUPS_ALLOWED: "false"`, you cannot register via the public form. Two options:

**Option A — temporarily allow signups.** Set `SIGNUPS_ALLOWED: "true"`, restart, register your account, then flip it back to `false` and restart again. Crude but quick.

**Option B — invite yourself via the admin panel.** Visit `https://vault.yourdomain.com/admin`, paste your `ADMIN_TOKEN`, click Users → Invite, enter your email. Click the link in the email and finish account setup.

Option B is the operationally clean version — admin invitations leave an audit trail, and you never have public signups enabled even briefly.

Once you're in, install the Bitwarden clients on your devices and configure the server URL (in the app, before logging in: Self-hosted environment → Server URL: https://vault.yourdomain.com). Your existing Bitwarden vault export imports cleanly via Tools → Import data.

## Push notifications for mobile clients

Without push, your iOS and Android Bitwarden apps get vault changes by polling — which means stale data and slow autofill. To get real push:

1. Register for a free Bitwarden push subscription at [bitwarden.com/host/](https://bitwarden.com/host/) (the same form Bitwarden uses for self-hosted Bitwarden Server).
2. They email you `installation_id` and `installation_key`.
3. Add to `.env`:`
PUSH_ENABLED=true
PUSH_INSTALLATION_ID=<your id>
PUSH_INSTALLATION_KEY=<your key>
PUSH_RELAY_URI=https://push.bitwarden.com
`
4. Restart Vaultwarden. Mobile clients now receive push notifications via Bitwarden's relay (your encrypted blobs never go through it; only the vault changed trigger does).

## Backups: the part nobody covers

A Vaultwarden instance without backups is a time bomb. The single Postgres volume (`pgdata`) plus the `vw-data` volume (which holds attachments, the RSA keys, and the Sends store) ARE your vault. Lose them, lose everything.

Minimum viable backup:

```
#!/bin/bash
# /usr/local/bin/vaultwarden-backup.sh
set -euo pipefail

BACKUP_DIR=/var/backups/vaultwarden
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"

# Postgres dump
docker exec vaultwarden-db pg_dump -U vault vaultwarden | \
  gzip > "$BACKUP_DIR/db-$TIMESTAMP.sql.gz"

# Data volume (RSA keys, attachments, sends)
docker run --rm -v vaultwarden_vw-data:/data -v "$BACKUP_DIR:/backup" \
  alpine tar czf "/backup/vw-data-$TIMESTAMP.tar.gz" -C /data .

# Retain last 30 days
find "$BACKUP_DIR" -type f -mtime +30 -delete
```

Schedule it via cron at `0 3 * * *`. **Then ship the backups off-host** — to S3, B2, or another VPS — using `restic` or `rclone`. A backup that lives only on the server it backs up is not a backup; it is a hostage situation waiting for a disk failure.

Test restoration once a quarter. The first time you discover your backup script silently failed for six months should not be the day your VPS dies.

## Operational concerns

A few things worth setting up before you forget about the box:

- **Fail2ban for the admin panel** and the login endpoint. Vaultwarden ships logs with explicit [ERROR][...login attempt failed] markers; the [Vaultwarden wiki has a ready-made jail config](https://github.com/dani-garcia/vaultwarden/wiki/Fail2Ban-Setup). Without this, the `/admin` endpoint is a brute-force target.
- **Rotate `ADMIN_TOKEN` quarterly.** Roll a new Argon2 hash, update `.env`, restart. Cheap, and it limits blast radius if the token leaks.
- **Disable `WEBSOCKET_ENABLED` only if you have to.** It powers real-time vault updates across logged-in clients; turning it off is a noticeable downgrade.
- **Monitor disk on the data volume.** Attachments and the Sends feature can grow over time. Set a `df` alert at 80% to avoid waking up to a full disk.
- **Patch on a schedule.** Subscribe to the [Vaultwarden releases feed](https://github.com/dani-garcia/vaultwarden/releases). Pin to a specific patch tag (`1.32.7-alpine`), test upgrades on a staging VPS first, then promote.

## Continuous deployment with DeployHQ

If you maintain a fork of the Vaultwarden repo (custom branding, patched defaults, additional adapters) or simply want every upgrade to be a `git push` instead of an SSH session, the same continuous-deployment pattern from our Paperclip walkthrough above applies:

1. Fork [`dani-garcia/vaultwarden`](https://github.com/dani-garcia/vaultwarden) (or just keep your `docker-compose.yml` and `Caddyfile` in a private deploy repo — for most users this is enough).
2. Connect the repo to [DeployHQ](https://www.deployhq.com) and configure a [Docker build environment](https://www.deployhq.com/features/docker-builds) — or skip the build and have [DeployHQ](https://www.deployhq.com) deploy only the compose file via SSH.
3. Add the VPS as an SSH server.
4. [Deploy](https://www.deployhq.com) command: `docker compose -f /opt/vaultwarden/docker-compose.yml --env-file /opt/vaultwarden/.env pull && docker compose -f /opt/vaultwarden/docker-compose.yml --env-file /opt/vaultwarden/.env up -d`.
5. Push to your `production` branch when you want to upgrade. Vaultwarden 1.32.6 → 1.32.7 is now a `git push` away, with the upgrade visible in DeployHQ's audit log.

## Wrapping up

You now have Vaultwarden on a VPS with TLS, admin panel access, push notifications, and a backup strategy that survives a disk failure. For under $10/month in VPS plus SMTP costs, you have an unlimited-user Bitwarden-compatible password manager that nobody else can read or revoke.

If you're running this for a small team or family, [start a free](https://www.deployhq.com/signup)[DeployHQ](https://www.deployhq.com) trial so the next Vaultwarden upgrade is a `git push` instead of an SSH session. Pricing is on the [plans page](https://www.deployhq.com/pricing); the [agency plan](https://www.deployhq.com/for-agencies) covers running Vaultwarden plus a dozen other self-hosted apps across multiple client VPSes.

Questions about Vaultwarden, backup strategy, or wiring up automated upgrades? Email us at [support@deployhq.com](mailto:support@deployhq.com) or ping [@deployhq](https://x.com/deployhq) on X.

