Vaultwarden 安全嗎?自架保管庫強化清單
Vaultwarden 在用戶端加密每個保管庫項目,伺服器不持有明文;真正風險是 admin token、JWT 金鑰與備份 tarball 的保護。
Vaultwarden 安全嗎?簡短回答
Vaultwarden 在最重要的環節是安全的,因為每個保管庫項目在送達伺服器前,就已先在裝置上完成加密。伺服器儲存的是自身無法讀取的資料區塊。即使有人複製整個資料庫,仍需要主密碼才能取得有用的內容。
但這個答案涵蓋了許多前提,而真正容易出問題的部分,正是由你的設定決定。管理員面板使用容易猜到的權杖。容器連接埠發布到整個網際網路。明文 config.json。備份 tarball 放在同一台主機的家目錄中。這些都不是密碼編譯問題,卻都是自架保管庫遭到清空的原因。
以下內容都假設你已完成可正常運作的安裝。如果你尚未安裝,請先依照 VPS 上的 Vaultwarden 安裝指南完成設定,再回來依序執行這份清單。
伺服器實際儲存的資料
Vaultwarden 實作 Bitwarden 的資料模型。保管庫項目的名稱、使用者名稱、密碼、備註與 URI,會在用戶端以主密碼衍生的金鑰加密,然後才送出任何請求。附件檔案內容也會以相同方式加密。伺服器收到的是附加 UUID(通用唯一識別碼)的不可讀資料。
有些資料不是密文,請確實了解以下項目:
- 以純文字儲存的帳戶電子郵件地址。
- KDF(金鑰衍生函式)設定與 salt,因為用戶端下次登入時需要這些資料來重新建立金鑰。
- 用戶端送出的主密碼雜湊值之伺服器端雜湊,用於驗證登入本身。
- 中繼資料:組織成員資格、裝置名稱、上次登入時間。
- 保護 Vaultwarden 登入的雙因素驗證方法所使用的 secret。這項資料未加密地儲存在
twofactor資料表中,因為伺服器必須計算預期的驗證碼,才能與您提供的驗證碼比對。這不同於您儲存在保管庫項目內的 TOTP(基於時間的一次性密碼)secret;後者會像其他欄位一樣加密。
資料資料夾很小。在 Docker 安裝中,它就是您掛載到 /data 的目錄。
sudo ls -l /vw-data/db.sqlite3 幾乎包含所有狀態資料。attachments/ 儲存上傳的檔案,每個 UUID 一個檔案;這也是唯一重要、且不儲存在資料庫資料表中的資料類型。sends/ 儲存 Send 附件,設計上是暫存資料。icon_cache/ 可直接捨棄。rsa_key.pem 及其相關檔案會簽署已登入使用者的 JWT(JSON Web Token),因此取得該私密金鑰的副本後,就能偽造保管庫登入工作階段。只有在啟用管理頁面後,config.json 才會存在;專案文件對此說明得很直接:其中以純文字儲存管理員 token 與 SMTP 認證資料。
因此,實際的威脅模型是檔案系統存取,而不是網路加密。只要能讀取該目錄,就等於取得所有使用者的電子郵件地址、登入雙因素驗證 secret、可偽造工作階段的金鑰,以及每個保管庫的離線副本,之後可以從容進行攻擊。以下每個步驟,都是為了阻止他人存取該目錄。
先修正管理員權杖
/admin 是完整的控制面板:使用者清單、邀請、刪除,以及所有執行階段設定。它只由一組共用密碼保護,沒有其他防護。沒有使用者名稱,也沒有個別使用者的雙因素驗證。
較早的指南會要求你使用 openssl rand -base64 48 產生 ADMIN_TOKEN。這種方式可行,但會將密碼以明文寫入 config.json 及 compose 檔案。Vaultwarden 也接受 Argon2 PHC(password hashing competition)字串,因此儲存的值會改為雜湊值。請對執行中的容器產生雜湊值:
docker exec -it vaultwarden /vaultwarden hash或者完全不接觸執行中的容器:
docker run --rm -it vaultwarden/server /vaultwarden hash它會要求輸入密碼兩次,然後輸出一行以 $argon2id$ 開頭的內容。在 bare-metal 安裝環境中,請執行 ./vaultwarden hash。如果你想直接使用 argon2 CLI,專案文件列出了 OWASP 的最低參數:
echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1接下來是最容易讓人花上一小時排查的陷阱。PHC 字串包含許多 $ 字元,而 Docker Compose 會將 $ 視為變數插值。若未跳脫就直接貼到 environment: 區塊中,傳入容器的值會遭到破壞,因此 /admin 會拒絕你確定正確的權杖。有兩種安全寫法。在 docker-compose.yml 中,將每個 $ 加倍:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UI在 .env 檔案中不需要跳脫,但請使用單引號:
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'接著限制控制面板的請求速率,並縮短工作階段期限:
ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20在五分鐘內出現三次錯誤嘗試後,控制面板會停止回應該用戶端。管理員工作階段閒置 20 分鐘後會到期。
更好的做法是關閉這個頁面。大多數執行個體只需要使用一次,以設定 SMTP 並邀請第一批使用者,之後就不再需要。若要停用,請不要設定 ADMIN_TOKEN 或 DISABLE_ADMIN_TOKEN,從 config.json 移除任何 "admin_token" 金鑰,然後重新建立容器。從檔案中刪除該金鑰很重要,因為管理員頁面會將設定寫入該處,而 config.json 中的內容優先於環境變數。只移除變數仍會讓頁面保持開放。
在任何人發現此網域前關閉註冊
SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=falseSIGNUPS_ALLOWED 預設為 true。維持此設定時,任何能連線到您網域的人都能建立帳戶,且其資料會與您的資料放在同一個 db.sqlite3 中。將其設為 false,再從管理員頁面透過邀請新增使用者;此功能需要可正常運作的 SMTP。INVITATIONS_ALLOWED 預設也為 true,允許組織擁有者邀請其他人。當您信任使用者時,這樣的設定沒有問題;在單一使用者執行個體上,則應設為 false。如果只有特定網域可以註冊,SIGNUPS_DOMAINS_WHITELIST=example.com 比開放註冊更嚴格,但安全性仍遠低於邀請制。
SHOW_PASSWORD_HINT 預設為 false,應維持此設定。啟用後,只要在登入表單輸入有效的電子郵件地址,就會顯示該帳戶的主密碼提示。這不僅會洩露提示內容,也會確認該地址確實存在。
如果您的執行個體曾在一段時間內開放註冊,請先開啟管理員頁面查看使用者清單,再假設其中只有您的帳戶。
未打算發布的連接埠
Docker image 會在容器內監聽 port 80。裸機安裝預設使用 ROCKET_PORT=8000。文件中的執行指令會以這種方式發布該連接埠:
--publish 127.0.0.1:8000:80127.0.0.1: 前綴才是關鍵。改用 -p 8000:80 時,Docker 會繫結 0.0.0.0,並將 DNAT(destination network address translation)規則寫入 nat table。這些規則會在 ufw 管理的 filter chain 之前進行評估,因此 ufw status 會回報該連接埠遭到拒絕,但該連接埠實際上仍會回應網際網路請求。完整機制請參閱Docker port 繞過 ufw 的指南。
檢查實際正在監聽的項目:
sudo ss -tlnp | grep 8000正常結果應只有一行繫結至 127.0.0.1:8000。繫結至 0.0.0.0:8000 的行表示 vault 直接暴露在外。修正 mapping,然後重新建立容器,因為 port binding 會在建立容器時固定,而 docker compose restart 不會變更它:
docker compose up -d --force-recreate舊版指南中還會看到另一個 port:3012,也就是獨立的 WebSocket port。Vaultwarden 1.31.0 已移除對該 port 的支援,因為通知流量已移至主要 HTTP port。自 1.29.0 起,WEBSOCKET_ENABLED 與 WEBSOCKET_PORT 會被忽略。目前使用的設定是 ENABLE_WEBSOCKET,預設值為 true。如果 firewall 或 compose file 仍開放 3012,請將其關閉。
在反向代理終止 TLS,不要在 Rocket 中處理
Vaultwarden 可透過其 Web framework Rocket 自行提供 TLS,但專案方建議不要在 production 中這麼做。Rocket 內建的 TLS 缺少嚴格的 SNI(server name indication)支援,這也是強化建議要求透過主機名稱存取執行個體、絕不要直接使用裸 IP 位址的原因。公開 IP 範圍會持續遭到掃描,而在 IP 位址上回應的 vault 就會被找到。
nginx server block 中需要注意的部分如下:
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}nginx 預設將 client_max_body_size 設為 1 MB,因此缺少該行時,附件上傳會失敗,並在 nginx error log 中出現 413 Request Entity Too Large;Vaultwarden 則完全不會記錄任何內容。Upgrade 與 Connection 標頭會將 WebSocket handshake 傳遞至 /notifications/hub。省略這些標頭後,vault 仍可運作,但變更不會再出現在其他裝置上,除非手動重新載入頁面。
Caddy 的設定較短,且會自行取得憑證:
vw.example.com {
reverse_proxy 127.0.0.1:8000 {
header_up X-Real-IP {remote_host}
}
}接著告知 Vaultwarden 相關資訊:
DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IPIP_HEADER 的預設值已是 X-Real-IP,因此需要確認 proxy 確實設定了該標頭。若沒有設定,每一行 log 與每項登入速率限制都會看到 127.0.0.1,也就是 proxy 本身。這表示一名攻擊者的失敗嘗試會計入該執行個體的所有使用者。也請將 DOMAIN 設為實際的 https URL,因為 Vaultwarden 會根據它建立邀請連結與密碼重設連結,而 WebAuthn security key 也會繫結至該 origin。
還有一點常被忽略:WebSocket connection 會以 /notifications/hub?access_token=[JWT] 的形式,將 session token 放在 query string 中。這會以明文出現在 proxy access log 中。請在 log format 中遮罩 access_token 參數,或確認這些 log 不會傳送至任何不受你控制的位置。
封鎖登入端點的暴力破解
速率限制預設已啟用(LOGIN_RATELIMIT_SECONDS=60、LOGIN_RATELIMIT_MAX_BURST=10)。這會降低攻擊者的嘗試速度,但無法阻止攻擊。fail2ban 可以阻止攻擊,但 Vaultwarden 必須先寫入日誌檔案,而這不是預設行為:
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=true登入失敗後會產生一行記錄,篩選器必須比對這個字串:
[2026-08-07 09:14:22][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.10. Username: user@example.com.將篩選器寫入 /etc/fail2ban/filter.d/vaultwarden.local:
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =再將 jail 寫入 /etc/fail2ban/jail.d/vaultwarden.local:
[vaultwarden]
enabled = true
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400如果保留管理員頁面,請再加入第二個 jail,將其 failregex 設為 ^.*Invalid admin token\. IP: <ADDR>.*$,因為管理員登入失敗會使用不同訊息記錄,而登入篩選器永遠無法比對這些訊息。接著檢查設定:
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwarden正常運作的 jail 會在 File list 列出日誌檔案,並顯示 Currently failed: 0。從不同的網路輸入錯誤密碼 3 次,該計數器應會增加,接著該位址會出現在 Banned IP list 下方。如果計數器始終不變,最常見的原因是 logpath:這必須是主機上的檔案路徑,而不是容器內的 /data/... 路徑。第二個常見原因是缺少 X-Real-IP,導致所有封鎖目標都變成你自己的代理伺服器。其他設定(包括你應已啟用的 SSH jail)請參閱Ubuntu 24.04 的 fail2ban 指南。
主密碼仍是整個系統的核心
用戶端加密表示主密碼就是加密金鑰。如果攻擊者已複製某個 instance 的資料庫,短主密碼不會受到本文任何設定保護,因為攻擊者可以在離線狀態下,以其硬體允許的速度攻擊該副本。伺服器上的設定無法影響攻擊者自己的機器。
PASSWORD_ITERATIONS=600000 很容易誤解。它設定的是伺服器端對用戶端傳送之登入雜湊值所使用的雜湊迭代次數。專案的設定範本表示,變更後的值會在現有使用者下次登入時套用。它不會影響用戶端 KDF;真正決定離線攻擊 vault 成本的是 KDF 設定。這項設定屬於各個帳戶:多年前註冊的使用者可能仍在使用較舊、較低的 KDF 設定,直到他們在 Web Vault 的安全性設定中提高設定值,並重新加密其金鑰。請告知使用者,因為伺服器無法代替他們完成這項操作。
接著,為每個帳戶啟用雙因素驗證。它無法保護密文,因為 vault 金鑰只來自主密碼。但它能防止攻擊者僅憑竊取的密碼登入並同步副本。REQUIRE_DEVICE_EMAIL=true 會強制要求新裝置登入確認信件:如果 Vaultwarden 無法寄出信件,登入就會失敗,因此未識別裝置的登入不會在未通知的情況下完成。這需要可正常運作的 SMTP,否則所有新裝置登入都會遭拒。
自架 vault 最容易在備份環節出錯
將資料目錄建立 tar czf,並留在同一台 VPS 的 home directory 中,會抵銷前述所有防護措施。該封存檔包含 db.sqlite3,其中有每位使用者的 ciphertext;也包含 rsa_key.pem,可用來偽造登入工作階段;以及 config.json,以純文字保存 admin token 和 SMTP password。只要能讀取這個檔案,就等於能讀取 vault。
只要遵守兩項規則即可。將封存檔移出該主機。在移出前先加密。
此外還有正確性問題。服務執行期間,使用 cp 複製 db.sqlite3,可能會產生仍在寫入中的檔案。該檔案將無法開啟,而你要到還原時才會發現。請改用 SQLite 自己的 snapshot:
sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"還原端通常沒有人測試,但這部分已涵蓋在 Vaultwarden 備份與還原指南 中。
自架 Vaultwarden 相較於託管 Bitwarden 的取捨
坦白評估。Bitwarden 的託管服務由全職負責營運的人員管理,並提供公開的第三方稽核報告,也有人在凌晨 3 點待命。自架服務則必須由你自行安排修補週期。
Vaultwarden 會以一般版本發布安全性修正,而且發布頻率不低。根據 2026 年 10 月 7 日檢查專案的版本來源,目前版本為 1.37.4,於 2026 年 10 月 5 日發布。版本說明涵蓋 7 個安全性公告,其中最嚴重的項目評為 High,並強烈建議儘快更新。這是自 2026 年 7 月 24 日發布 1.37.0 以來的第 4 個版本:1.37.1 於 7 月 29 日發布,1.37.2 於 8 月 22 日發布(2026.8.0 及更新版本的用戶端必須使用此版本),1.37.3 於 9 月 13 日發布;該版本會在認證資料或 2FA 變更時撤銷 2FA remember token,並限制 prelogin 與 auth request endpoint 的請求速率。如果你執行的 organization 中有不完全信任的管理員,1.37.4 的版本說明也建議在更新後輪替每個 organization 的 API key。你一年前設定、之後忘記管理的執行個體,現在使用的就是一年前的程式碼。latest 標籤本身沒有幫助:執行中的容器會持續使用啟動時採用的 image,直到你執行 docker compose pull 並重新建立容器。請為主機套件設定 Ubuntu 的無人值守升級,並將容器更新加入你確實會查看的行事曆提醒。
坦白說,讀者應得出的結論是:這裡的密碼學採用 Bitwarden 的設計,安全性仍然可靠,但營運風險完全轉移到你身上。如果你會修補系統,並將備份存放在其他位置,那麼在自己控制的 VPS 上執行 Vaultwarden,是保存密碼的合理方式。如果這兩項習慣不會落實,請付費使用託管服務,把時間與注意力用在其他地方。逐項功能比較請參閱 Vaultwarden 與自架 Bitwarden 的比較。
強化容器底層的主機
Vaultwarden 是 Linux 主機上的一個程序。無論應用程式如何設定,該主機上的 root 都能讀取 /vw-data。在 compose 檔案中使用 user: "1000:1000",讓容器以非特權使用者執行,並讓資料資料夾的擁有者與其相符。對容器不需寫入的內容,使用 :ro 以唯讀方式掛載。接著關閉入口:VPS 上的 SSH 強化涵蓋僅使用金鑰登入及停用密碼驗證。這可阻止繞過上述防護的常見攻擊。
FAQ
如果 Vaultwarden 資料庫遭竊,別人能讀取我的密碼嗎?
不能直接讀取。每個 vault 項目都會在用戶端使用衍生自主密碼的金鑰加密,因此 db.sqlite3 只包含密文。對方立即取得的內容包括每個帳戶的電子郵件地址、KDF 設定、登入與裝置中繼資料,以及 twofactor 資料表中的雙因素驗證密鑰。由於伺服器必須計算預期的驗證碼,這些密鑰會以未加密形式儲存。對方也能無限期離線破解 vault 密文,因此主密碼長度才是決定結果的關鍵。
我應該使用 ADMIN_TOKEN,還是完全停用管理頁面?
如果可以,請停用管理頁面。大多數執行個體只需要使用它設定 SMTP 與邀請使用者一次,之後就不再需要。若要停用,請不要設定 ADMIN_TOKEN 或 DISABLE_ADMIN_TOKEN,從 config.json 移除任何 "admin_token" 金鑰,然後重新建立容器。只移除環境變數並不足夠,因為管理頁面寫入的設定會儲存在 config.json,並且具有優先權。如果仍要保留管理頁面,請使用 vaultwarden hash 產生的 Argon2 雜湊值儲存 token,不要使用明文的隨機字串,並設定 ADMIN_RATELIMIT_MAX_BURST=3。
我的 ADMIN_TOKEN 正確,但 /admin 拒絕它。問題出在哪裡?
幾乎都是 $ 插值造成的問題。Argon2 PHC 字串包含數個 $ 字元,而 Docker Compose 會在 docker-compose.yml environment: 區塊中將它們展開為變數。因此,容器收到的值會遭到改寫,即使檔案中的內容看起來正確。請在 compose 檔案中將每個 $ 加倍為 $$,或將值移至以單引號包住的 .env 檔案中,這樣就不需要跳脫。之後請重新建立容器,因為重新啟動不會套用環境變更。
通知功能仍需要開放連接埠 3012 嗎?
不需要。Vaultwarden 1.31.0 已移除連接埠 3012 上的 WebSocket 流量支援,因為通知功能已移至主要 HTTP 連接埠;WEBSOCKET_ENABLED 與 WEBSOCKET_PORT 自 1.29.0 起也會被忽略。目前的設定是 ENABLE_WEBSOCKET,預設值為 true。請在防火牆中關閉 3012,並從 compose 檔案中刪除該連接埠。接著確認反向代理會轉送 Upgrade 與 Connection 標頭,因為即時同步目前實際依賴這些標頭。