SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

Vaultwarden VPS 備份與還原完整指南

使用 sqlite3 .backup 複製運作中的 Vaultwarden vault,並保留 attachments、config.json 與 rsa_key 檔案,還原前先驗證備份確實可用。

Vaultwarden 備份必須包含的內容

Vaultwarden 備份是整個資料目錄的副本,其中的資料庫也必須以正確方式複製。請執行 sqlite3 db.sqlite3 ".backup out.sqlite3",不要執行 cp,因為直接複製正在寫入的資料庫,可能會產生無法開啟的檔案。接著保留與資料庫放在一起的其他檔案;這是最容易被忽略的部分。

在 Docker 安裝中,資料目錄就是掛載到 /data 的位置。這可以是主機上的路徑,也可以是 named volume;bind mount 與 named volume 的差異會決定 vault 實際儲存在磁碟上的位置。其內容如下。

  • db.sqlite3:所有帳戶、vault 項目、資料夾與組織。遺失此檔案就會遺失 vault。
  • db.sqlite3-waldb.sqlite3-shm:write-ahead log (WAL) 與其 shared memory index。SQLite 將最近的寫入合併到主檔案前,這些資料會先存放在這裡。
  • attachments/:使用者附加至 vault 項目的檔案。檔案會經過加密,並依項目分別存放在不同目錄中。
  • sends/:Bitwarden Send 連結所指向的檔案。
  • config.json:從管理頁面儲存的所有設定。
  • rsa_key.pem,以及較舊安裝中的 rsa_key.derrsa_key.pub.der:用來簽署登入 token 的 key。
  • icon_cache/:下載的網站圖示。這是唯一可以略過的目錄,因為 Vaultwarden 會在需要時重新取得圖示。

Vaultwarden 資料庫安全嗎?檔案實際包含哪些內容

只要執行 2 個命令即可確認,而且現在就能執行。

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 執行中直接複製 db.sqlite3 不算備份

Vaultwarden 預設以 WAL 模式執行 SQLite(ENABLE_DB_WAL=true)。寫入內容會先寫入 db.sqlite3-wal,只有執行 checkpoint 後,才會整合到 db.sqlite3。單獨複製 db.sqlite3 時,取得的是上次 checkpoint 時的資料庫狀態。因此,十分鐘前儲存的密碼可能不在封存檔中,而且不會有任何警告。

使用 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-certificatescurllibmariadb3libpq5openssl,因此執行 docker exec vaultwarden sqlite3 ... 會失敗並顯示:

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

請改在主機上針對已掛載的路徑執行,以上命令就是這樣做的。如果資料位於具名 volume 中,docker volume inspect <name> 會在 /var/lib/docker/volumes/ 下輸出主機路徑。

Vaultwarden 也從 1.32.1 版開始提供自己的備份命令。在伺服器上執行:

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/ 會以不透明名稱儲存密文。每個附件的資料庫資料列都包含加密後的檔名,以及 client 解密檔案所需的金鑰材料。沒有資料庫的附件只是無法讀取的雜訊;沒有附件的資料庫則會讓使用者看到下載失敗的項目。請在同一次執行中同時備份兩者。

config.json 儲存你從管理頁面儲存的所有設定,其值會優先於相符的環境變數。這可能造成雙面刃:還原舊的 config.json 會悄悄覆寫 compose file 中的設定,而該檔案本身也屬於敏感資料,因為其中可能包含 SMTP 密碼和管理員 token。請將該 token 儲存為 Argon2id PHC(password hashing competition)字串,不要使用純文字。docker run --rm -it vaultwarden/server /vaultwarden hash 會替你產生一組。

rsa_key.pem 會簽署讓 client 保持登入狀態的 JSON web token(JWT)。如果啟動時找不到該檔案,Vaultwarden 會產生新的金鑰,因此所有由舊金鑰簽署的 token 都會停止通過驗證,所有 client 也會被登出。Vault 內容不受影響,因為它們是使用由主密碼衍生的金鑰加密。還原此金鑰檔案即可避免所有 client 同時登出。

sends/ 儲存 Send link 背後的檔案。缺少這些檔案只會導致下載失敗,不會影響其他功能。

將所有內容放在同一個 script 中

#!/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 比對,才能讓錯誤的複本使 script 失敗。接著,set -euo pipefail 會停止所有操作,而不是讓 tar 在損壞的資料庫外建立看似完整的 archive。

最後的 tar -tzf 會列出實際擷取的內容。第一次執行時請閱讀這份清單。請確認其中包含 ./db.sqlite3./rsa_key.pem./config.json./attachments/,且不包含 ./db.sqlite3-wal。若需要 journalctl 輸出,以及能回報失敗狀態的 unit,請使用 systemd service 和 timer 每晚執行,而不要使用 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。使用者數量應與已知帳戶數量一致。cipher 數量應接近 sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;" 顯示的目前數值;正在使用的 vault 不應為 0。attachments 目錄的大小應大致符合預期。如果沒有使用者上傳附件,可以略過此項。接著執行 sudo rm -rf /tmp/vw-check,因為該目錄現在包含所有資料的另一份副本。

還原任何手動複製的資料目錄時,請遵守一項規則:啟動伺服器前先刪除 db.sqlite3-waldb.sqlite3-shm。否則,SQLite 會嘗試使用屬於其他副本的日誌復原還原的資料庫,導致原本完整的資料庫損毀。上述 script 產生的封存檔不會包含這些檔案,因為 .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 即可;若有設定,請改用其中的 uid 和 gid。若伺服器無法寫入資料目錄,登入頁面會導致每個請求失敗,日誌也會記錄原因。

正常啟動時,最後會出現 Rocket 行:

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

接著使用瀏覽器登入,開啟一個項目,並下載一個附件。若登入正常但附件下載失敗,表示封存檔包含資料庫,卻沒有包含 attachments/。在所有項目確認無誤前,請保留 data.old.*,確認完成後再刪除。回復的步驟相同,只需將目錄對調。

若您的路徑與本文不同,請參閱 VPS 的 Vaultwarden 安裝指南;其中包含本指令所假設的 compose 檔案。

備份不應放置的位置

  • 不要放在與資料目錄相同的磁碟上。單一磁碟區故障會同時損失兩份副本,對錯誤路徑執行一次 rm -rf 也會造成相同結果。
  • 不要放在同一台伺服器上,即使使用第二個磁碟區也不行。攻擊者一旦取得 root 權限,就能在同一個工作階段存取備份。
  • 不要在未加密的物件儲存中保存,因為封存檔包含電子郵件地址、密碼提示、復原碼及可供離線破解的 vault ciphertext。
  • 不要只依賴供應商的快照。快照還原速度快,值得保留,但快照與伺服器位於同一個帳戶中,因此帳戶發生問題時,快照也會一併受影響。

這時適合使用 restic 建立異地副本,因為 restic repository 會先在機器上加密,再上傳任何資料。在伺服器上:

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 指向封存目錄,而不是即時資料目錄。如此一來,上傳的內容就是你已經檢查過的一致副本。將 repository 密碼保存於受保護伺服器以外的位置:密碼遺失後,快照將無法讀取,這是設計上的結果。若儲存系統支援,請提供伺服器只能寫入、不能刪除的認證資訊。如此一來,即使伺服器遭到入侵,攻擊者也無法清除該伺服器自己的歷史備份。在 VPS 上設定 restic 備份完整說明 repository 與排程;如果尚未決定方案,請參閱 restic 與 BorgBackup 的比較

依排程測試還原

每月選定一天。使用 restic restore latest --tag vaultwarden --target /tmp/vw-check 將最新的 snapshot 拉取至暫存目錄,執行相同的 PRAGMA integrity_check,重新統計相同的資料列數,然後記錄日期與統計結果。若一份備份在六個月內從未還原過,就無法確認其狀態;等到服務中斷時才發現問題,是最不適合進行驗證的時機。

每年執行一次完整測試。使用還原後的資料目錄,在未使用的連接埠上啟動第二個 Vaultwarden container,並使用實際帳號登入。這可從頭到尾驗證 master password 的流程,任何資料列數都無法做到這點。依相同排程執行 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 tokens (JWT),因此所有現有 token 都會停止通過驗證,所有用戶端都會被登出,必須重新登入。Vault 內容不受影響,因為內容使用各使用者的主密碼衍生出的金鑰加密,而不是使用 RSA 金鑰加密。將 rsa_key.pem 與資料資料夾中的其他內容一併還原,使用者不會察覺這次還原。

備份封存檔可以直接上傳到物件儲存嗎?

不可以。項目名稱、密碼與備註都是密文,但電子郵件地址、帳戶名稱、密碼提示及雙因素驗證復原碼會以明文儲存在資料庫中;離線攻擊者也能按照自己的速度破解密文。封存檔離開機器前,請先加密。restic repository 會代你完成這項工作,而 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_dumpmysqldump,其他規則維持不變。傾印檔應與 attachments/sends/config.jsonrsa_key 檔案放在同一個封存檔中;這些內容必須在同一次執行中取得、加密,並儲存在製作該備份的伺服器以外的位置。