2026-03-2012 phút đọcVI
- 1.Tại sao cần Secret Manager? — Vượt ra ngoài .env files
- 2.Tổng quan Vaultwarden — Tương thích Bitwarden, chạy bằng Rust
- 3.Cài đặt Docker với bảo mật nâng cao cho Vaultwarden
- 4.Cloudflare Tunnel và Zero Trust Access — Truy cập bảo mật không cần mở port
- 8.Backup và Disaster Recovery — Vault là sinh mệnh của bạn(bài này)
Backup và Disaster Recovery — Vault là sinh mệnh của bạn
Vault là Single Point of Failure
Vault chứa mọi thứ — API key, database password, SSH key, bot token, OAuth credential. Nếu vault mất, bạn mất quyền truy cập vào toàn bộ hạ tầng. Không phải một service, không phải một database — mà tất cả cùng lúc.
Điều này biến vault thành single point of failure quan trọng nhất trong hệ thống của bạn. Và giải pháp duy nhất cho single point of failure là redundancy — backup đáng tin cậy, đã được kiểm chứng, và có quy trình restore rõ ràng.
Backup không phải optional. Backup là bắt buộc từ ngày đầu tiên bạn đưa Vaultwarden vào sử dụng.
Bài viết này đi sâu vào cấu trúc dữ liệu của Vaultwarden, kỹ thuật backup SQLite đúng cách, script production-ready, quy trình restore, và các kịch bản disaster recovery thực tế.
Cấu trúc dữ liệu Vaultwarden
Trước khi backup, cần hiểu rõ Vaultwarden lưu gì và ở đâu.
Các thành phần dữ liệu
/data/ # Docker volume mount (mặc định)
├── db.sqlite3 # Database chính — QUAN TRỌNG NHẤT
├── db.sqlite3-wal # Write-Ahead Log
├── db.sqlite3-shm # Shared Memory file
├── config.json # Runtime configuration
├── rsa_key.der # RSA private key (JWT signing)
├── rsa_key.pub.der # RSA public key
├── rsa_key.pem # RSA key (PEM format)
├── attachments/ # Encrypted file attachments
│ └── <uuid>/ # Mỗi attachment có thư mục riêng
│ └── <file>
├── sends/ # Bitwarden Send files
│ └── <uuid>/
│ └── <file>
└── icon_cache/ # Website favicon cache
└── <domain>.png
Mô tả chi tiết từng thành phần
db.sqlite3 — Database chính
Đây là file quan trọng nhất. Chứa toàn bộ:
- Thông tin user (email, master password hash, KDF settings)
- Tất cả vault items (encrypted: logins, notes, cards, identities)
- Organizations và collections
- Cấu hình 2FA
- Emergency access settings
- Policies và cấu hình tổ chức
Mất file này = mất toàn bộ vault.
db.sqlite3-wal — Write-Ahead Log
SQLite sử dụng WAL (Write-Ahead Logging) mode để tăng hiệu suất concurrent read/write. WAL file chứa các thay đổi chưa được flush vào database chính.
- Kích thước thay đổi liên tục
- Có thể chứa dữ liệu mới nhất chưa được ghi vào
db.sqlite3 - Bắt buộc phải backup cùng với db.sqlite3
db.sqlite3-shm — Shared Memory
File shared memory hỗ trợ WAL mode. Chứa index cho WAL file.
- Thường nhỏ (vài KB)
- Cần backup cùng db.sqlite3 và WAL để đảm bảo consistency
attachments/ — File đính kèm đã mã hoá
Khi user đính kèm file vào vault item, file được mã hoá client-side và lưu tại đây.
- Mã hoá bằng key của user — server không thể đọc
- Mất thư mục này = mất tất cả file đính kèm
- Kích thước có thể lớn tuỳ usage
config.json — Cấu hình runtime
Chứa các setting được thay đổi qua Admin Panel (khác với environment variables trong Docker).
rsa_key.* — RSA Keys
Dùng cho JWT token signing. Nếu mất, tất cả session hiện tại bị invalid (user cần đăng nhập lại), nhưng dữ liệu không mất.
icon_cache/ — Cache favicon
Cache của website icon. Có thể tái tạo hoàn toàn — không cần backup. Vaultwarden sẽ tự download lại khi cần.
Nguyên lý backup SQLite
SQLite không giống PostgreSQL hay MySQL — bạn không thể đơn giản pg_dump hay mysqldump. SQLite lưu tất cả trong file, nhưng việc copy file khi database đang hoạt động có thể dẫn đến corruption.
WAL Mode là gì và tại sao quan trọng
Khi SQLite chạy ở WAL mode (mặc định của Vaultwarden):
- Write operations ghi vào WAL file thay vì trực tiếp vào database
- Read operations đọc từ database + WAL file
- Checkpoint là quá trình flush dữ liệu từ WAL vào database chính
Điều này có nghĩa: tại bất kỳ thời điểm nào, db.sqlite3 có thể không chứa dữ liệu mới nhất. Dữ liệu mới nằm trong WAL file.
Checkpoint trước khi copy
-- Flush tất cả dữ liệu từ WAL vào database chính
PRAGMA wal_checkpoint(TRUNCATE);TRUNCATE mode:
- Flush tất cả WAL data vào database
- Truncate WAL file về kích thước 0
- Sau checkpoint:
db.sqlite3chứa đầy đủ dữ liệu
Copy an toàn vs không an toàn
An toàn:
# Cách 1: Checkpoint rồi copy tất cả files
sqlite3 /data/db.sqlite3 "PRAGMA wal_checkpoint(TRUNCATE);"
cp /data/db.sqlite3 /backup/db.sqlite3
cp /data/db.sqlite3-wal /backup/db.sqlite3-wal 2>/dev/null || true
cp /data/db.sqlite3-shm /backup/db.sqlite3-shm 2>/dev/null || true
# Cách 2: Sử dụng SQLite backup API (tốt nhất)
sqlite3 /data/db.sqlite3 ".backup /backup/db.sqlite3"Không an toàn (TRÁNH):
# NGUY HIỂM: Copy khi đang có write operation
cp /data/db.sqlite3 /backup/db.sqlite3
# → Có thể bị corruption nếu Vaultwarden đang ghi dữ liệu
# NGUY HIỂM: Copy db mà không copy WAL
cp /data/db.sqlite3 /backup/db.sqlite3
# → Mất dữ liệu chưa được checkpointSQLite .backup command
Cách an toàn nhất là sử dụng SQLite backup API thông qua .backup command:
sqlite3 /data/db.sqlite3 ".backup /backup/db.sqlite3"Ưu điểm:
- Atomic — đảm bảo consistency
- Hoạt động ngay cả khi database đang có connection active
- Không cần stop Vaultwarden
- Tự xử lý WAL
Script backup Production-Ready
Dưới đây là script backup hoàn chỉnh, sẵn sàng cho production:
#!/usr/bin/env bash
set -euo pipefail
# vaultwarden-backup.sh — Production-ready backup script
# Sử dụng: ./vaultwarden-backup.sh [--dry-run]
#
# Yêu cầu: sqlite3 CLI, rsync (cho offsite backup)
# ============================================================
# CẤU HÌNH
# ============================================================
# Đường dẫn dữ liệu Vaultwarden (Docker volume mount)
VAULT_DATA="/opt/vaultwarden/data"
# Thư mục backup local
BACKUP_DIR="/opt/backups/vaultwarden"
# Offsite backup (comment out nếu không dùng)
OFFSITE_HOST="backup-server"
OFFSITE_DIR="/backups/vaultwarden"
# Retention policy
DAILY_RETENTION=7 # Giữ 7 bản daily
WEEKLY_RETENTION=4 # Giữ 4 bản weekly (mỗi Chủ nhật)
# Kích thước tối thiểu db.sqlite3 (bytes) — dưới ngưỡng = có thể corruption
MIN_DB_SIZE=10240 # 10 KB
# Dry-run mode
DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=true
fi
# ============================================================
# HÀM TIỆN ÍCH
# ============================================================
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DAY_OF_WEEK=$(date +%u) # 1=Monday, 7=Sunday
LOG_FILE="${BACKUP_DIR}/logs/backup-${TIMESTAMP}.log"
log() {
local msg="[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*"
echo "$msg"
if [ "$DRY_RUN" = false ]; then
echo "$msg" >> "$LOG_FILE"
fi
}
die() {
log "FATAL: $*"
exit 1
}
# ============================================================
# KIỂM TRA ĐIỀU KIỆN TIÊN QUYẾT
# ============================================================
preflight_check() {
log "--- Pre-flight check ---"
# Kiểm tra sqlite3 có sẵn không
command -v sqlite3 > /dev/null 2>&1 || die "sqlite3 not found. Install: apt install sqlite3"
# Kiểm tra thư mục dữ liệu tồn tại
[ -d "$VAULT_DATA" ] || die "Vault data directory not found: $VAULT_DATA"
# Kiểm tra database file tồn tại
[ -f "${VAULT_DATA}/db.sqlite3" ] || die "Database file not found: ${VAULT_DATA}/db.sqlite3"
# Kiểm tra database integrity
local integrity
integrity=$(sqlite3 "${VAULT_DATA}/db.sqlite3" "PRAGMA integrity_check;" 2>&1)
if [ "$integrity" != "ok" ]; then
log "WARNING: Database integrity check failed: $integrity"
log "WARNING: Proceeding with backup anyway (backup of corrupted db is better than no backup)"
else
log "OK: Database integrity check passed"
fi
# Tạo thư mục backup nếu chưa có
if [ "$DRY_RUN" = false ]; then
mkdir -p "${BACKUP_DIR}/daily"
mkdir -p "${BACKUP_DIR}/weekly"
mkdir -p "${BACKUP_DIR}/logs"
fi
log "OK: Pre-flight check passed"
}
# ============================================================
# BACKUP DATABASE
# ============================================================
backup_database() {
log "--- Backing up database ---"
local backup_file="${BACKUP_DIR}/daily/vaultwarden-${TIMESTAMP}.sqlite3"
if [ "$DRY_RUN" = true ]; then
log "DRY-RUN: Would checkpoint WAL"
log "DRY-RUN: Would backup to ${backup_file}"
return
fi
# Bước 1: Checkpoint WAL
log "Checkpointing WAL..."
sqlite3 "${VAULT_DATA}/db.sqlite3" "PRAGMA wal_checkpoint(TRUNCATE);" 2>&1 | \
while read -r line; do log " checkpoint: $line"; done
# Bước 2: Backup bằng SQLite backup API
log "Running SQLite backup..."
sqlite3 "${VAULT_DATA}/db.sqlite3" ".backup '${backup_file}'"
# Bước 3: Verify kích thước backup
local backup_size
backup_size=$(stat -f%z "$backup_file" 2>/dev/null || stat -c%s "$backup_file" 2>/dev/null)
if [ "$backup_size" -lt "$MIN_DB_SIZE" ]; then
log "WARNING: Backup file suspiciously small: ${backup_size} bytes (threshold: ${MIN_DB_SIZE})"
log "WARNING: Possible corruption — keeping file for investigation"
else
log "OK: Backup size: ${backup_size} bytes"
fi
# Bước 4: Verify integrity của backup
local backup_integrity
backup_integrity=$(sqlite3 "$backup_file" "PRAGMA integrity_check;" 2>&1)
if [ "$backup_integrity" != "ok" ]; then
log "WARNING: Backup integrity check failed: $backup_integrity"
else
log "OK: Backup integrity verified"
fi
log "OK: Database backed up to ${backup_file}"
}
# ============================================================
# BACKUP ATTACHMENTS VÀ CONFIG
# ============================================================
backup_extras() {
log "--- Backing up attachments and config ---"
local extras_dir="${BACKUP_DIR}/daily/extras-${TIMESTAMP}"
if [ "$DRY_RUN" = true ]; then
log "DRY-RUN: Would backup attachments and config to ${extras_dir}"
return
fi
mkdir -p "$extras_dir"
# Config file
if [ -f "${VAULT_DATA}/config.json" ]; then
cp "${VAULT_DATA}/config.json" "${extras_dir}/"
log "OK: config.json copied"
fi
# RSA keys
for key_file in rsa_key.der rsa_key.pub.der rsa_key.pem; do
if [ -f "${VAULT_DATA}/${key_file}" ]; then
cp "${VAULT_DATA}/${key_file}" "${extras_dir}/"
fi
done
log "OK: RSA keys copied"
# Attachments
if [ -d "${VAULT_DATA}/attachments" ]; then
cp -r "${VAULT_DATA}/attachments" "${extras_dir}/"
local attachment_count
attachment_count=$(find "${extras_dir}/attachments" -type f 2>/dev/null | wc -l)
log "OK: ${attachment_count} attachment files copied"
else
log "INFO: No attachments directory found (OK if no attachments used)"
fi
# Sends
if [ -d "${VAULT_DATA}/sends" ]; then
cp -r "${VAULT_DATA}/sends" "${extras_dir}/"
log "OK: Sends directory copied"
fi
log "OK: Extras backed up to ${extras_dir}"
}
# ============================================================
# WEEKLY BACKUP (Chủ nhật)
# ============================================================
promote_weekly() {
if [ "$DAY_OF_WEEK" != "7" ]; then
log "INFO: Not Sunday — skipping weekly promotion"
return
fi
log "--- Promoting to weekly backup ---"
if [ "$DRY_RUN" = true ]; then
log "DRY-RUN: Would promote today's backup to weekly"
return
fi
local weekly_dir="${BACKUP_DIR}/weekly/week-${TIMESTAMP}"
mkdir -p "$weekly_dir"
# Copy database backup mới nhất
local latest_db
latest_db=$(ls -t "${BACKUP_DIR}"/daily/vaultwarden-*.sqlite3 2>/dev/null | head -1)
if [ -n "$latest_db" ]; then
cp "$latest_db" "$weekly_dir/"
log "OK: Database promoted to weekly"
fi
# Copy extras mới nhất
local latest_extras
latest_extras=$(ls -dt "${BACKUP_DIR}"/daily/extras-* 2>/dev/null | head -1)
if [ -n "$latest_extras" ]; then
cp -r "$latest_extras" "$weekly_dir/"
log "OK: Extras promoted to weekly"
fi
log "OK: Weekly backup created at ${weekly_dir}"
}
# ============================================================
# RETENTION — XOÁ BACKUP CŨ
# ============================================================
cleanup_old_backups() {
log "--- Cleaning up old backups ---"
if [ "$DRY_RUN" = true ]; then
log "DRY-RUN: Would clean up backups older than retention policy"
return
fi
# Daily: giữ N bản mới nhất
local daily_dbs
daily_dbs=$(ls -t "${BACKUP_DIR}"/daily/vaultwarden-*.sqlite3 2>/dev/null)
local count=0
while IFS= read -r file; do
count=$((count + 1))
if [ $count -gt "$DAILY_RETENTION" ]; then
rm -f "$file"
log "CLEANUP: Removed old daily: $(basename "$file")"
fi
done <<< "$daily_dbs"
# Daily extras: giữ N bản mới nhất
local daily_extras
daily_extras=$(ls -dt "${BACKUP_DIR}"/daily/extras-* 2>/dev/null)
count=0
while IFS= read -r dir; do
count=$((count + 1))
if [ $count -gt "$DAILY_RETENTION" ] && [ -d "$dir" ]; then
rm -rf "$dir"
log "CLEANUP: Removed old daily extras: $(basename "$dir")"
fi
done <<< "$daily_extras"
# Weekly: giữ N bản mới nhất
local weekly_dirs
weekly_dirs=$(ls -dt "${BACKUP_DIR}"/weekly/week-* 2>/dev/null)
count=0
while IFS= read -r dir; do
count=$((count + 1))
if [ $count -gt "$WEEKLY_RETENTION" ] && [ -d "$dir" ]; then
rm -rf "$dir"
log "CLEANUP: Removed old weekly: $(basename "$dir")"
fi
done <<< "$weekly_dirs"
log "OK: Cleanup complete"
}
# ============================================================
# OFFSITE BACKUP
# ============================================================
offsite_sync() {
if [ -z "${OFFSITE_HOST:-}" ]; then
log "INFO: Offsite backup not configured — skipping"
return
fi
log "--- Syncing to offsite ---"
if [ "$DRY_RUN" = true ]; then
log "DRY-RUN: Would rsync to ${OFFSITE_HOST}:${OFFSITE_DIR}"
return
fi
rsync -avz --delete \
"${BACKUP_DIR}/daily/" \
"${OFFSITE_HOST}:${OFFSITE_DIR}/daily/" \
2>&1 | tail -3 | while read -r line; do log " rsync: $line"; done
rsync -avz --delete \
"${BACKUP_DIR}/weekly/" \
"${OFFSITE_HOST}:${OFFSITE_DIR}/weekly/" \
2>&1 | tail -3 | while read -r line; do log " rsync: $line"; done
log "OK: Offsite sync complete"
}
# ============================================================
# THỰC THI
# ============================================================
main() {
log "=========================================="
log "Vaultwarden Backup — ${TIMESTAMP}"
if [ "$DRY_RUN" = true ]; then
log "MODE: DRY-RUN (no changes will be made)"
fi
log "=========================================="
preflight_check
backup_database
backup_extras
promote_weekly
cleanup_old_backups
offsite_sync
log "=========================================="
log "Backup complete."
log "=========================================="
}
main "$@"Lưu script và cấp quyền thực thi:
chmod +x /opt/scripts/vaultwarden-backup.sh
# Test với dry-run trước
/opt/scripts/vaultwarden-backup.sh --dry-runThiết lập Cron
Cấu hình cron cho backup tự động
# Mở crontab
crontab -eThêm các dòng sau:
# Vaultwarden backup — chạy mỗi ngày lúc 3:00 AM
0 3 * * * /opt/scripts/vaultwarden-backup.sh >> /opt/backups/vaultwarden/logs/cron.log 2>&1
# Integrity check hàng tuần (Chủ nhật 2:00 AM, trước backup)
0 2 * * 0 sqlite3 /opt/vaultwarden/data/db.sqlite3 "PRAGMA integrity_check;" >> /opt/backups/vaultwarden/logs/integrity.log 2>&1Xác nhận cron đã hoạt động
# Xem crontab hiện tại
crontab -l
# Kiểm tra cron log (tuỳ distro)
# Ubuntu/Debian
grep -i cron /var/log/syslog | tail -20
# CentOS/RHEL
grep -i cron /var/log/cron | tail -20
# Kiểm tra backup file đã được tạo
ls -la /opt/backups/vaultwarden/daily/RPO và RTO
Hai khái niệm quan trọng nhất trong disaster recovery planning:
RPO — Recovery Point Objective
"Bạn chấp nhận mất bao nhiêu dữ liệu?"
RPO đo bằng thời gian: khoảng cách giữa backup gần nhất và thời điểm sự cố.
| Tần suất backup | RPO | Nghĩa là |
|---|---|---|
| Hàng ngày (3:00 AM) | 24 giờ | Mất tối đa 24 giờ dữ liệu |
| Mỗi 6 giờ | 6 giờ | Mất tối đa 6 giờ dữ liệu |
| Mỗi giờ | 1 giờ | Mất tối đa 1 giờ dữ liệu |
| Real-time replication | ~0 | Gần như không mất dữ liệu |
Khuyến nghị cho Vaultwarden:
- Minimum: Hàng ngày (RPO 24h) — phù hợp nếu vault ít thay đổi
- Recommended: Mỗi 6-12 giờ (RPO 6-12h) — cân bằng giữa an toàn và tài nguyên
- Không cần real-time: Vault thường không thay đổi liên tục — daily đã đủ cho hầu hết trường hợp
RTO — Recovery Time Objective
"Bạn chấp nhận downtime bao lâu?"
RTO đo thời gian từ khi phát hiện sự cố đến khi service hoạt động trở lại.
| Thành phần | Thời gian ước tính |
|---|---|
| Phát hiện sự cố | 5-30 phút (tuỳ monitoring) |
| Chuẩn bị môi trường mới (nếu cần) | 10-30 phút |
| Restore database | 1-5 phút |
| Restore attachments | 1-10 phút (tuỳ kích thước) |
| Start Vaultwarden | 1-2 phút |
| Verify | 5-10 phút |
| Tổng RTO ước tính | 30 phút - 1.5 giờ |
Cách giảm RPO
# Tăng tần suất backup
# Thay vì 1 lần/ngày → 4 lần/ngày
0 */6 * * * /opt/scripts/vaultwarden-backup.sh
# Hoặc sử dụng Litestream cho continuous SQLite replication
# https://litestream.io — stream WAL changes liên tục tới S3/remote
litestream replicate /opt/vaultwarden/data/db.sqlite3 s3://my-bucket/vaultwarden/Quy trình Restore chi tiết
Khi cần khôi phục vault, thực hiện từng bước sau:
Bước 1: Dừng Vaultwarden
# Docker Compose
docker compose -f /opt/vaultwarden/docker-compose.yml down
# Hoặc Docker standalone
docker stop vaultwardenQuan trọng: PHẢI dừng container trước khi thay thế database. Ghi đè file khi Vaultwarden đang chạy sẽ gây corruption.
Bước 2: Xác định backup cần restore
# Liệt kê backup có sẵn
echo "=== Daily backups ==="
ls -lh /opt/backups/vaultwarden/daily/vaultwarden-*.sqlite3
echo "=== Weekly backups ==="
ls -lh /opt/backups/vaultwarden/weekly/*/vaultwarden-*.sqlite3
# Chọn backup phù hợp (ví dụ: bản gần nhất)
RESTORE_DB=$(ls -t /opt/backups/vaultwarden/daily/vaultwarden-*.sqlite3 | head -1)
echo "Will restore from: $RESTORE_DB"
# Kiểm tra integrity của backup trước khi restore
sqlite3 "$RESTORE_DB" "PRAGMA integrity_check;"
# Phải trả về "ok"Bước 3: Xác định đường dẫn volume
# Nếu dùng Docker volume
docker volume inspect vaultwarden_data | jq -r '.[0].Mountpoint'
# Ví dụ: /var/lib/docker/volumes/vaultwarden_data/_data
# Nếu dùng bind mount (phổ biến hơn)
# Xem docker-compose.yml hoặc docker inspect
docker inspect vaultwarden | jq '.[0].Mounts'
# Ví dụ mount path: /opt/vaultwarden/dataBước 4: Thay thế database
VAULT_DATA="/opt/vaultwarden/data"
# Backup file hiện tại (đề phòng)
if [ -f "${VAULT_DATA}/db.sqlite3" ]; then
mv "${VAULT_DATA}/db.sqlite3" "${VAULT_DATA}/db.sqlite3.broken.$(date +%Y%m%d)"
mv "${VAULT_DATA}/db.sqlite3-wal" "${VAULT_DATA}/db.sqlite3-wal.broken.$(date +%Y%m%d)" 2>/dev/null || true
mv "${VAULT_DATA}/db.sqlite3-shm" "${VAULT_DATA}/db.sqlite3-shm.broken.$(date +%Y%m%d)" 2>/dev/null || true
fi
# Copy backup vào
cp "$RESTORE_DB" "${VAULT_DATA}/db.sqlite3"
# Nếu có extras backup, restore attachments
RESTORE_EXTRAS=$(ls -dt /opt/backups/vaultwarden/daily/extras-* | head -1)
if [ -d "${RESTORE_EXTRAS}/attachments" ]; then
rm -rf "${VAULT_DATA}/attachments"
cp -r "${RESTORE_EXTRAS}/attachments" "${VAULT_DATA}/"
fi
# Restore config nếu cần
if [ -f "${RESTORE_EXTRAS}/config.json" ]; then
cp "${RESTORE_EXTRAS}/config.json" "${VAULT_DATA}/"
fi
# Restore RSA keys
for key_file in rsa_key.der rsa_key.pub.der rsa_key.pem; do
if [ -f "${RESTORE_EXTRAS}/${key_file}" ]; then
cp "${RESTORE_EXTRAS}/${key_file}" "${VAULT_DATA}/"
fi
doneBước 5: Sửa quyền sở hữu file
Vaultwarden container thường chạy với UID 1000. Nếu quyền sai, container sẽ không đọc được database.
# Đặt ownership đúng
chown -R 1000:1000 "${VAULT_DATA}/db.sqlite3"
chown -R 1000:1000 "${VAULT_DATA}/attachments" 2>/dev/null || true
chown -R 1000:1000 "${VAULT_DATA}/config.json" 2>/dev/null || true
# Đặt permission đúng
chmod 600 "${VAULT_DATA}/db.sqlite3"
chmod 700 "${VAULT_DATA}/attachments" 2>/dev/null || trueBước 6: Khởi động lại Vaultwarden
# Docker Compose
docker compose -f /opt/vaultwarden/docker-compose.yml up -d
# Kiểm tra container đã start
docker ps | grep vaultwarden
# Xem log khởi động
docker logs vaultwarden --tail 50Bước 7: Xác nhận hoạt động
# Kiểm tra web interface
curl -sf https://vault.example.com/ | head -5
# Đăng nhập thử
# → Mở trình duyệt, truy cập vault, đăng nhập bằng master password
# Kiểm tra items có đầy đủ không
# → Kiểm tra số lượng items, organizations, collections
# Kiểm tra 2FA vẫn hoạt động
# → Nếu 2FA settings nằm trong database (TOTP secret), cần xác nhận
# Kiểm tra attachments
# → Mở một item có attachment, tải xuống và verifyKịch bản Disaster Recovery
Kịch bản 1: SQLite Corruption
Triệu chứng: Vaultwarden báo lỗi database, không thể đọc vault.
[ERROR] rusqlite::Error::SqliteFailure: database disk image is malformed
Xử lý:
# 1. Thử repair trước
sqlite3 /opt/vaultwarden/data/db.sqlite3 ".recover" > /tmp/recovered.sql
sqlite3 /tmp/recovered.sqlite3 < /tmp/recovered.sql
# Kiểm tra recovered database
sqlite3 /tmp/recovered.sqlite3 "PRAGMA integrity_check;"
# 2. Nếu repair thành công → dùng recovered database
# 3. Nếu repair thất bại → restore từ backup (quy trình ở trên)Kịch bản 2: Host Machine bị hỏng
Triệu chứng: Server không khởi động được, disk failure, hardware failure.
Xử lý:
# 1. Provision host mới (VM, VPS, bare metal)
# 2. Cài đặt Docker
apt update && apt install -y docker.io docker-compose-v2
# 3. Deploy Vaultwarden (từ docker-compose.yml đã lưu)
mkdir -p /opt/vaultwarden/data
# Copy docker-compose.yml từ git/backup
# 4. Restore từ offsite backup
rsync -avz backup-server:/backups/vaultwarden/daily/ /opt/backups/vaultwarden/daily/
# 5. Restore database (quy trình ở trên)
RESTORE_DB=$(ls -t /opt/backups/vaultwarden/daily/vaultwarden-*.sqlite3 | head -1)
cp "$RESTORE_DB" /opt/vaultwarden/data/db.sqlite3
chown 1000:1000 /opt/vaultwarden/data/db.sqlite3
# 6. Start Vaultwarden
docker compose -f /opt/vaultwarden/docker-compose.yml up -d
# 7. Cập nhật DNS (nếu IP thay đổi)
# 8. VerifyKịch bản 3: Ransomware
Triệu chứng: Tất cả file bị encrypt, ransom note xuất hiện.
Xử lý:
# 1. KHÔNG TRUY CẬP vào máy bị nhiễm
# 2. Isolate máy (ngắt mạng)
# 3. Provision host hoàn toàn mới
# 4. Restore từ OFFSITE backup (backup local có thể đã bị encrypt)
# Offsite backup qua SSH/rsync/cloud storage
rsync -avz backup-server:/backups/vaultwarden/ /opt/backups/vaultwarden/
# 5. Kiểm tra integrity của backup (ransomware có thể đã encrypt backup nếu accessible)
sqlite3 /opt/backups/vaultwarden/daily/vaultwarden-latest.sqlite3 "PRAGMA integrity_check;"
# 6. Restore và deploy (quy trình ở trên)
# 7. ROTATE TẤT CẢ SECRETS (coi như đã bị compromise)
# 8. Điều tra phạm vi ảnh hưởngBài học: Đây là lý do offsite backup (hoặc air-gapped backup) quan trọng. Nếu backup nằm trên cùng máy hoặc cùng mạng, ransomware có thể encrypt luôn.
Kịch bản 4: Xoá nhầm Vault Items
Triệu chứng: User vô tình xoá item hoặc collection quan trọng.
Xử lý:
# Vaultwarden có Trash (Recycle Bin) — item xoá nằm trong trash 30 ngày
# → Kiểm tra Trash trước
# Nếu đã purge khỏi Trash:
# 1. Xác định thời điểm xoá
# 2. Tìm backup trước thời điểm đó
ls -lt /opt/backups/vaultwarden/daily/vaultwarden-*.sqlite3
# 3. KHÔNG restore toàn bộ vault (sẽ mất thay đổi mới)
# 4. Mount backup database readonly, export item cần thiết
sqlite3 /opt/backups/vaultwarden/daily/vaultwarden-20260315.sqlite3 \
"SELECT * FROM ciphers WHERE name LIKE '%keyword%';"
# 5. Hoặc: tạo Vaultwarden instance tạm với backup database
docker run -d --name vw-restore \
-v /opt/backups/vaultwarden/daily/extras-20260315:/data \
-p 8888:80 \
vaultwarden/server:latest
# 6. Đăng nhập vào instance tạm, export item cần thiết
# 7. Import vào vault chính
# 8. Dừng và xoá instance tạm
docker rm -f vw-restoreKiểm tra phục hồi định kỳ (Restore Drill)
Backup chưa được test = backup có thể không hoạt động. Thực hiện restore drill hàng tháng:
Checklist Restore Drill
## Restore Drill — [Tháng] [Năm]
**Ngày thực hiện:** YYYY-MM-DD
**Người thực hiện:** _______________
**Backup sử dụng:** [filename + date]
### Các bước thực hiện
- [ ] Tạo test instance (Docker, port khác production)
- [ ] Copy backup database vào test instance data directory
- [ ] Copy backup attachments (nếu có)
- [ ] Fix permissions (UID 1000)
- [ ] Start test instance
- [ ] Đăng nhập thành công bằng master password
- [ ] Kiểm tra số lượng items (so sánh với production)
- [ ] Mở 3 items ngẫu nhiên, verify nội dung
- [ ] Tải xuống 1 attachment (nếu có), verify
- [ ] Kiểm tra organization access
- [ ] Dừng và xoá test instance
- [ ] Ghi nhận kết quả
### Kết quả
- [ ] PASS — Restore thành công, dữ liệu đầy đủ
- [ ] PARTIAL — Restore thành công nhưng có vấn đề: ___
- [ ] FAIL — Không thể restore: ___
### Thời gian restore thực tế: ___ phút
### Ghi chú: _______________Script tự động hoá restore drill
#!/usr/bin/env bash
set -euo pipefail
# restore-drill.sh — Automated restore verification
BACKUP_DB=$(ls -t /opt/backups/vaultwarden/daily/vaultwarden-*.sqlite3 | head -1)
TEST_PORT=8888
TEST_DATA="/tmp/vw-restore-drill"
echo "=== Restore Drill ==="
echo "Backup: $BACKUP_DB"
echo "Test port: $TEST_PORT"
# Setup
rm -rf "$TEST_DATA"
mkdir -p "$TEST_DATA"
cp "$BACKUP_DB" "${TEST_DATA}/db.sqlite3"
chown -R 1000:1000 "$TEST_DATA"
# Start test instance
docker run -d --name vw-drill \
-v "${TEST_DATA}:/data" \
-p "${TEST_PORT}:80" \
-e ADMIN_TOKEN=drill-test-token \
vaultwarden/server:latest
echo "Waiting for startup..."
sleep 5
# Health check
if curl -sf "http://localhost:${TEST_PORT}/alive" > /dev/null 2>&1; then
echo "PASS: Vaultwarden started successfully"
else
echo "FAIL: Vaultwarden failed to start"
docker logs vw-drill --tail 20
docker rm -f vw-drill
rm -rf "$TEST_DATA"
exit 1
fi
# Kiểm tra admin panel accessible
if curl -sf "http://localhost:${TEST_PORT}/admin" -o /dev/null; then
echo "PASS: Admin panel accessible"
else
echo "WARN: Admin panel not accessible"
fi
# Cleanup
echo "Cleaning up..."
docker rm -f vw-drill
rm -rf "$TEST_DATA"
echo "=== Restore Drill Complete ==="Chiến lược Offsite Backup
rsync qua SSH / Tailscale
# rsync qua SSH (đơn giản, đáng tin cậy)
rsync -avz -e "ssh -p 22" \
/opt/backups/vaultwarden/ \
user@backup-host:/backups/vaultwarden/
# rsync qua Tailscale (zero-config VPN)
rsync -avz \
/opt/backups/vaultwarden/ \
backup-host:/backups/vaultwarden/
# Tailscale tự xử lý encryption và routingCloud Storage (Encrypted)
Sử dụng rclone để đẩy backup lên cloud storage đã encrypt:
# Cấu hình rclone (một lần)
rclone config
# → Tạo remote "b2-encrypted" với Backblaze B2 + crypt overlay
# Sync backup lên cloud
rclone sync /opt/backups/vaultwarden/ b2-encrypted:vaultwarden-backup/
# Verify
rclone ls b2-encrypted:vaultwarden-backup/daily/Quan trọng: Luôn encrypt trước khi đẩy lên cloud. Dù database đã encrypt ở application layer, defense-in-depth yêu cầu encrypt thêm ở storage layer.
USB Drive (Air-gapped)
Cho mức bảo mật cao nhất — backup offline, không kết nối mạng:
# Hàng quý: copy backup ra USB drive
# 1. Cắm USB drive
# 2. Mount
sudo mount /dev/sdb1 /mnt/usb
# 3. Copy
rsync -avz /opt/backups/vaultwarden/weekly/ /mnt/usb/vaultwarden-backup/
# 4. Unmount
sudo umount /mnt/usb
# 5. Cất USB ở nơi an toàn (khác vị trí server)Air-gapped backup bảo vệ khỏi: ransomware, network compromise, cloud provider outage, và insider threat.
Tóm tắt chuỗi bài viết
Bài viết này kết thúc chuỗi 8 bài về Vaultwarden. Dưới đây là tóm tắt toàn bộ hành trình:
| Bài | Chủ đề | Nội dung chính |
|---|---|---|
| 1 | Giới thiệu Vaultwarden | Tại sao cần password manager, Vaultwarden vs Bitwarden |
| 2 | Cài đặt và cấu hình | Docker deployment, reverse proxy, TLS |
| 3 | Quản lý người dùng và tổ chức | Users, organizations, collections, sharing |
| 4 | Bitwarden CLI và tự động hoá | CLI commands, scripting, CI/CD integration |
| 5 | Bảo mật Vaultwarden | Hardening, 2FA, admin panel, fail2ban |
| 6 | Secret management cho đội ngũ | Workflow, naming conventions, access control |
| 7 | Xoay vòng và Audit Secret | Rotation schedule, automation, compliance checklist |
| 8 | Backup và Disaster Recovery | SQLite backup, restore procedure, DR scenarios |
Từ cài đặt ban đầu đến vận hành production với backup và disaster recovery — bạn đã có đầy đủ kiến thức để triển khai và duy trì một hệ thống quản lý secret đáng tin cậy cho đội ngũ của mình.
Nguyên tắc xuyên suốt: Secret management không phải là "set and forget". Nó đòi hỏi vận hành liên tục — rotation, audit, backup, và kiểm tra định kỳ. Vaultwarden cung cấp nền tảng; quy trình và kỷ luật vận hành là trách nhiệm của bạn.