Password Manager Backup and Recovery Strategies
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)
| Scenario | Target RTO | Steps |
|---|---|---|
| Database corruption | 15 minutes | Restore from last backup |
| Server failure | 30 minutes | Deploy new server, restore data |
| Data center outage | 2 hours | Failover to secondary site |
| Complete disaster | 4 hours | Rebuild from off-site backup |
Recovery Testing
Quarterly test procedure:
- Spin up test environment (separate VM/container)
- Restore latest backup to test environment
- Verify functionality:
- Login with test account
- Access vault items
- Test 2FA
- Verify attachments
- Document results and update procedures
- 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
- Backup source server using backup scripts
- Transfer backup files to destination:
rsync -avz /backups/vaultwarden/ user@newserver:/backups/vaultwarden/ - Restore on destination using restore scripts
- Update DNS to point to new server
- Test functionality thoroughly
- 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 Related Documentation
- Bitwarden Self-Hosted Deployment - Official Bitwarden setup
- Vaultwarden Self-Hosted Deployment - Vaultwarden deployment
- Database Options for Self-Hosted Applications - Database configuration