Vaultwarden 安全嗎?完整強化檢查清單
Vaultwarden 會在用戶端加密每個 vault 項目,伺服器不持有明文;本文逐項檢查 admin token、公開連接埠與備份檔案等實際風險。
Vaultwarden 安全嗎?簡短回答
Vaultwarden 在最重要的環節是安全的,因為每個 vault 項目都會先在你的裝置上加密,再傳送到伺服器。伺服器儲存的是它無法讀取的資料區塊。即使有人複製整個資料庫,仍需要 master password 才能取得其中的有效內容。
這個答案涵蓋了許多前提,而真正容易出問題的地方,通常是你的設定。管理面板使用容易猜到的 token。容器連接埠公開發布到整個網際網路。明文 config.json。備份 tarball 放在同一台主機的 home directory 中。這些都不是密碼編譯問題,但都是自架 vault 內容遭到清空的原因。
以下內容假設你已完成可正常運作的安裝。如果尚未安裝,請先參閱 VPS 上的 Vaultwarden 安裝指南 完成設定,再依照下列順序逐項檢查。
伺服器實際儲存的內容
Vaultwarden 實作 Bitwarden 的資料模型。Vault 項目的名稱、使用者名稱、密碼、備註與 URI,會在用戶端中以主密碼衍生出的金鑰加密,之後才送出任何請求。附件檔案內容也會以相同方式加密。伺服器收到的是附帶 UUID (universally unique identifier) 的不可讀資料。
有些內容不是 ciphertext,必須明確了解其中包括哪些:
- 使用明文儲存的帳戶電子郵件地址。
- KDF (key derivation function) 設定與 salt,因為用戶端下次登入時需要這些資訊來重建金鑰。
- 用戶端送出的主密碼雜湊值之伺服器端雜湊,用於驗證登入本身。
- 中繼資料:組織成員資格、裝置名稱、上次登入時間。
- 保護 Vaultwarden 登入的 two-factor method secret。該 secret 未加密儲存在
twofactor資料表中,因為伺服器必須計算預期代碼,才能與你的代碼比對。這與你儲存在 Vault 項目內的 TOTP (time-based one-time password) secret 不同;後者會像其他欄位一樣加密。
資料目錄很小。在 Docker 安裝中,就是你掛載到 /data 的目錄。
sudo ls -l /vw-data/db.sqlite3 幾乎包含所有狀態。attachments/ 儲存上傳的檔案,每個 UUID 一個;這也是唯一一類不儲存在資料庫資料表中的重要資料。sends/ 儲存 Send 附件,設計上應為暫存資料。icon_cache/ 可以捨棄。rsa_key.pem 及其相關檔案會簽署已登入使用者的 JWTs (JSON web tokens),因此取得該私密金鑰的副本後,即可偽造 Vault 登入工作階段。只有在啟用管理頁面後,config.json 才會存在;專案文件對此說明得很直接:其中以明文儲存管理員 token 與 SMTP 憑證。
因此,實際的威脅模型是檔案系統存取,而不是網路加密。只要能讀取該目錄,就能取得每位使用者的電子郵件地址、登入 2FA secrets、可偽造工作階段的金鑰,以及每個 Vault 的離線副本,並有充裕時間進行攻擊。以下每個步驟,都是為了阻止他人存取該目錄。
先修正 admin token
/admin 是完整的控制面板,可管理使用者清單、邀請、刪除操作及所有執行環境設定。它只受一組共用 secret 保護,沒有其他防護。沒有 username,也沒有 per-user two-factor。
較早期的指南會告訴你使用 openssl rand -base64 48 產生 ADMIN_TOKEN。這種方式可正常運作,但會將 secret 以明文寫入 config.json 及 compose file。Vaultwarden 也接受 Argon2 PHC(password hashing competition)字串,因此儲存的值會改為 hash。請對執行中的 container 產生:
docker exec -it vaultwarden /vaultwarden hash如果完全不想接觸執行中的 container,也可以使用:
docker run --rm -it vaultwarden/server /vaultwarden hash它會要求輸入密碼 2 次,然後輸出一行以 $argon2id$ 開頭的內容。在 bare-metal 安裝中,請執行 ./vaultwarden hash。如果你想直接使用 argon2 CLI,專案文件列出了 OWASP 的最低參數:
echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1接下來是最容易讓人浪費 1 小時的陷阱。PHC 字串包含許多 $ 字元,而 Docker Compose 會將 $ 視為變數插值。在 environment: 區塊中直接貼上且未進行逸出時,傳入 container 的值會遭到破壞,因此 /admin 會拒絕你確定正確的 token。有 2 種安全寫法。在 docker-compose.yml 中,將每個 $ 加倍:
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UI在 .env file 中不需要逸出,但請使用單引號:
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'接著限制控制面板的請求速率,並縮短其 session:
ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20在 5 分鐘內有 3 次錯誤嘗試時,控制面板會停止回應該 client。管理員 session 在閒置 20 分鐘後過期。
更好的做法是直接關閉此頁面。大多數 instance 只需使用它 1 次來設定 SMTP 並邀請第一批使用者,之後便不再需要。若要停用,請勿設定 ADMIN_TOKEN 或 DISABLE_ADMIN_TOKEN,從 config.json 移除任何 "admin_token" key,然後重新建立 container。從 file 中刪除 key 很重要,因為管理頁面會將設定寫入該處,而 config.json 中的內容優先於 environment。只移除變數仍會讓頁面保持開放。
在有人發現該網域前關閉註冊
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 會在 container 內監聽 port 80。裸機安裝預設使用 ROCKET_PORT=8000。文件中的執行命令會以以下方式發布該連接埠:
--publish 127.0.0.1:8000:80127.0.0.1: 前綴才是關鍵。改成 -p 8000:80 後,Docker 會綁定 0.0.0.0,並將 DNAT(目的地網路位址轉譯)規則寫入 nat table。這些規則會先於 ufw 管理的 filter chains 評估,因此 ufw status 會回報該連接埠遭拒絕,但該連接埠仍會正常回應網際網路上的請求。完整機制請參閱Docker 連接埠繞過 ufw 的指南。
確認實際正在監聽的項目:
sudo ss -tlnp | grep 8000正常結果應只有一行綁定至 127.0.0.1:8000。若有一行綁定至 0.0.0.0:8000,表示 vault 直接暴露在外。修正對應設定後重新建立 container,因為 port binding 會在建立 container 時固定,而 docker compose restart 不會變更該設定:
docker compose up -d --force-recreate舊版指南還會提到另一個連接埠:3012,也就是獨立的 WebSocket port。Vaultwarden 1.31.0 已移除對它的支援,因為通知流量已移至主要 HTTP port。自 1.29.0 起,WEBSOCKET_ENABLED 與 WEBSOCKET_PORT 會被忽略。目前使用的開關是 ENABLE_WEBSOCKET,預設值為 true。如果防火牆或 compose file 仍開放 3012,請將其關閉。
在反向代理終止 TLS,不要在 Rocket 中終止
Vaultwarden 可透過其 Web framework Rocket 直接提供 TLS(傳輸層安全性),但專案文件不建議在 production 中這樣做。Rocket 內建的 TLS 不具備嚴格的 SNI(伺服器名稱指示)支援,因此強化安全性的建議是透過主機名稱連線到執行個體,絕不要直接使用裸 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 keys 會繫結至該 origin。
有一項細節常被忽略:WebSocket connection 會在 query string 中以 /notifications/hub?access_token=[JWT] 傳送 session token。這會以明文寫入 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 指南。
主密碼仍是整個系統的核心
用戶端加密表示主密碼就是加密金鑰。如果攻擊者已複製執行個體的資料庫,短主密碼不受本節任何設定保護,因為攻擊者會在離線環境中,以其硬體允許的速度破解該副本。伺服器上的設定無法影響攻擊者自己的機器。
建立新帳戶時,系統會將 PASSWORD_ITERATIONS=600000 的 KDF 迭代次數傳送給用戶端。現有帳戶會保留建立時使用的值,因此提高該值不會影響去年註冊的使用者。他們必須自行前往 Web vault 的安全性設定中變更,系統才會重新加密其金鑰。請主動告知使用者,因為介面不會提示這項資訊。
接著,為每個帳戶啟用雙因素驗證。它無法保護密文,因為 vault 金鑰只來自主密碼。但它能防止攻擊者只憑竊取的密碼登入並同步副本。REQUIRE_DEVICE_EMAIL=true 會在帳戶首次從未識別的裝置登入時,新增電子郵件確認步驟。
自架 vault 最容易在備份環節出錯
將資料目錄壓縮成 tar czf,並把它留在同一台 VPS 的家目錄中,會讓前面的所有防護失去作用。該封存檔包含每位使用者的密文 db.sqlite3、可偽造登入工作階段的 rsa_key.pem,以及以純文字儲存管理員 token 和 SMTP 密碼的 config.json。只要能讀取這個檔案,就等於能讀取整個 vault。
遵守兩項規則即可。將封存檔移出該主機。移出前先加密。
此外,還有資料正確性的問題。服務執行期間使用 cp 複製 db.sqlite3,可能會產生仍在寫入中的檔案,導致檔案無法開啟;而且要到還原時才會發現。請改用 SQLite 自己的 snapshot:
sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"還原端往往沒有人測試,相關步驟請參閱Vaultwarden 備份與還原指南。
託管 Bitwarden 之外,您需要放棄什麼
誠實評估。Bitwarden 的託管服務由全職負責營運的人員管理,並公開第三方稽核結果,也有人在凌晨 3 點待命。自行託管則必須由您自行負責修補更新節奏。
Vaultwarden 會以一般版本形式發布安全性修正。Version 1.37.0 於 24 July 2026 發布,截至 August 2026 仍是目前版本,其版本說明要求使用者儘快更新。您在一年前設定、之後忘記的執行個體,現在仍在執行一年前的程式碼。單靠 latest 標籤沒有幫助:執行中的容器會持續使用啟動時採用的 image,直到您執行 docker compose pull 並重新建立容器為止。請為主機套件啟用 Ubuntu 的 unattended upgrades,並將容器更新加入您確實會查看的行事曆提醒。
誠實的讀者應得出以下結論:這裡的密碼學採用 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 標頭,因為即時同步現在實際依賴這些標頭。