SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-23

Vaultwarden VPS 备份与恢复:SQLite 正确方法

使用 sqlite3 .backup 复制运行中的 Vaultwarden 数据库,同时保留 attachments、config.json 和 rsa_key 文件,并在故障前验证恢复结果。

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, August 5, 2026.

Vaultwarden 备份必须包含哪些内容

Vaultwarden 备份是整个数据目录的副本,其中的数据库必须使用正确方式复制。请运行 sqlite3 db.sqlite3 ".backup out.sqlite3",不要运行 cp,因为直接复制正在写入的数据库,可能得到无法打开的文件。还要保留数据库旁边的其他文件,这部分最容易被忽略。

在 Docker 安装中,数据目录就是挂载到 /data 的目录。它可以是主机上的路径,也可以是命名卷。绑定挂载和命名卷的区别决定了保险库实际存储在磁盘上的位置。该目录包含以下内容。

  • db.sqlite3:所有账户、保险库项目、文件夹和组织。丢失此文件就会丢失整个保险库。
  • db.sqlite3-wal 和 db.sqlite3-shm:预写式日志(WAL)及其共享内存索引。SQLite 将最近的写入合并到主文件之前,这些写入会保存在这里。
  • attachments/:用户附加到保险库项目的文件。这些文件经过加密,并按项目分别存放在目录中。
  • sends/:Bitwarden Send 链接所对应的文件。
  • config.json:您在管理页面中保存的所有设置。
  • rsa_key.pem,以及旧版安装中的 rsa_key.der 和 rsa_key.pub.der:用于签署登录令牌的密钥。
  • icon_cache/:下载的网站图标。这是唯一可以跳过的目录,因为 Vaultwarden 会在需要时重新获取这些图标。

Vaultwarden 数据库安全吗?文件实际保存了什么

两个命令即可回答这个问题,您现在就可以运行这两个命令。

sudo apt update && sudo apt install -y sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select email from users;"
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select name from ciphers limit 1;"

第一个命令会以明文输出用户的电子邮件地址。第二个命令会输出一个项目名称,结果如下:

2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=

项目名称、用户名、密码和备注会在发送前由客户端加密,因此服务器保存的是无法读取的密文。2. 前缀表示 Bitwarden 的加密类型,后面依次是初始化向量(IV)、密文和 MAC(消息认证码);这些内容均采用 base64 编码,并以 | 分隔。用于解密的密钥由账户主密码派生,而主密码不会以可用形式传输到服务器。正如Vaultwarden 与自托管 Bitwarden 的比较所述,无论运行 Vaultwarden 还是官方服务器,情况都相同。

数据库的其余部分未加密。电子邮件地址、账户名称、密码提示和双因素恢复代码以明文存储,旁边还有创建时间、项目所属组织等元数据。因此,备份文件本身就是机密数据。持有该文件的任何人都能知道您的用户是谁,并可利用其硬件允许的速度,对加密数据块执行离线攻击。这一事实决定了后文的存储规则:副本必须在离开服务器前完成加密。管理令牌是同一问题的另一部分,自托管 Vaultwarden 的加固流程会同时处理这两项内容。

为什么在 Vaultwarden 运行时复制 db.sqlite3 不能作为备份

Vaultwarden 默认让 SQLite 使用 WAL 模式(ENABLE_DB_WAL=true)。写入内容首先进入 db.sqlite3-wal,只有检查点操作才会将其合并到 db.sqlite3。单独复制 db.sqlite3 时,得到的只是上一个检查点时的数据库状态。因此,十分钟前保存的密码可能不会出现在归档中,而且不会有任何提示。

使用 cp 同时复制这 3 个文件也不能解决问题。复制操作发生在略有差异的时间点,因此保存的 WAL 可能描述了页面版本,但与保存的主数据库文件不一致。随后 SQLite 会尝试使用其中一个文件恢复另一个文件,结果可能不正确。你可能要很久以后才发现:

Error: database disk image is malformed

.backup 使用 SQLite 的 Online Backup API,因此不会出现这个问题。SQLite 将该 API 记录为复制正在使用中的数据库的方式。它在读锁下读取页面;如果写入进程在读取期间修改了文件,它会重新开始。因此,最终写入磁盘的是数据库在某个一致时刻的状态。

使用 sqlite3 .backup 获取数据库副本

sudo apt update && sudo apt install -y sqlite3
sudo install -d -m 700 /var/backups/vaultwarden
OUT=/var/backups/vaultwarden/db-$(date '+%Y%m%d-%H%M').sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 ".backup '$OUT'"
sudo sqlite3 "$OUT" "PRAGMA integrity_check;"

最后一条命令会单独输出一行 ok。如果输出其他内容,说明副本不可用。不要保留该副本,也不要删除之前的副本。整个过程都在运行中的服务器上执行,因此不会注销任何用户,也不会重启容器。

sqlite3 工具不在 Vaultwarden 容器内。该镜像基于 debian:trixie-slim 构建,包含 ca-certificates、curl、libmariadb3、libpq5 和 openssl,因此 docker exec vaultwarden sqlite3 ... 会失败,并显示:

exec: "sqlite3": executable file not found in $PATH

请在主机上针对已挂载路径运行该命令,这正是上面命令的做法。如果数据位于命名卷中,docker volume inspect <name> 会在 /var/lib/docker/volumes/ 下输出主机路径。

从 1.32.1 版本开始,Vaultwarden 也提供了自己的备份命令。在服务器上运行:

docker exec -it vaultwarden /vaultwarden backup

该命令运行 VACUUM INTO,并将 db_YYYYMMDD_HHMMSS.sqlite3 写入数据目录。由此需要注意两点。副本会与原文件位于同一磁盘上,因此这只是暂存步骤,还不能算作备份。其次,该命令仅支持 SQLite:如果使用 MariaDB 或 PostgreSQL,则会因 The database type is not SQLite. Backups only works for SQLite databases 停止。

人们容易遗漏的文件

attachments/ 按不透明名称保存密文。每个附件对应的数据库行都包含其加密文件名,以及客户端解密文件所需的密钥材料。没有数据库,附件只是无法读取的噪声;没有附件,数据库中的文件项会导致用户下载失败。请在同一次备份中同时保存两者。

config.json 保存您从管理页面保存的所有设置,其值优先于匹配的环境变量。这有利也有弊:恢复旧的 config.json 会静默覆盖 compose 文件中的设置,而该文件本身也属于敏感信息,因为其中可能包含 SMTP 密码和管理员令牌。请将该令牌保存为 Argon2id PHC(密码哈希竞赛)字符串,而不是明文。docker run --rm -it vaultwarden/server /vaultwarden hash 可为您生成一个。

rsa_key.pem 用于签署 JSON Web Token(JWT),客户端依靠这些令牌保持登录状态。如果启动时缺少该文件,Vaultwarden 会生成新密钥,因此旧密钥签署的所有令牌都将无法通过验证,所有客户端都会退出登录。Vault 内容不会因此丢失,因为它们使用从主密码派生的密钥加密。恢复该密钥文件即可避免所有客户端同时退出登录。

sends/ 保存 Send 链接指向的文件。缺少这些文件只会导致相应下载失败,不会影响其他功能。

将全部内容放在一个脚本中

#!/bin/bash
set -euo pipefail

DATA=/opt/vaultwarden/data
DEST=/var/backups/vaultwarden
STAMP=$(date '+%Y%m%d-%H%M%S')
STAGE=$(mktemp -d /tmp/vw-stage.XXXXXX)

install -d -m 700 "$DEST"
sqlite3 "$DATA/db.sqlite3" ".backup '$STAGE/db.sqlite3'"
test "$(sqlite3 "$STAGE/db.sqlite3" 'PRAGMA integrity_check;')" = "ok"
cp -a "$DATA"/rsa_key* "$STAGE/"
for extra in config.json attachments sends; do
  if [ -e "$DATA/$extra" ]; then cp -a "$DATA/$extra" "$STAGE/"; fi
done
tar -C "$STAGE" -czf "$DEST/vw-$STAMP.tar.gz" .
chmod 600 "$DEST/vw-$STAMP.tar.gz"
rm -rf "$STAGE"
tar -tzf "$DEST/vw-$STAMP.tar.gz"

将其保存为 /usr/local/sbin/vw-backup.sh,为其添加执行权限 chmod 700,然后以 root 身份运行。test 行会执行实际操作:即使 PRAGMA integrity_check 报告数据损坏,sqlite3 仍会以退出状态 0 结束。因此,将输出与 ok 比较,才能在副本损坏时让脚本失败。随后,set -euo pipefail 会停止所有操作,而不是让 tar 围绕损坏的数据库创建一个看似完整的归档。

最后的 tar -tzf 会列出实际捕获的内容。首次运行时请阅读此列表。需要检查是否存在 ./db.sqlite3、./rsa_key.pem、./config.json 和 ./attachments/,并确认不存在 ./db.sqlite3-wal。如果希望获得 journalctl 输出以及能够报告失败的单元,请使用 systemd 服务和计时器 每晚运行,而不是使用 cron。

验证备份:将其恢复到临时目录

未经测试的备份只是猜测。将备份恢复到临时目录只需一分钟,也不会触及任何正在运行的数据。

sudo install -d -m 700 /tmp/vw-check
sudo tar -C /tmp/vw-check -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
ls -l /tmp/vw-check
sudo sqlite3 /tmp/vw-check/db.sqlite3 "PRAGMA integrity_check;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from users;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from ciphers;"
sudo du -sh /tmp/vw-check/attachments

有 4 个结果需要关注。integrity_check 会输出 ok。用户数量应与已知账户数量一致。密码数量应接近 sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;" 显示的当前数量;正在使用的密码库中该数量绝不会为 0。附件目录的大小应大致符合预期;如果没有人上传附件,可以跳过此项。然后运行 sudo rm -rf /tmp/vw-check,因为该目录现在包含了所有数据的第二份副本。

恢复手动复制的数据目录时,必须遵循一条规则:启动服务器前删除 db.sqlite3-wal 和 db.sqlite3-shm。否则,SQLite 会尝试使用属于另一份副本的日志来恢复恢复出的数据库,从而损坏原本完整的数据库。上述脚本生成的归档中不会包含这些文件,因为 .backup 会写入一个完整的数据库。

恢复到服务器

以下命令在您自己的服务器上执行,并且容器必须已停止。Vaultwarden 不得在数据目录发生变化时写入该目录。

cd /opt/vaultwarden
docker compose stop vaultwarden
sudo mv data data.old.$(date '+%Y%m%d-%H%M%S')
sudo install -d -m 700 data
sudo tar -C data -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
sudo chown -R root:root data
docker compose start vaultwarden
docker compose logs --tail 20 vaultwarden

chown 必须指定容器运行所使用的用户。默认镜像以 root 身份运行,因此除非您在 compose 文件中设置了 user:,否则使用 root:root 即可;如果设置了 user:,请使用其中的 uid 和 gid。服务器无法写入的数据目录会导致登录页面的每个请求都失败,日志会明确记录这一点。

正常启动时,最后应出现 Rocket 行:

[INFO] Rocket has launched from http://0.0.0.0:80

然后在浏览器中登录,打开一个条目并下载一个附件。如果可以登录但附件下载失败,说明归档中包含数据库,但不包含 attachments/。在所有检查完成前保留 data.old.*,确认无误后再删除它。回滚时执行相同的 3 个步骤,但交换两个目录的顺序。

如果您的路径与本文不同,请参阅 VPS 的 Vaultwarden 安装指南,其中包含这些命令所假定的 compose 文件。

备份不应放在哪里

  • 不要放在与数据目录相同的磁盘上。一个卷故障会同时导致两份副本丢失,在错误路径执行一次 rm -rf 也会造成同样的结果。
  • 不要放在同一台服务器上,即使使用第二个卷也不行。攻击者一旦获得 root 权限,就能在同一个会话中访问备份。
  • 不要在未加密的对象存储中保存,因为归档包含电子邮件地址、密码提示、恢复代码和保管库密文,攻击者可以在离线状态下对其进行攻击。
  • 不要只依赖服务提供商的快照。快照恢复速度快,确实值得保留,但它们与服务器位于同一个账户中,因此账户出现问题时,快照也会一并受到影响。

异地副本适合使用 restic,因为 restic 存储库会在服务器上先完成加密,然后才上传任何数据。在服务器上:

sudo apt install -y restic
export RESTIC_REPOSITORY=s3:https://s3.example.com/vaultwarden-backups
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /var/backups/vaultwarden --tag vaultwarden
restic snapshots --tag vaultwarden
restic forget --tag vaultwarden --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

让 restic 指向归档目录,而不是实时数据目录。这样上传的就是你已经检查过的一致副本。将存储库密码保存在受保护服务器之外的其他位置:密码丢失后,快照将无法读取,这是设计如此。存储系统支持时,为服务器提供只能写入、不能删除的凭据。这样服务器遭到入侵后,攻击者无法删除服务器自己的备份历史。在 VPS 上设置 restic 备份完整介绍存储库和计划任务;如果你还没有决定,restic 与 BorgBackup 的比较介绍如何选择。

按计划测试恢复

每月选择一天。使用 restic restore latest --tag vaultwarden --target /tmp/vw-check 将最新快照拉取到临时目录,运行相同的 PRAGMA integrity_check,执行相同的行数统计,然后记录日期和统计结果。六个月内无人恢复过的备份,其状态是未知的。等到服务中断时才了解它的状态,是最糟糕的时机。

每年执行一次完整测试。使用恢复的数据目录,在备用端口上启动第二个 Vaultwarden 容器,并使用真实账户登录。这样可以端到端验证主密码路径,而行数统计无法做到这一点。按相同计划运行 restic check --read-data-subset=10%,可以验证存储的数据确实可读取,而不只是能被列出。

FAQ

我可以在 Vaultwarden 运行时使用 cp 复制 db.sqlite3 吗?

不可以。Vaultwarden 以 WAL 模式运行 SQLite,因此最近的写入位于 db.sqlite3-wal 中,尚未写入 db.sqlite3。单独复制主数据库文件的 cp 会静默丢失这些写入,分别复制这两个文件还可能生成不匹配的文件对,之后才表现为 Error: database disk image is malformed。请改用 sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'"。它使用 SQLite 的 Online Backup API,在服务器继续提供服务的同时生成一个一致的文件。

我必须停止 Vaultwarden 容器才能创建备份吗?

不需要,这正是 .backup 的作用。服务器运行时复制数据库是安全的。附件和 Send 文件会在用户上传时写入,因此数据库复制和 tar 之间新增的文件可能不会进入当晚的归档,最坏情况下只会丢失一个附件。如果您不介意停机几秒钟,可在脚本前执行 docker compose stop,并在脚本后执行 docker compose start,这样连这类遗漏也能避免。

如果恢复时没有 rsa_key 文件,会发生什么?

Vaultwarden 会在启动时生成新密钥。该密钥用于签名维持会话有效的 JSON Web Token (JWT),因此所有现有令牌都会停止验证,所有客户端都会被注销,必须重新登录。Vault 内容不受影响,因为这些内容使用从每个用户的主密码派生的密钥加密,而不是使用 RSA 密钥加密。将 rsa_key.pem 与数据目录的其余内容一并恢复,用户不会察觉这次恢复。

备份归档可以原样上传到对象存储吗?

不可以。项目名称、密码和备注是密文,但电子邮件地址、账户名称、密码提示和双因素恢复代码在数据库中是明文,离线攻击者还可以自行安排破解密文的速度。归档离开服务器前必须加密。restic 仓库会自动完成这项工作,而 gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz 会生成一个可交给任意存储服务的单个加密文件。

如何备份使用 PostgreSQL 或 MariaDB 的 Vaultwarden?

SQLite 步骤不适用,内置命令会因 The database type is not SQLite. Backups only works for SQLite databases 而拒绝执行。使用数据库原生工具导出数据库,例如 pg_dump 或 mysqldump,其他规则保持不变。导出文件应与 attachments/、sends/、config.json 以及 rsa_key 文件一起放入一个归档中;这些内容必须在同一次运行中获取、加密,并存储在创建该备份的服务器之外的位置。