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

Vaultwarden VPS 自架密碼管理器設定與安全部署

使用 Docker 在 VPS 部署 Vaultwarden,先完成 HTTPS 才能連線,512 MB RAM 可運作;並設定 admin token、Fail2ban 與備份還原測試。

建置內容

這是一套完全由你自行掌控的密碼管理器:Vaultwarden 在單一小型容器中執行,由負責終止 HTTPS 的反向代理置於前方;手機、筆記型電腦與瀏覽器上的官方 Bitwarden 應用程式則連線至這個服務。Vaultwarden 以 Rust 重新實作 Bitwarden 伺服器 API,使用與 bitwarden.com 相同的通訊協定,因此所有官方用戶端都能不經修改直接連線;但其記憶體需求約為 100 MB,低於官方多容器堆疊。

安裝本身只需十多行 Compose 設定。真正重要、也最容易出問題的是以下三點:在首次載入 Web Vault 前,TLS 必須已經可用;建立自己的帳戶後,必須立即關閉公開註冊;資料磁碟區必須完成備份並測試還原,因為該目錄存放你擁有的所有密碼。

先決條件與必須注意的實際問題

  • 一台安裝 Docker Engine 與 Compose plugin 的 VPS。主機應是全新的 Ubuntu 24.04 KVM 主機,並具備 root 或 sudo 權限。512 MB RAM 確實足夠,1 GB 則較為寬裕。這是可執行的服務中最輕量的項目之一,位於值得自行代管的服務清單前段。不過,仍應依共用該主機的其他服務規劃規格:若在同一台 VPS 上部署PhotoPrism 或 Immich 等自行代管的相片庫,RAM 最低需求會提高到數 GB;Vaultwarden 幾乎不會增加負載。同樣的計算方式也適用於之後加上的媒體前端,因為將 Jellyfin 媒體庫整理成可瀏覽的 90 年代出租店,代表同一份資源預算還要負擔另一個持續運作的容器,以及轉碼所需的餘裕。
  • 一個具有 A record 的網域;若使用 IPv6,也應設定 AAAA record,並將其指向 vault.example.com VPS。TLS 憑證會核發給這個確切的名稱,因此開始操作前,DNS 必須已能正確解析。
  • 對網際網路開放 80 與 443 埠,並由反向代理終止連線,絕不能直接由 Vaultwarden 終止。80 埠僅用於 ACME 憑證驗證,以及將 HTTP 重新導向至 HTTPS。
  • 首要且最容易踩到的問題是:Bitwarden 用戶端拒絕連線至未使用 HTTPS 的伺服器。不存在「先用 http 測試」這種做法,因為該流程無法運作;下一節會說明具體原因。

為何選擇 Vaultwarden,而不是官方 Bitwarden stack

使用相同的用戶端,但資源負擔僅為一小部分。官方的自架 Bitwarden 會以容器套件形式部署,包括 MSSQL、Nginx、Identity、Api、Admin 等元件,約需要 2 GB RAM。Vaultwarden 則是單一 binary,預設會將所有資料儲存在 SQLite database 中,閒置時僅使用幾十 MB。對個人、家庭或小型團隊而言,這是明顯較合適的選擇。由於它完整實作 Bitwarden API,資料可在 Vaultwarden 與 bitwarden.com 之間移轉。

需要放棄的是大部分企業功能:不支援 SCIM provisioning(但實驗性的 OpenID Connect SSO 已在 1.35.0 加入)。此外,您是系統管理者,因此修補程式、HTTPS 與備份都由您負責。本指南將說明這三項工作。

HTTPS 為何不是選用項目

Bitwarden web vault 與瀏覽器擴充功能會在瀏覽器中使用 Web Crypto API(window.crypto.subtle)衍生加密金鑰。瀏覽器只會在 secure context 中提供 crypto.subtle,也就是 HTTPS,或特殊的 http://localhost。透過純 HTTP(http://vault.example.com)時,該功能為 undefined,因此應用程式一衍生金鑰就會擲出錯誤,主控台會顯示:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')

頁面會停滯或顯示一般性的加密錯誤,且無法登入。桌面、行動裝置與瀏覽器用戶端都會自行檢查 self-hosted URL;若端點使用 http 或無法連線,便會拒絕並顯示:

This is not a recognized Bitwarden server. You may need to check with your provider or update your server.

兩者的原因相同:沒有有效的 HTTPS。因此我們會先建立 TLS,絕不透過 http 開啟 vault,即使只是快速查看一次也不例外。

步驟 1:DNS 與反向代理(先處理 TLS)

將記錄指向您的 VPS,並確認解析到正確的位址:

dig +short vault.example.com

輸出的那一行必須是您的 VPS IP。若為空白或位址錯誤,請修正 DNS 並等待 TTL 生效。名稱無法解析時,憑證簽發會失敗。

本指南使用 Traefik 作為 HTTPS 前端。Traefik 會自動簽發及續期 Let's Encrypt 憑證,並可直接整合 Compose。若您尚未執行 Traefik,請先依照 Traefik 反向代理與自動 TLS 設定 操作;該設定會建立外部 Docker network(下方的 proxy)與 ACME resolver(letsencrypt),供 Vaultwarden 服務連接。從 Vaultwarden 的角度來看,使用一般 nginx 搭配手動簽發的憑證,運作方式完全相同。

偏好使用 nginx 與 Certbot,而不是 Traefik? 將 Vaultwarden 放在 127.0.0.1:8080 上(在服務中加入 ports: ["127.0.0.1:8080:80"],並移除 Traefik labels),接著簽發憑證並將代理流量轉送至該服務。憑證部分請參閱 使用 Certbot 與 nginx 簽發 Let's Encrypt 憑證。需要額外處理的重點,是 notifications 路徑上的 WebSocket upgrade:

server {
    listen 443 ssl;
    server_name vault.example.com;

    client_max_body_size 525M;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

請注意 X-Real-IP 這一行。它讓 Fail2ban 之後能看到實際的攻擊者,而不是 127.0.0.1。無論前端使用 Traefik 或 nginx,本指南的其他部分都相同。

第 2 步,Compose 檔案

先建立專案目錄。本指南使用 /opt/vaultwarden,讓 Compose 專案名稱以及資料磁碟區名稱 vaultwarden_vw-data 保持可預測;下方的 Fail2ban 與備份步驟都依賴這個確切名稱。

sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwarden

在該目錄中建立一個 .env,以及 Compose 檔案。

# .env
ADMIN_TOKEN=paste-a-strong-token-here

使用 openssl rand -base64 48 產生該權杖,然後貼上。下一節會說明更安全的雜湊形式;一開始使用長的隨機字串即可。

# docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.example.com"
      SIGNUPS_ALLOWED: "true"          # closed in Step 4, keep true just to register
      ADMIN_TOKEN: "${ADMIN_TOKEN}"
      IP_HEADER: "X-Forwarded-For"     # X-Real-IP if your proxy sends that instead
      LOG_FILE: "/data/vaultwarden.log"
      LOG_LEVEL: "warn"
    volumes:
      - vw-data:/data
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vw.rule=Host(`vault.example.com`)"
      - "traefik.http.routers.vw.entrypoints=websecure"
      - "traefik.http.routers.vw.tls.certresolver=letsencrypt"
      - "traefik.http.services.vw.loadbalancer.server.port=80"

volumes:
  vw-data:

networks:
  proxy:
    external: true

這個檔案中有兩項設定支撐整體設計。檔案中沒有 ports: 對映,因此 Vaultwarden 只能透過 Traefik 及其 TLS 連線;在主機上公開其連接埠,會導致使用者意外透過 http 提供 vault。DOMAIN 必須是完整的公開 HTTPS URL:系統會將它寫入附件連結、WebAuthn 2FA 與 notifications endpoint,因此即使網站可以載入,URL 錯誤或使用 http 仍會使這些功能失效。latest 標籤是對一般不得使用 latest 規則的刻意例外。Vaultwarden 將穩定版本以單一持續更新映像檔提供,並以 :testing 作為獨立的預發布通道,因此請依計畫更新,並在 pull 前快速查看版本資訊。這項例外的適用範圍很窄;大多數長期執行的容器最好固定使用確切標籤,這能讓 在同一 VPS 上持續運作的自架代理程式 在重新開機與 pull 後維持可預測性。

啟動服務並監控日誌:

docker compose up -d
docker compose logs -f vaultwarden

正確啟動後,結尾會出現類似 Rocket has launched from http://0.0.0.0:80 的行。等待 Traefik 幾秒以取得憑證,然後載入 https://vault.example.com;此時應會看到 Bitwarden web vault,瀏覽器顯示有效的鎖頭,且沒有憑證警告。

步驟 3:強大的 ADMIN_TOKEN 與 $$ 陷阱

ADMIN_TOKEN 會保護 /admin。這是可以讀取執行個體中每位使用者及所有設定的管理面板,因此應將它視同 root 密碼妥善保管。可使用以下兩種形式。

簡單形式是使用你剛才透過 openssl rand -base64 48 產生的隨機字串。由於 base64 不會包含 $,因此可直接放入 .env,無須跳脫。

強化形式是 Argon2 PHC 雜湊,因此磁碟上不會儲存純文字 token。請使用相同的映像檔產生雜湊:

docker run --rm -it vaultwarden/server /vaultwarden hash --preset owasp

此命令會提示你輸入兩次,然後輸出以 $argon2id$v=19$... 開頭的字串。以下是容易讓人花上一小時排查的陷阱:Docker Compose 會將 $ 視為變數插值,因此將雜湊貼入 Compose 檔案時,必須將每個 $ 加倍為 $$。請直接放在 environment: 下方,不要透過 .env,也不要用引號包住:

    environment:
      ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObG

如果保留單一 $ 符號,Compose 會發出警告 The "argon2id" variable is not set 並將 token 清空,接著 /admin 會拒絕你輸入的正確密碼。請執行 docker compose up -d,並將你在提示中輸入的純文字保存在自己的密碼管理工具中。

步驟 4:註冊帳戶,然後關閉註冊

使用 SIGNUPS_ALLOWED: "true" 開啟 https://vault.example.com,按一下 Create account,並使用電子郵件地址和高強度主密碼註冊。主密碼無法復原,也沒有重設功能,因此請先將它儲存在可靠的位置。

現在關閉註冊功能。編輯 Compose 檔案,停用註冊:

      SIGNUPS_ALLOWED: "false"

使用 docker compose up -d 重新套用設定。這項強化措施不能延後。註冊功能開啟時,任何找到 URL 的人都能在您的伺服器上建立帳戶,搜尋引擎爬蟲也可能找到該 URL。他們無法讀取您的保存庫,但會消耗資源,並將您的私有執行個體變成公開服務。若 /admin 列出您從未建立的帳戶,就表示您仍未關閉註冊功能。

日後若要新增家人或團隊成員,請使用 /admin 中的 Invite User 按鈕,不必重新開放公開註冊。此流程需要先設定 SMTP,受邀者才能收到邀請連結。

步驟 5:存取 /admin

瀏覽至 https://vault.example.com/admin,並輸入純文字管理員權杖(隨機字串,或是你雜湊處理的密碼,不是雜湊值本身)。進入後,可以列出使用者、調整設定、傳送測試電子郵件,以及建立資料庫快照。

如果頁面回傳 404 Not Found,表示 ADMIN_TOKEN 為空或未設定,這會完全停用管理面板。若你不需要管理面板,這也是有效的選擇。如果頁面能載入,但拒絕你的權杖,請參閱下方失敗清單中的 $$ 跳脫問題。忘記權杖了嗎?系統沒有復原提示;請編輯 .env 或 Compose 檔案,設定新的權杖,然後執行 docker compose up -d

步驟 6,連線 Bitwarden 用戶端

每個官方用戶端都能連線至自架伺服器。因此,請從一般應用程式商店安裝 Bitwarden 桌面、行動或瀏覽器用戶端;不需要特殊的 Vaultwarden 組建。

登入前,請在登入畫面開啟設定齒輪(標示為 Self-hostedRegion → Self-hosted),將 Server URL 設為 https://vault.example.com,然後儲存。接著使用註冊時提供的電子郵件地址與主密碼登入。用戶端應會立即連線,並提供填入及儲存認證資料的功能。

如果用戶端顯示 This is not a recognized Bitwarden server. You may need to check with your provider or update your server.,表示 URL 錯誤、使用了 http,或憑證不受信任。請先確認在瀏覽器中開啟 https://vault.example.com 時沒有錯誤。其他裝置上的更新速度較慢,原因是 WebSocket 推送;下文會說明相關設定。

步驟 7,為登入端點設定 Fail2ban jail

Vaultwarden 會將每次失敗的登入記錄到 LOG_FILE 所指定的檔案,這正是暴力破解防護機制所需的資訊。如果您尚未執行 Fail2ban,請參閱Fail2ban SSH 強化指南中的安裝與基本設定;本節只為 vault 新增一個 jail。

先找出 named volume 在主機上的位置,讓 Fail2ban 能讀取日誌:

docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}'

此命令會輸出類似 /var/lib/docker/volumes/vaultwarden_vw-data/_data 的內容;其中的日誌位於 vaultwarden.log。建立 filter:

# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =

接著建立 jail:

# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled   = true
filter    = vaultwarden
logpath   = /var/lib/docker/volumes/vaultwarden_vw-data/_data/vaultwarden.log
banaction = iptables-allports
chain     = DOCKER-USER
maxretry  = 5
findtime  = 600
bantime   = 3600

使用 sudo systemctl restart fail2ban 重新載入,並以 sudo fail2ban-client status vaultwarden 確認。

以下 3 個 Docker 細節會決定這項防護是否有效。第一,如果日誌在每次失敗嘗試中都顯示 IP: 127.0.0.1 或代理伺服器的位址,Vaultwarden 封鎖的會是代理伺服器。請將 IP_HEADER 設為代理伺服器實際傳送的標頭:Traefik 使用 X-Forwarded-For,上方 nginx 區塊使用 X-Real-IP,Cloudflare 後方使用 CF-Connecting-IP。第二,正確的 iptables chain 取決於代理伺服器的設定:如果 Traefik 以已發布連接埠的容器執行,流量會經過 Docker 的 FORWARD 路徑,因此封鎖規則必須如上所示放在 DOCKER-USER;但如果您在步驟 1 選擇 host-nginx 選項,連線會在主機的 INPUT chain 上由 nginx 終止,DOCKER-USER ban 將無法攔截這些連線。此時請刪除 chain = DOCKER-USER 行,讓 Fail2ban 使用預設的 INPUT chain。第三,請使用 banaction = iptables-allports,不要使用依連接埠設定的預設值。此 jail 未定義連接埠,而在 DOCKER-USER 中封鎖所有連接埠,可以讓該來源主機無法存取此主機上發布的任何服務。

步驟 8:備份 vault,然後實際還原

vw-data volume 就是你的密碼管理器。它包含 db.sqlite3(每筆項目)、attachments/sends/ 目錄、用來簽署登入工作階段的 rsa_key.* 檔案,以及管理面板中的 config.json。備份只要漏掉其中任何一項,真正需要時就會還原失敗。

Vaultwarden 寫入 db.sqlite3 時進行複製,可能會取得尚未寫入完成的損毀檔案。因此請建立冷快照,停機時間只需幾秒:

#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F)
DEST=/root/vw-backups
VOL=$(docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}')
mkdir -p "$DEST"
docker compose -f /opt/vaultwarden/docker-compose.yml stop vaultwarden
tar czf "$DEST/vw-$STAMP.tgz" -C "$VOL" .
docker compose -f /opt/vaultwarden/docker-compose.yml start vaultwarden

請透過 cron 每晚執行,並將 .tgz 複製到伺服器外部。只存放在受保護伺服器上的備份,不算是真正的備份。穩妥的做法是使用 每晚以 restic 備份至另一台伺服器或物件儲存,由它加密封存檔,並自動對重複的快照進行去重。管理面板中的 Backup Database 按鈕可快速建立 SQLite 檔案本身的即時快照,但不包含附件與金鑰。

接下來是區分真正備份與僥倖心理的步驟:還原一次,確認它確實可用:

mkdir -p /tmp/vw-restore
tar xzf /root/vw-backups/vw-2026-07-15.tgz -C /tmp/vw-restore
docker run --rm -p 127.0.0.1:8888:80 -v /tmp/vw-restore:/data vaultwarden/server

從筆記型電腦使用 ssh -L 8888:127.0.0.1:8888 you@your-vps 建立通道,然後開啟 http://localhost:8888。由於 localhost 是安全內容環境,crypto.subtle 可用,因此 vault 可在此透過純 http 解密;這是唯一允許如此操作的地方。使用主密碼登入,確認項目都在:如果項目存在,表示資料庫、RSA 金鑰與主密碼都能完整往返還原,之後可在幾分鐘內於新的 VPS 上重建。使用 Ctrl-C 停止容器,然後刪除 /tmp/vw-restore。對伺服器上其他不應直接暴露於網際網路的管理介面,也請維持建立通道的習慣;例如,存取執行於 port 5173 的 自架 open-kritt 安全掃描器時,也應採用相同方式。

錯誤模式與畫面中會看到的字串

瀏覽器主控台中出現 Cannot read properties of undefined (reading 'importKey') Vault 是透過 http 載入,因此 crypto.subtle 未定義。只能透過 https:// 存取,並在代理伺服器上加入 HTTP-to-HTTPS 重新導向。

用戶端中出現 This is not a recognized Bitwarden server... Server URL 使用 http、輸入錯誤,或憑證不受信任。確認 https://vault.example.com 顯示有效的鎖頭圖示,再到用戶端的 self-hosted 設定中重新輸入。

/admin 拒絕正確的密碼。 Argon2 雜湊值的跳脫字元遺失。Compose 中的每個 $ 都必須是 $$,或是你輸入了雜湊值,而不是它所代表的明文。

跨裝置同步速度緩慢;主控台顯示 WebSocket connection to 'wss://vault.example.com/notifications/hub' failed 代理伺服器未轉送 Upgrade/Connection 標頭。Traefik 會自動處理,nginx 則需要加入 Step 1 的兩行 upgrade 設定。Vault 仍可運作,但只會在開啟時同步。舊的專用連接埠 3012 已在 v1.31.0 移除,因此不需要另外設定 WebSocket 路由。

Fail2ban 回報已封鎖,但攻擊者仍持續連線。 Fail2ban 封鎖的是 127.0.0.1,因為 IP_HEADER 設定錯誤,或封鎖規則位於錯誤的 iptables chain。請設定 chain = DOCKER-USERbanaction = iptables-allports

升級

拉取新映像並重新建立容器;具名磁碟區與所有資料都會保留:

docker compose pull
docker compose up -d

Vaultwarden 經常發布新版本。請監看專案的版本資訊,不要固定 patch 版本,因為部分版本包含移轉注意事項。在進行任何重大升級前,先建立最新備份;若需要回復,可以將 tarball 還原至新的磁碟區。

FAQ

Vaultwarden 與 Bitwarden 相同嗎?

Vaultwarden 是相容但獨立的伺服器,不是官方伺服器。Vaultwarden 以 Rust 重新實作 Bitwarden 伺服器 API,因此官方桌面、行動裝置、瀏覽器與 CLI 用戶端都能連線使用,而且所需資源遠低於官方技術堆疊。保存庫格式相同,因此可以透過匯出與匯入雙向移轉。

我真的需要 HTTPS 嗎?還是可以在 LAN 上透過 http 執行?

除了 localhost 測試之外,其他情況都需要 HTTPS。Bitwarden 網頁保存庫與擴充功能會使用瀏覽器的 Web Crypto API,而該 API 只有在安全內容環境中才可用。因此,透過純 http 連線時,用戶端會顯示 Cannot read properties of undefined,且永遠無法登入。唯一可運作的 http 位址是 http://localhost,這也是第 8 步的還原測試使用 SSH tunnel 的原因。

如何阻止陌生人在我的伺服器上註冊?

在建立自己的帳戶後,立即於 Compose 檔案中設定 SIGNUPS_ALLOWED: "false",並執行 docker compose up -d。之後,透過 /admin 中的 Invite User 按鈕新增使用者。這需要先設定 SMTP,讓使用者能收到邀請連結。請定期檢查管理員使用者清單,確認沒有出現預期外的帳戶。

如何備份 Vaultwarden 保存庫?

暫時停止容器,並將整個 vw-data volume、db.sqlite3attachments/sends/config.jsonrsa_key.* 檔案封存,然後將封存檔複製到伺服器外部;理想情況下,透過 nightly cron 執行。伺服器執行期間複製運作中的 SQLite 檔案,可能會產生損毀的快照,因此應在服務停止時備份。最重要的是,先將備份還原到一次性容器並登入,以確認備份有效,再依賴這份備份。

自行代管密碼真的安全嗎?

可以,只要完成本指南涵蓋的 3 件事:使用真正的 HTTPS、關閉註冊並設定強健的管理員 token,以及測試備份。保存庫會以主密碼在用戶端加密,因此即使伺服器也看不到明文密碼;沒有主密碼,遭竊的 db.sqlite3 毫無用途。代價是修補程式與備份現在由你負責,因此這裡的 Fail2ban 與還原流程不是可有可無的選項。完成這些設定後,進一步了解自架保存庫實際可能遭受攻擊的位置 是有用的下一步,因為保存庫項目本身已在用戶端加密,剩下需要防護的就是管理員 token 與備份封存檔。