Password Manager Backup and Recovery Strategies

6 October 2025 Β· Updated 7 October 2025

backuprecoverypassword-managerself-hosteddisaster-recovery

Notes on backing up a self-hosted password vault and actually getting it back when something goes wrong. A password manager you can’t restore from is an expensive way to lose credentials, so the backup story is the real product β€” not the manager itself.

Critical Data to Backup

Bitwarden Components

  • Environment files: ./bwdata/env/ - Configuration and secrets
  • Attachments: ./bwdata/core/attachments/ - Vault file attachments
  • Database: ./bwdata/mssql/data/ - All password vault data
  • Auth tokens: ./bwdata/core/aspnet-dataprotection/ - Encryption keys

Vaultwarden Components

  • Data directory: ./vw-data/ - Complete vault (SQLite default)
  • Database file: /data/db.sqlite3 - All passwords and metadata
  • Attachments: /data/attachments/ - File attachments
  • Configuration: /data/config.json - Runtime settings

Backup Methods

Manual Backup (Bitwarden)

#!/bin/bash
# bitwarden-backup.sh

BACKUP_DIR="/backups/bitwarden"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BW_DIR="/home/bitwarden/bwdata"

# Stop Bitwarden
cd /home/bitwarden
./bitwarden.sh stop

# Create backup directory
mkdir -p "$BACKUP_DIR/$TIMESTAMP"

# Backup critical directories
tar -czf "$BACKUP_DIR/$TIMESTAMP/env.tar.gz" -C "$BW_DIR" env
tar -czf "$BACKUP_DIR/$TIMESTAMP/attachments.tar.gz" -C "$BW_DIR/core" attachments
tar -czf "$BACKUP_DIR/$TIMESTAMP/mssql-data.tar.gz" -C "$BW_DIR/mssql" data
tar -czf "$BACKUP_DIR/$TIMESTAMP/aspnet-dataprotection.tar.gz" -C "$BW_DIR/core" aspnet-dataprotection

# Start Bitwarden
./bitwarden.sh start

# Keep last 30 days
find "$BACKUP_DIR" -type d -mtime +30 -exec rm -rf {} +

echo "Backup completed: $BACKUP_DIR/$TIMESTAMP"

Manual Backup (Vaultwarden)

#!/bin/bash
# vaultwarden-backup.sh

BACKUP_DIR="/backups/vaultwarden"
DATA_DIR="./vw-data"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# Stop container for consistent backup
docker stop vaultwarden

# Create backup
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/vaultwarden-$TIMESTAMP.tar.gz" "$DATA_DIR"

# Start container
docker start vaultwarden

# Keep last 30 backups
find "$BACKUP_DIR" -name "vaultwarden-*.tar.gz" -mtime +30 -delete

echo "Backup completed: $BACKUP_DIR/vaultwarden-$TIMESTAMP.tar.gz"

Automated Backup Scheduling

Cron Scheduling

Monthly backup (Bitwarden):

# Edit crontab
crontab -e

# Add line (runs 1st of month at 2 AM)
0 2 1 * * /home/bitwarden/bitwarden-backup.sh >> /var/log/bitwarden-backup.log 2>&1

Daily backup (Vaultwarden):

# Runs daily at 2 AM
0 2 * * * /home/user/vaultwarden-backup.sh >> /var/log/vaultwarden-backup.log 2>&1

Retention Policies

  • Daily backups: Keep 7 days
  • Weekly backups: Keep 4 weeks
  • Monthly backups: Keep 12 months
  • Yearly backups: Keep indefinitely (off-site)

Database-Specific Backups

MySQL/MariaDB

# Vaultwarden with MySQL
mysqldump -u vaultwarden -p vaultwarden > vaultwarden-$(date +%Y%m%d).sql

# Automated with compression
mysqldump -u vaultwarden -p vaultwarden | gzip > vaultwarden-$(date +%Y%m%d).sql.gz

PostgreSQL

# Bitwarden with PostgreSQL
pg_dump -U bitwarden bitwarden > bitwarden-$(date +%Y%m%d).sql

# Vaultwarden with PostgreSQL
pg_dump -U vaultwarden vaultwarden > vaultwarden-$(date +%Y%m%d).sql

SQLite (Vaultwarden Default)

# Online backup (no downtime)
sqlite3 /data/db.sqlite3 ".backup '/backups/db-backup.sqlite3'"

# Offline backup (requires stop)
docker stop vaultwarden
cp /vw-data/db.sqlite3 /backups/db-$(date +%Y%m%d).sqlite3
docker start vaultwarden

Recovery Procedures

Restore Bitwarden

#!/bin/bash
# bitwarden-restore.sh

BACKUP_DATE="20250101_020000"
BACKUP_DIR="/backups/bitwarden/$BACKUP_DATE"
BW_DIR="/home/bitwarden/bwdata"

# Stop Bitwarden
cd /home/bitwarden
./bitwarden.sh stop

# Restore directories
tar -xzf "$BACKUP_DIR/env.tar.gz" -C "$BW_DIR"
tar -xzf "$BACKUP_DIR/attachments.tar.gz" -C "$BW_DIR/core"
tar -xzf "$BACKUP_DIR/mssql-data.tar.gz" -C "$BW_DIR/mssql"
tar -xzf "$BACKUP_DIR/aspnet-dataprotection.tar.gz" -C "$BW_DIR/core"

# Fix permissions
chown -R bitwarden:bitwarden "$BW_DIR"

# Start Bitwarden
./bitwarden.sh start

echo "Restore completed from: $BACKUP_DIR"

Restore Vaultwarden

# Stop Vaultwarden
docker stop vaultwarden

# Remove existing data
rm -rf ./vw-data

# Extract backup
tar -xzf vaultwarden-20250106.tar.gz

# Start Vaultwarden
docker start vaultwarden

Disaster Recovery Planning

Recovery Time Objectives (RTO)

ScenarioTarget RTOSteps
Database corruption15 minutesRestore from last backup
Server failure30 minutesDeploy new server, restore data
Data center outage2 hoursFailover to secondary site
Complete disaster4 hoursRebuild from off-site backup

Recovery Testing

Quarterly test procedure:

  1. Spin up test environment (separate VM/container)
  2. Restore latest backup to test environment
  3. Verify functionality:
    • Login with test account
    • Access vault items
    • Test 2FA
    • Verify attachments
  4. Document results and update procedures
  5. Destroy test environment

Off-Site Backup Strategy

Options:

  • Cloud storage: Encrypted backups to S3, Azure Blob, Google Cloud Storage
  • Remote server: rsync to secondary location
  • Network storage: Synology, QNAP NAS with replication
  • Tape backup: For compliance/archival requirements

Example - S3 sync:

# Encrypt and upload to S3
aws s3 sync /backups/vaultwarden/ s3://backup-bucket/vaultwarden/ \
  --sse AES256 \
  --storage-class STANDARD_IA

Migration Between Servers

Pre-Migration Checklist

  • Backup current installation
  • Document all configurations
  • Test backup restoration
  • Prepare new server with same version
  • Schedule maintenance window
  • Notify users of downtime

Migration Steps

  1. Backup source server using backup scripts
  2. Transfer backup files to destination:
    rsync -avz /backups/vaultwarden/ user@newserver:/backups/vaultwarden/
  3. Restore on destination using restore scripts
  4. Update DNS to point to new server
  5. Test functionality thoroughly
  6. Monitor for 24 hours before decommissioning old server

Backup Encryption

Encrypt Backups

# GPG encryption
tar -czf - ./vw-data/ | gpg --symmetric --cipher-algo AES256 -o backup-$(date +%Y%m%d).tar.gz.gpg

# Decrypt and restore
gpg -d backup-20250106.tar.gz.gpg | tar -xzf -

Password Protection

# ZIP with password
zip -e -r backup-$(date +%Y%m%d).zip ./vw-data/

# 7zip with encryption
7z a -p -mhe=on backup-$(date +%Y%m%d).7z ./vw-data/

Monitoring Backup Health

Backup Verification Script

#!/bin/bash
# verify-backup.sh

BACKUP_FILE="$1"
LOG_FILE="/var/log/backup-verify.log"

# Check if backup exists
if [ ! -f "$BACKUP_FILE" ]; then
    echo "ERROR: Backup file not found: $BACKUP_FILE" | tee -a "$LOG_FILE"
    exit 1
fi

# Check file size (should be > 1MB for valid backup)
SIZE=$(stat -c%s "$BACKUP_FILE")
if [ "$SIZE" -lt 1048576 ]; then
    echo "WARNING: Backup file too small: $SIZE bytes" | tee -a "$LOG_FILE"
    exit 1
fi

# Test archive integrity
if tar -tzf "$BACKUP_FILE" > /dev/null 2>&1; then
    echo "SUCCESS: Backup verified: $BACKUP_FILE" | tee -a "$LOG_FILE"
    exit 0
else
    echo "ERROR: Backup corrupted: $BACKUP_FILE" | tee -a "$LOG_FILE"
    exit 1
fi

Alert on Backup Failure

# Add to backup script
if [ $? -ne 0 ]; then
    echo "Backup failed!" | mail -s "Backup Failure Alert" admin@example.com
fi