Self-Hosting Vaultwarden: Ditching 1Password for Total Password Ownership

By Anas Semesmieh · May 22, 2026 · Homelab · Self-Hosting · Security

I used 1Password for years. It worked well — until the price hikes. Each increase was a reminder that I was renting access to my own credentials. My passwords lived on someone else's infrastructure, behind someone else's pricing decisions. When 1Password raised prices again, I decided to stop paying for what I could own outright.

Today I run Vaultwarden — a lightweight, Rust-based server compatible with the official Bitwarden ecosystem. It costs me nothing beyond the electricity my homelab already draws. My passwords are stored locally, backed up nightly to a NAS, and accessible from every device through the same Bitwarden apps I'd use with a cloud subscription.

Why Self-Host Your Password Manager?

The argument for self-hosting passwords comes down to three things:

Is it safe? Self-hosting a password manager requires discipline: keep the image updated, enforce HTTPS, maintain backups, and restrict network access. If you already run a homelab with proper backup automation, you're well-positioned for this.

What Made the Switch Easy

Two things eliminated any friction in moving away from 1Password:

1. Painless migration. 1Password exports to .1pux format, and Bitwarden's import tool handles it natively. I exported my entire vault — logins, secure notes, credit cards, identities — and imported it into Vaultwarden in under two minutes. Nothing was lost, nothing needed manual re-entry.

2. The Bitwarden app ecosystem. Because Vaultwarden is API-compatible with Bitwarden, I use the official Bitwarden apps on every platform — iOS, Android, macOS, Windows, Linux, and browser extensions for Chrome, Firefox, and Safari. The UX is identical to what you'd get with a Bitwarden cloud subscription. Autofill works, biometric unlock works, TOTP codes work. My family didn't even notice the backend changed.

This is the key insight: you're not giving up convenience by self-hosting. The client experience is the same. The only thing that changes is where the data lives — and now it lives with you.

Architecture Overview

My setup runs on a Dell Optiplex micro PC (the same host that runs the rest of my homelab). The architecture is simple:

┌─────────────────┐     Cloudflare Tunnel     ┌───────────────────┐
│  Bitwarden Apps │ ◄──────────────────────── │    Optiplex Host  │
│  (all devices)  │     vault.semesmieh.com    │   Vaultwarden:80  │
└─────────────────┘                            └───────────────────┘
                                                        │
                                                  NFS mount + rsync
                                                   Nightly 3AM
                                                        │
                                                        ▼
                                               ┌───────────────────┐
                                               │   Synology NAS    │
                                               │  /volume1/docker/ │
                                               │  (active standby) │
                                               └───────────────────┘

Implementation Guide

Docker Compose

The primary instance is a single container. Here's the compose file (secrets injected via environment variables):

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    user: "1000:1000"
    ports:
      - "8081:80"
    volumes:
      - ./data:/data
    environment:
      DOMAIN: "https://vault.semesmieh.com"
      PUSH_ENABLED: "true"
      PUSH_INSTALLATION_ID: "${PUSH_INSTALLATION_ID}"
      PUSH_INSTALLATION_KEY: "${PUSH_INSTALLATION_KEY}"
      SMTP_HOST: smtp.gmail.com
      SMTP_FROM: "${SMTP_FROM}"
      SMTP_PORT: "587"
      SMTP_SECURITY: starttls
      SMTP_USERNAME: "${SMTP_USERNAME}"
      SMTP_PASSWORD: "${SMTP_APP_PASSWORD}"

Key decisions:

Push Notifications

Vaultwarden supports Bitwarden's push notification relay so vault changes sync instantly across devices (no manual refresh needed). To enable it:

  1. Register at bitwarden.com/host to get an installation ID and key.
  2. Set PUSH_ENABLED=true along with your PUSH_INSTALLATION_ID and PUSH_INSTALLATION_KEY in the compose environment.
  3. Ensure the DOMAIN variable matches the external URL (the push relay validates this).

Reference: Vaultwarden Wiki — Enabling Push Notifications

SMTP (Email Notifications)

Email is used for account verification, 2FA recovery, and emergency access invitations. I use a Gmail App Password:

Reference: Vaultwarden Wiki — SMTP Configuration

Cloudflare Tunnel (External Access)

Rather than opening ports or managing dynamic DNS, I use a Cloudflare Tunnel to expose Vaultwarden externally. The tunnel runs as a lightweight cloudflared daemon that creates an outbound-only connection to Cloudflare's edge — no inbound firewall rules needed.

The tunnel routes vault.semesmieh.comhttp://localhost:8081. Cloudflare handles TLS termination, DDoS protection, and access policies.

Setup guide: Cloudflare Tunnel — Get Started

Backup & Disaster Recovery

SQLite databases require special care during backup — you can't just cp a live database file. Vaultwarden includes a built-in backup command that uses SQLite's Online Backup API to produce a consistent snapshot without stopping the service.

NFS Mount to NAS

The Optiplex mounts the Synology NAS's Docker volume over NFS. This gives the backup script direct filesystem access to the NAS — no SSH keys or remote rsync daemons needed:

# /etc/fstab entry
10.0.0.10:/volume1/docker /mnt/nas_docker nfs defaults,nofail,_netdev,x-systemd.automount 0 0

Key flags:

Reference: Arch Wiki — NFS Mount via fstab

The Backup Script

This runs every night at 3 AM via a cron job in /etc/cron.d/vaultwarden-backup:

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/home/anas/vaultwarden/data"
NAS_DEST="/mnt/nas_docker/vaultwarden/data"

# Step 1: Safe SQLite backup via Vaultwarden's built-in command
docker exec vaultwarden /vaultwarden backup

# Step 2: Remove stale WAL/SHM on NAS
rm -f "$NAS_DEST/db.sqlite3-wal" "$NAS_DEST/db.sqlite3-shm"

# Step 3: Copy clean backup as the main DB on NAS
BACKUP_FILE=$(ls -t "$BACKUP_DIR"/db_*.sqlite3 | head -1)
cp "$BACKUP_FILE" "$NAS_DEST/db.sqlite3"

# Step 4: Rsync attachments, config, keys (exclude DB files and icon cache)
rsync -az --no-perms --no-owner --no-group \
      --exclude='db.sqlite3' \
      --exclude='db.sqlite3-wal' \
      --exclude='db.sqlite3-shm' \
      --exclude='db_*.sqlite3' \
      --exclude='icon_cache' \
      "$BACKUP_DIR/" "$NAS_DEST/"

# Step 5: Cleanup local backup file
rm -f "$BACKUP_FILE"

The cron entry (in /etc/cron.d/vaultwarden-backup):

# /etc/cron.d/vaultwarden-backup
0 3 * * * root /home/anas/vaultwarden/backup-to-nas.sh >> /home/anas/vaultwarden/backup.log 2>&1

Because the NAS volume is an NFS mount, the rsync and copy operations write directly to the NAS filesystem — no network transfer overhead beyond standard NFS. The entire backup completes in under a second for a typical vault.

NAS Active Standby

The NAS holds a complete, always-current copy of the Vaultwarden data — synced nightly over the NFS mount via rsync. Because the data is already sitting on the NAS filesystem, failover is nearly instantaneous:

  1. SSH into the NAS and start the standby container: docker compose -f docker-compose.nas-standby.yml up -d
  2. Update the Cloudflare Tunnel to point vault.semesmieh.com at the NAS IP (10.0.0.10).
  3. Vaultwarden comes up immediately with data at most 24 hours old — no restore process, no downloading backups.

The key enabler here is that the rsync writes directly to the path the standby container will mount (/volume1/docker/vaultwarden/data). There's no extraction step. The data is always ready to serve.

The standby compose uses the NAS volume path and identical environment variables. Full config is in my homelab repo.

Migrating from 1Password

The migration took less than five minutes:

  1. Export from 1Password — In the 1Password desktop app: File → Export → choose .1pux format. This includes logins, secure notes, credit cards, and identities.
  2. Import into Vaultwarden — Log into your Vaultwarden web vault → Tools → Import Data → select "1Password (1pux)" as the format → upload the file.
  3. Verify — Spot-check a few entries. TOTP seeds, custom fields, and attachments all came across intact for me.
  4. Install Bitwarden apps — On each device, install the Bitwarden app and set the server URL to your self-hosted domain (https://vault.semesmieh.com) before logging in.
  5. Decommission 1Password — Once you've verified everything, cancel the subscription and delete the 1Password account.
Tip: Keep the 1Password export file in a secure location for 30 days after migration as a safety net. Then securely delete it — it contains all your secrets in plaintext.

References

Closing Thought

Passwords are the keys to your digital life. Trusting them to a vendor means trusting their pricing, their security practices, and their continued existence. Self-hosting with Vaultwarden gives you total ownership — your data lives on your hardware, backed up on your terms, accessible through battle-tested Bitwarden clients that work everywhere.

The best part? Moving away from a vendor-managed solution wasn't painful. The migration was trivial, the apps are the same, and the ongoing maintenance is a single Docker container and a cron job. Sometimes the self-hosted alternative really is just better.