Self-hosting Vaultwarden on a small VPS: docker-compose, Caddy reverse proxy, and migrating from a paid Bitwarden plan

You’ve been paying $40/year for a Bitwarden family plan, or $36/year for an individual one. The product is excellent — there’s nothing wrong with what they’ve built. But you also have a $5/month VPS that’s already running a couple of small services, and you start to wonder: is hosting your own password manager actually viable? It’s the question that gets a thousand confused answers because there are two different things sometimes called “self-hosting Bitwarden”: the official Rust server (resource-heavy), and Vaultwarden, the unofficial Rust reimplementation that’s much lighter and what most self-hosters actually run.

Yes, it’s viable. Vaultwarden runs in about 50 MB of RAM, fits comfortably alongside other services on a small VPS, and uses the official Bitwarden mobile / desktop / browser apps without modification. Here’s the actual setup, the gotchas that bite, and how to migrate from the paid plan if that’s where you’re coming from.

What you need before starting

  • A small Linux VPS (1 GB RAM is fine; even 512 MB works). Oracle Cloud Free Tier, Hetzner CX11, a Raspberry Pi at home — all fine.
  • A domain name pointed at the VPS (e.g. vault.yourdomain.com). HTTP-only Vaultwarden is a non-starter — Bitwarden clients require HTTPS.
  • Docker + docker-compose installed.
  • A reverse proxy in front of it. I’ll show Caddy because it’s the simplest; nginx and Traefik work too.

docker-compose.yml

# /opt/vaultwarden/docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.yourdomain.com"
      SIGNUPS_ALLOWED: "false"           # CRITICAL — flip to true only briefly when adding a new user
      INVITATIONS_ALLOWED: "true"
      ADMIN_TOKEN: "REPLACE_WITH_LONG_RANDOM_STRING"
      WEBSOCKET_ENABLED: "true"
      LOG_LEVEL: "warn"
    volumes:
      - ./data:/data
    ports:
      - "127.0.0.1:8222:80"             # bind to localhost — Caddy is in front

  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy_data:/data
      - ./caddy_config:/config

Generate the ADMIN_TOKEN with openssl rand -base64 48 and paste it in. The admin token is what gates access to the /admin dashboard where you manage users.

Caddyfile

# /opt/vaultwarden/Caddyfile
vault.yourdomain.com {
    encode zstd gzip

    # Vaultwarden's websocket endpoint for live sync
    reverse_proxy /notifications/hub vaultwarden:3012
    # Everything else
    reverse_proxy vaultwarden:80

    # Optional: hide the /admin panel entirely from non-allowlisted IPs
    @adminblocked {
        path /admin*
        not remote_ip 192.0.2.1 203.0.113.5
    }
    respond @adminblocked 404
}

Replace the IP addresses in the not remote_ip line with your actual home + office IPs. The /admin panel is protected by the token, but exposing it publicly is unnecessary surface area — only you need to reach it.

Bring it up:

cd /opt/vaultwarden
docker compose up -d
docker compose logs -f          # watch the first start, look for "Rocket has launched"

Caddy will automatically obtain a Let’s Encrypt cert on first request to vault.yourdomain.com. Within ~30 seconds, the URL serves Vaultwarden’s web vault with a real cert.

Initial signup, then lock it down

  1. Visit https://vault.yourdomain.com in your browser.
  2. Click Create Account. Use a real email + a long passphrase. Lose this passphrase and you lose every credential. There is no recovery path — that’s by design.
  3. Sign in. Confirm everything works (create a test entry, copy/paste it).
  4. Edit docker-compose.yml, change SIGNUPS_ALLOWED: "true" back to "false". Restart: docker compose up -d.
  5. Now no one else can register. To add a family member, log into /admin with your token and use Invite User.

Migrating from the paid Bitwarden plan

  1. On Bitwarden’s web vault: Tools → Export Vault → JSON (encrypted). Save the file. Don’t use the unencrypted JSON unless you trust your laptop’s disk encryption.
  2. Sign into Vaultwarden’s web vault as your new account.
  3. Tools → Import Data → choose “Bitwarden (json)” → upload the file. If you exported encrypted, it’ll prompt for the original Bitwarden master password to decrypt.
  4. Verify the import: spot-check 3-5 entries (logins, secure notes, identities). Folders and TOTP codes carry over cleanly.
  5. Update the URL in your Bitwarden mobile / desktop / browser app: Settings → Serverhttps://vault.yourdomain.com. Log out, log in to the new server, the apps work identically.
  6. Once you’ve verified the new server for a week, cancel the paid plan.

The four gotchas that bite

  • Backups. The ./data directory is your entire vault. Snapshot it every night to somewhere off the VPS. A nightly tar -czf /backups/vw-$(date +%F).tgz /opt/vaultwarden/data + rclone copy to B2 / Jotta / Google Drive is fifteen minutes to set up and the only reason you have a self-hosted vault that survives the VPS dying.
  • Don’t expose /admin publicly. The token guards it, but every public service is a brute-force target. The not remote_ip Caddy rule above keeps the panel visible only to your IPs.
  • WebSocket support. If real-time sync between devices isn’t working (you change a password on phone, laptop doesn’t see it for 5 minutes), check that /notifications/hub proxies to port 3012 and WEBSOCKET_ENABLED=true in env. Cloudflare in front of your domain can also drop websockets unless you enable that.
  • Email setup is optional. Without an SMTP server, password resets and invites won’t email — you’ll have to communicate the invite link manually. For a single-family setup that’s fine; for a small-team setup, configure SMTP credentials in env (SMTP_HOST, SMTP_USERNAME, etc.).

What you give up vs the paid plan

Almost nothing user-facing. Vaultwarden supports passkeys, TOTP-as-2FA, hardware-key-as-2FA, organizations, collections, attachments, the file send feature. The differences are:

  • You’re now the operator. If the VPS goes down, your vault is unreachable until you bring it back. Worth it for the flexibility, expensive on the rare bad day.
  • Bitwarden’s professional security audits don’t apply to Vaultwarden — it’s a community project. Most people consider this a fair trade given that the actual encryption is end-to-end client-side and identical between the two; the server only stores ciphertext. But it’s a real consideration if you’re managing credentials for a regulated business.
  • Premium-tier features you may not be paying for anyway. Things like emergency access, encrypted Send, and security audits are enabled in Vaultwarden by default — what would cost you $10/year on Bitwarden Premium ships free here.

For a one-person or one-family setup, Vaultwarden + a $5 VPS + nightly off-server backups is one of the better trades in self-hosting. The vault is yours, the encryption is end-to-end, the apps are the official ones, and the maintenance overhead is roughly nothing — image updates twice a year, backups verify themselves.

Photo: Combination padlock by Modun Studio on Pexels.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.