Migrate Vaultwarden to PostgreSQL: Scale for Teams [2026]

August 25, 2026

If you have been self-hosting Vaultwarden using the default SQLite database, you may have encountered limitations as your user base expanded. While SQLite is efficient for single-user and small household applications, it struggles under the pressure of concurrent writes from multiple users, leading to slow synchronization, timeouts, and the dreaded “database is locked” errors. This guide is designed to assist Canadian IT administrators and self-hosting enthusiasts in transitioning their Vaultwarden installation from SQLite to PostgreSQL, thereby enabling multi-user organizations, group policies, and robust backup solutions—essentially bridging the gap between a personal vault and a production-ready password manager for teams.

Why Move Vaultwarden From SQLite to PostgreSQL

SQLite, being a single-file, serverless database, is the default choice for Vaultwarden due to its zero-configuration setup. However, its write-locking mechanism allows only one write transaction at a time across the entire database file. This limitation is negligible for small households but becomes a significant bottleneck for larger teams, resulting in failed vault item saves and delays in administrative tasks.

In contrast, PostgreSQL offers a client-server architecture that supports concurrent connections and row-level locking, making it a more suitable choice for teams. Vaultwarden has long supported PostgreSQL through its Diesel ORM layer, and the official documentation provides clear guidance on the necessary connection string syntax. Organizations experiencing performance issues with SQLite are directed toward PostgreSQL or MariaDB as a viable solution.

Moreover, PostgreSQL enhances data durability with features like point-in-time recovery, streaming replication for high availability, and established backup tools such as pg_dump. These capabilities are crucial for organizations that rely on Vaultwarden to store sensitive credentials.

Where to Run This: VPS Options for Canadian Teams

When deploying this stack, the choice of hosting provider can significantly impact latency and simplify data residency discussions. Below is a comparison of typical monthly costs in CAD for VPS options suitable for running Vaultwarden alongside a PostgreSQL container:

Provider Plan Size Approx. Monthly Cost (CAD) Canadian Data Centre
DigitalOcean 2 vCPU / 4GB RAM ~ Toronto (TOR1)
Vultr 2 vCPU / 4GB RAM ~ Toronto
OVHcloud 2 vCPU / 4GB RAM ~ Beauharnois, QC
Linode (Akamai) 2 vCPU / 4GB RAM ~ Toronto
Self-hosted on-prem hardware N/A (existing hardware) Electricity only Your own office/closet

A VPS with 2 vCPU and 4GB RAM provides ample resources for Vaultwarden and PostgreSQL, accommodating up to approximately 50 users. For larger teams, consider upgrading to 4 vCPU and 8GB RAM to prevent resource contention during peak usage.

Prerequisites and Versions You’ll Need

Before initiating the migration, ensure the following prerequisites are met to avoid common pitfalls:

  • An existing Vaultwarden deployment running version 1.34 or later (this guide is tested against 1.37.1) with a functioning SQLite database (db.sqlite3)
  • Docker Engine 27.x or later and Docker Compose v2.29 or later on a Linux host (preferably Ubuntu 24.04 LTS or Debian 12)
  • A PostgreSQL server, version 16 or 17, either as a Docker container (postgres:16-alpine) or a managed instance
  • pgloader version 3.6.x or later installed on the host
  • Root or sudo access to the server, plus SSH access if the server is remote
  • A verified, working backup of your current vw-data volume
  • A maintenance window of 60 to 90 minutes where users are asked to close their Vaultwarden clients
  • At least 2 GB of free disk space for the temporary export and the new PostgreSQL data directory

No new domain or reverse proxy configuration is required for this migration, as PostgreSQL operates alongside your existing Caddy or Nginx setup.

Step 1 — Back Up Your Existing Vaultwarden Data Volume

Backing up your data is crucial. Stop the container and archive the entire data directory to ensure you capture all necessary files, including rsa_key.pem and attachments.

cd /opt/vaultwarden
docker compose stop vaultwarden
tar -czf vw-backup-$(date +%Y%m%d-%H%M).tar.gz ./vw-data
ls -lh vw-backup-*.tar.gz

Store this backup securely off the host. In case of any issues during the migration, this archive serves as your rollback plan.

Step 2 — Deploy a PostgreSQL Container Alongside Vaultwarden

Add a PostgreSQL service to your existing compose.yaml file, ensuring it operates on the same internal Docker network as Vaultwarden.

services:
  vw-db:
    image: postgres:16-alpine
    container_name: vw-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    volumes:
      - ./vw-postgres-data:/var/lib/postgresql/data
    networks:
      - vw-net

  vaultwarden:
    image: vaultwarden/server:1.37.1-alpine
    container_name: vaultwarden
    restart: unless-stopped
    depends_on:
      - vw-db
    environment:
      DOMAIN: "https://vault.example.ca"
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    networks:
      - vw-net

networks:
  vw-net:
    driver: bridge

Start the database container first and confirm it initializes correctly before proceeding with Vaultwarden.

docker compose up -d vw-db
docker compose logs -f vw-db
# wait for: "database system is ready to accept connections"

Step 3 — Set the DATABASE_URL Environment Variable

Update the .env file with the new DATABASE_URL variable, formatted according to PostgreSQL URI standards. Do not restart Vaultwarden yet; the schema needs to be populated first.

# .env
DATABASE_URL=postgresql://vaultwarden:REPLACE_WITH_A_STRONG_PASSWORD@vw-db:5432/vaultwarden
ADMIN_TOKEN=$argon2id$v=19$m=65540,t=3,p=4$...
SIGNUPS_ALLOWED=false
WEBSOCKET_ENABLED=true

Ensure any special characters in the database password are percent-encoded to avoid parsing errors.

Step 4 — Let Vaultwarden Create the Empty PostgreSQL Schema

Start Vaultwarden with the new PostgreSQL connection to allow it to create the necessary schema. This step is solely for schema generation; your vault data remains in SQLite.

docker compose up -d vaultwarden
docker compose logs -f vaultwarden
# look for: "Migrations to run" followed by no errors

Once Vaultwarden starts successfully, stop it again to prepare for the data migration.

docker compose stop vaultwarden

Step 5 — Install pgloader and Prepare the SQLite Source File

Install pgloader from the PostgreSQL APT repository to ensure you have the latest version. Disable Write-Ahead Logging mode on the SQLite database to allow for a consistent snapshot during migration.

sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
sudo apt install -y pgloader
pgloader --version

sqlite3 ./vw-data/db.sqlite3 "PRAGMA journal_mode=DELETE;"

Step 6 — Run the pgloader Migration

Create a pgloader command file to load data from SQLite into PostgreSQL, excluding the Diesel migration tracking table.

cat > vw-migrate.load 'EOF'
LOAD DATABASE
  FROM sqlite:///opt/vaultwarden/vw-data/db.sqlite3
  INTO postgresql://vaultwarden:REPLACE_WITH_A_STRONG_PASSWORD@localhost:5432/vaultwarden

WITH data only, include no drop, reset sequences

EXCLUDING TABLE NAMES LIKE '__diesel_schema_migrations'

SET work_mem to '32MB', maintenance_work_mem to '128MB';
EOF

pgloader vw-migrate.load

After running pgloader, check the summary output for any discrepancies in row counts. If errors occur, verify your connection string and table names before retrying.

Step 7 — Start Vaultwarden on PostgreSQL and Verify Data Integrity

Bring Vaultwarden back online and check the logs for a successful startup. Log in with existing accounts to ensure all data has migrated correctly.

docker compose up -d vaultwarden
docker compose logs --tail=50 vaultwarden

Log in as multiple users to confirm that all vault items, folders, and organizations are intact before notifying your team to resume using the service.

Step 8 — Create Organizations for Team-Based Vault Sharing

With PostgreSQL now managing concurrent writes effectively, you can create organizations for shared vault access. This feature is available in self-hosted Vaultwarden without requiring a paid Bitwarden license.

  • Navigate to Organizations → New Organization in the web vault.
  • Name the organization according to your team or department.
  • Create Collections to categorize credentials by project or client.
  • Invite members via email, even with SIGNUPS_ALLOWED=false.
  • Assign roles based on the principle of least privilege.

Step 9 — Configure Group Policies for Consistent Team Security

Establish security policies within your organization to enforce a baseline level of security for all members.

  • Set minimum master password requirements under Organization Settings → Policies.
  • Enable two-step login enforcement for added security.
  • Consider implementing “Single Organization” policies to restrict membership to your organization only.
  • Configure “Personal Ownership” restrictions to direct new items into the organization rather than personal vaults.

Step 10 — Automate PostgreSQL Backups

Unlike SQLite, PostgreSQL requires a structured backup approach. Implement a cron job to automate pg_dump backups, ensuring regular data protection.

services:
  vw-db:
    image: postgres:16-alpine
    container_name: vw-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    volumes:
      - ./vw-postgres-data:/var/lib/postgresql/data
    networks:
      - vw-net

  vaultwarden:
    image: vaultwarden/server:1.37.1-alpine
    container_name: vaultwarden
    restart: unless-stopped
    depends_on:
      - vw-db
    environment:
      DOMAIN: "https://vault.example.ca"
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    networks:
      - vw-net

networks:
  vw-net:
    driver: bridge

Test the restore process periodically to ensure your backups are reliable and effective.

Step 11 — Tune PostgreSQL for Your Team Size

Adjust PostgreSQL settings to optimize performance for your user base. The default settings may not suffice for larger teams.

Setting Default Recommended for 10–50 Users Why It Matters
shared_buffers 128MB 256MB Enhances caching, reducing disk reads during sync
max_connections 100 50 Lowering this frees host memory for other processes
work_mem 4MB 16MB Improves query performance and sorting operations
effective_cache_size 4GB 1GB (adjust to host RAM) Helps optimize query planning
wal_compression off on Reduces WAL file size, saving backup storage costs

Step 12 — Monitor Database Health Going Forward

PostgreSQL provides valuable metrics for monitoring. Regularly check active connection counts and database size, and set up alerts for disk space usage to avoid unexpected outages.

services:
  vw-db:
    image: postgres:16-alpine
    container_name: vw-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    volumes:
      - ./vw-postgres-data:/var/lib/postgresql/data
    networks:
      - vw-net

  vaultwarden:
    image: vaultwarden/server:1.37.1-alpine
    container_name: vaultwarden
    restart: unless-stopped
    depends_on:
      - vw-db
    environment:
      DOMAIN: "https://vault.example.ca"
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    networks:
      - vw-net

networks:
  vw-net:
    driver: bridge

For those already utilizing Prometheus and Grafana, integrating postgres_exporter can provide detailed insights into database performance.

SQLite vs PostgreSQL for Vaultwarden: Which One Fits Your Team

Not every deployment necessitates PostgreSQL. The following table serves as a practical guide for selecting the appropriate backend based on team size and usage patterns:

Deployment Type Recommended Backend Typical Symptom If Wrong Choice Backup Method
Single user SQLite (default) None — SQLite is already ideal File copy of vw-data
Household (2–5 people) SQLite (default) Rare, occasional lock timeout File copy of vw-data
Small team (6–20 people) PostgreSQL Slow sync, admin panel hangs at peak hours pg_dump + attachment archive
Department (20–100 people) PostgreSQL, tuned Frequent “database is locked” errors pg_dump + WAL archiving
Enterprise (100+ people, HA required) PostgreSQL with replication Downtime risk from single point of failure Streaming replication + pg_dump

How Long the Migration Actually Takes

The duration of the migration primarily hinges on the size of your vault. The following table outlines expected runtimes based on real-world experiences:

Vault Size Approx. Users pgloader Runtime Recommended Maintenance Window
Small Under 10 Under 30 seconds 30 minutes
Medium 10–50 1–3 minutes 60 minutes
Large 50–200 3–10 minutes 90 minutes
Very large, many attachments 200+ 10–30 minutes 2 hours, scheduled outside business hours

While the pgloader step is typically swift, the verification process is where most time should be allocated. Ensure thorough testing by logging in with multiple accounts and checking for data integrity.

High Availability: Running Vaultwarden Behind a Load Balancer

For teams requiring zero downtime, consider implementing a highly available setup with PostgreSQL streaming replication and multiple Vaultwarden instances behind a load balancer.

  • Establish a primary PostgreSQL instance with one or more streaming replicas.
  • Point multiple Vaultwarden containers at the same primary DATABASE_URL.
  • Share the /data volume across instances using NFS or shared block storage.
  • Utilize HAProxy or Nginx to distribute HTTP traffic and manage failover.

Complete Working Project: The Full Stack in One Place

To simplify the setup, here’s a consolidated view of the necessary files for a complete working project:

compose.yaml — the full stack configuration for Vaultwarden, PostgreSQL, and Caddy for automatic HTTPS.

services:
  vw-db:
    image: postgres:16-alpine
    container_name: vw-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    volumes:
      - ./vw-postgres-data:/var/lib/postgresql/data
    networks:
      - vw-net

  vaultwarden:
    image: vaultwarden/server:1.37.1-alpine
    container_name: vaultwarden
    restart: unless-stopped
    depends_on:
      - vw-db
    environment:
      DOMAIN: "https://vault.example.ca"
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    networks:
      - vw-net

networks:
  vw-net:
    driver: bridge

Caddyfile — configuration for automatic HTTPS with Let’s Encrypt.

services:
  vw-db:
    image: postgres:16-alpine
    container_name: vw-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    volumes:
      - ./vw-postgres-data:/var/lib/postgresql/data
    networks:
      - vw-net

  vaultwarden:
    image: vaultwarden/server:1.37.1-alpine
    container_name: vaultwarden
    restart: unless-stopped
    depends_on:
      - vw-db
    environment:
      DOMAIN: "https://vault.example.ca"
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    networks:
      - vw-net

networks:
  vw-net:
    driver: bridge

.env — environment variables for the Vaultwarden container.

services:
  vw-db:
    image: postgres:16-alpine
    container_name: vw-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    volumes:
      - ./vw-postgres-data:/var/lib/postgresql/data
    networks:
      - vw-net

  vaultwarden:
    image: vaultwarden/server:1.37.1-alpine
    container_name: vaultwarden
    restart: unless-stopped
    depends_on:
      - vw-db
    environment:
      DOMAIN: "https://vault.example.ca"
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    networks:
      - vw-net

networks:
  vw-net:
    driver: bridge

With these files in place, the entire stack can be launched with a single command, providing a secure, PostgreSQL-backed Vaultwarden deployment ready for team use.

SSO and LDAP for Larger Teams

As your self-hosted vault scales, managing user accounts manually becomes impractical. Vaultwarden’s SSO support allows integration with existing identity providers, streamlining account lifecycle management.

  • Enable SSO by setting SSO_ENABLED=true and configuring the necessary parameters for your OIDC provider.
  • Register Vaultwarden as an OIDC client application with the correct redirect URI.
  • Conduct tests with a non-critical account before rolling out to the entire organization.
  • Maintain a local master-password login option for administrative fallback during outages.

Disaster Recovery: Running a Full Restore Drill

Regularly testing your backup strategy is essential. Conduct a full restore drill on a temporary host to verify your backups and the restoration process.

services:
  vw-db:
    image: postgres:16-alpine
    container_name: vw-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: vaultwarden
      POSTGRES_USER: vaultwarden
      POSTGRES_PASSWORD: "REPLACE_WITH_A_STRONG_PASSWORD"
    volumes:
      - ./vw-postgres-data:/var/lib/postgresql/data
    networks:
      - vw-net

  vaultwarden:
    image: vaultwarden/server:1.37.1-alpine
    container_name: vaultwarden
    restart: unless-stopped
    depends_on:
      - vw-db
    environment:
      DOMAIN: "https://vault.example.ca"
    env_file:
      - .env
    volumes:
      - ./vw-data:/data
    networks:
      - vw-net

networks:
  vw-net:
    driver: bridge

After confirming the user count matches expectations, document the successful drill date for future reference.

Common Pitfalls When Migrating Vaultwarden to PostgreSQL

Here are some common mistakes to avoid during the migration process:

  • Running pgloader while Vaultwarden is still connected to SQLite. Always stop the container before migration to prevent inconsistent exports.
  • Forgetting to percent-encode special characters in the database password. This can lead to connection string parsing errors.
  • Neglecting to let Vaultwarden create the schema first. An empty database will cause migration errors.
  • Failing to exclude the Diesel migrations table from the pgloader load. This can disrupt future updates.
  • Assuming the migration is reversible without a backup. Always have a backup before proceeding.
  • Exposing the PostgreSQL port to the host network. Keep it internal for security.
  • Underestimating the maintenance window for large vaults. Allocate sufficient time for larger migrations.

Troubleshooting Guide: 8 Common Errors and Fixes

Here are some common errors you may encounter during or after migration, along with their solutions:

  • “Error parsing DATABASE_URL” — Check for unescaped special characters in the password.
  • “relation does not exist” during pgloader — Ensure the Vaultwarden schema was created first.
  • “database is locked” persists after migration — Verify DATABASE_URL points to PostgreSQL.
  • Vaultwarden starts but the vault appears empty — Recheck the migration process and connection string.
  • pgloader reports “connection refused” — Ensure the PostgreSQL container is healthy.
  • WebSocket sync stops working after migration — Confirm WEBSOCKET_ENABLED=true is set in .env.
  • Attachments show as broken links — Verify that the /data volume contains the actual files.
  • Admin panel login fails after migration — Check the ADMIN_TOKEN in your .env file.

Advanced Tips for Running Vaultwarden at Team Scale

Once your migration is complete, consider these advanced configurations:

  • Pin your Vaultwarden image to a specific version tag to avoid unexpected updates.
  • Enable SMTP for email notifications regarding new device logins and invitations.
  • Set PUSH_ENABLED=true for faster mobile sync notifications.
  • Review diagnostics after PostgreSQL upgrades to catch schema mismatches early.
  • Rotate your ADMIN_TOKEN periodically and store it securely.

Vaultwarden vs Official Bitwarden Self-Hosted Server for Teams

Understanding the trade-offs between Vaultwarden and Bitwarden’s official self-hosted server is essential for making an informed decision:

Factor Vaultwarden (Community) Bitwarden Official Self-Hosted
Resource footprint Single lightweight container, tens of MB RAM Multi-container stack, several GB RAM recommended
License cost Free, MIT-licensed Free tier available; paid tiers for Organizations at scale
Vendor support Community-driven (GitHub, Discourse) Official Bitwarden support channels
Database options SQLite, PostgreSQL, MySQL/MariaDB MSSQL by default in the official stack
Setup complexity Single Docker Compose file Multiple services, install script required

Frequently Asked Questions

Do I have to migrate to PostgreSQL if my Vaultwarden team is small?

No, SQLite is sufficient for small teams. Migrate when you experience performance issues.

Will my users notice anything during the migration?

Users will only notice a brief downtime during the migration process.

Can I migrate directly from SQLite to PostgreSQL without pgloader?

While possible, it is not recommended due to the complexity and potential for errors.

Is MySQL/MariaDB a better choice than PostgreSQL for Vaultwarden?

Both are viable options, but PostgreSQL is often preferred for its stability and tooling.

Do I need a paid Bitwarden license to use Organizations on self-hosted Vaultwarden?

No, Vaultwarden supports Organizations for free.

What happens if the pgloader migration fails partway through?

Your SQLite file remains intact, allowing you to revert back easily.

How often should I run pg_dump backups for a team Vaultwarden instance?

Nightly backups are recommended for shared credential environments.

Can I run this migration on a managed PostgreSQL service instead of a Docker container?

Yes, as long as the Vaultwarden container can access it on the network.

Tech Optimizer
Migrate Vaultwarden to PostgreSQL: Scale for Teams [2026]