SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 已更新 2026-07-24

VPS 架設 Vaultwarden 密碼管理系統教學

使用 Docker 在 VPS 上部署輕量級 Vaultwarden,完美相容 Bitwarden App。本文詳細說明如何透過 Reverse Proxy 設定 HTTPS、管理 Admin Token、整合 Fail2ban 以及進行資料備份,確保您的密碼資料安全且僅需極低資源。

建立目標

建立一個完全由您掌控的密碼管理系統:在反向代理(Reverse Proxy)後方執行一個輕量級的 Vaultwarden 容器,並由該代理處理 HTTPS 終端加密。接著,在您的手機、筆電與瀏覽器上使用官方 Bitwarden App 並指向該服務。Vaultwarden 使用 Rust 語言重新實作了 Bitwarden 伺服器 API,其通訊協定與 bitwarden.com 完全相同,因此所有官方用戶端皆可直接使用。相較於官方的多容器架構,Vaultwarden 僅需約 100 MB 的 RAM。

安裝過程僅需十幾行 Compose 設定。以下是三個關鍵且最容易出錯的要素:在開啟 Web Vault 之前必須先設定好 TLS;建立個人帳號後必須立即關閉公開註冊功能;此外,必須對資料目錄(Data Volume)進行備份並測試還原,因為該目錄存放了您所有的密碼。

前置作業與注意事項

  • 一台安裝 Docker Engine 與 Compose plugin 的 VPS。建議使用全新的 Ubuntu 24.04 KVM 環境,並具備 root 或 sudo 權限。512 MB RAM 已足夠;若有 1 GB 則更為理想。這是極輕量化的應用程式之一,位居 值得自行架設的服務清單 前列。
  • 一個擁有 A 紀錄(若使用 IPv6 則需 AAAA 紀錄)並指向 vault.example.com 的網域。TLS 憑證將核發給此特定名稱,因此在開始前必須確保 DNS 解析正常。
  • 外部網路需開放 80 與 443 埠,並由反向代理(reverse proxy)處理連線 —— 絕不可由 Vaultwarden 直接處理。80 埠僅用於 ACME 憑證驗證與 HTTP 轉向 HTTPS。
  • 最主要的注意事項:Bitwarden 客戶端拒絕與非 HTTPS 的伺服器通訊。無法透過「先使用 HTTP 測試」的方式進行,因為下文將說明其原因。

為什麼選擇 Vaultwarden 而非官方 Bitwarden 堆疊

客戶端完全相同,但資源消耗極低。官方自架版 Bitwarden 以容器組合形式提供(包含 MSSQL、Nginx、Identity、Api、Admin 等),且需要約 2 GB 的 RAM。Vaultwarden 則是單一 binary 檔案,預設使用 SQLite 資料庫儲存所有資料,閒置時僅佔用數十 MB。對於個人、家庭或小型團隊而言,這是顯而易見的選擇;且由於其忠實實作了 Bitwarden API,您的資料可以在 Vaultwarden 與 bitwarden.com 之間自由遷移。

您所犧牲的是大部分的企業級功能:不支援 SCIM 配置(雖然 1.35.0 版本已推出實驗性的 OpenID Connect SSO),且您必須擔任維運人員,負責修補程式更新、HTTPS 與備份。本指南將涵蓋這三項工作。

為什麼 HTTPS 是必要的

Bitwarden 的 Web Vault 與瀏覽器擴充功能會使用 Web Crypto API (window.crypto.subtle) 在瀏覽器中推導加密金鑰。瀏覽器僅會在安全上下文 (secure context) 中提供 crypto.subtle,例如 HTTPS 或特殊的 http://localhost。在 plain http://vault.example.com 環境下,這是不被允許的 (undefined),因此應用程式在嘗試推導金鑰時會立即報錯,並在 console 顯示:

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

頁面會卡住或顯示通用的加密錯誤,且無法登入。桌面版、行動版與瀏覽器用戶端會針對自架 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,即使只是為了快速查看也不行。

Step 1 — DNS 與反向代理 (優先處理 TLS)

將紀錄指向您的 VPS,並確認解析結果為正確的位址:

dig +short vault.example.com

輸出的內容必須為您的 VPS IP。若結果為空白或錯誤,請修正 DNS 並等待 TTL 生效 — 若名稱無法解析,憑證核發將會失敗。

本指南的 HTTPS 前端使用 Traefik,它能自動核發並更新 Let's Encrypt 憑證,並能直接整合至 Compose。若您尚未部署,請先參考 Traefik 反向代理與自動 TLS 設定;該步驟會建立一個外部 Docker 網路 (如下方的 proxy) 以及一個 ACME 解析器 (letsencrypt),Vaultwarden 服務將連接至此。使用手動核發憑證的標準 nginx 對 Vaultwarden 而言效果相同。

比起 Traefik,您更偏好使用 nginx 與 Certbot? 請將 Vaultwarden 部署於 127.0.0.1:8080 (在服務中加入 ports: ["127.0.0.1:8080:80"] 並移除 Traefik labels),接著核發憑證並進行代理。憑證相關步驟請參閱 使用 Certbot 與 nginx 核發 Let's Encrypt 憑證。關鍵的額外設定是在通知路徑 (notifications path) 進行 WebSocket 升級:

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,本指南的其他內容皆完全相同。

Step 2 — the Compose file

請先建立專案目錄。本指南使用 /opt/vaultwarden,這會使 Compose 專案名稱(以及數據卷 vaultwarden_vw-data)是可預測的;下方的 Fail2ban 與備份步驟皆依賴此確切名稱。

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

在該目錄下建立一個用於存放 admin secret 與 Compose 檔案的 .env

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

使用 openssl rand -base64 48 產生該 token 並貼入。(下一節會介紹更強的雜湊形式;初期使用長隨機字串即可。)

# 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 進行存取;若將其 port 映射至 host,使用者可能會誤用 http 存取 vault。此外,DOMAIN 必須是完整的公開 HTTPS URL:它會被寫入附件連結、WebAuthn 2FA 以及通知端點,因此若設定錯誤或使用 http,即使網站能載入,這些功能也會失效。latest 標籤是刻意違反常見的「永不 latest」規則 —— Vaultwarden 的穩定版本採用單一滾動映像檔發布,並將 :testing 作為獨立的預覽版本頻道 —— 因此請主動更新,並在 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。

Step 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,並將您在提示字元輸入的明文儲存於您的密碼管理工具中。

Step 4 — 註冊帳戶,並關閉存取權限

使用 SIGNUPS_ALLOWED: "true" 開啟 https://vault.example.com,點擊 Create account,並使用電子郵件與強密碼進行註冊。此主密碼無法找回,且無法重設,請務必先將其妥善儲存。

接著關閉存取權限。編輯 Compose 檔案以停用註冊功能:

      SIGNUPS_ALLOWED: "false"

使用 docker compose up -d 重新套用設定。這並非必須立即執行的強化措施。若保持開啟狀態,任何發現該 URL 的人(包含網路爬蟲)都能在您的伺服器上建立帳戶。雖然他們無法讀取 您的 儲存庫,但會消耗系統資源,並將您的私有實例變成公開服務。若您未關閉註冊功能,徵兆如下:/admin 會列出您從未建立過的帳戶。

若日後需在不重新開啟公開註冊的情況下新增家人或團隊成員,請使用 /admin 中的 Invite User 按鈕;該流程需要配置 SMTP,以便受邀者能收到邀請連結。

Step 5 — 進入 /admin

瀏覽至 https://vault.example.com/admin 並輸入明文 admin token(請輸入隨機字串或您進行 hash 處理前的原始密碼,而非 hash 值本身)。進入後,您可以列出使用者、調整設定、發送測試郵件並進行資料庫快照。

若頁面回傳 404 Not Found,代表 ADMIN_TOKEN 為空或未設定,這會完全停用控制台——若您從不使用該功能,這也是一種合理的選擇。若頁面可載入但拒絕您的 token,請參閱下方錯誤清單中的 $$ 轉義陷阱。忘記 token?系統不提供復原提示;請編輯 .env 或 Compose file,設定新的 token,然後 docker compose up -d

Step 6 — 連線 Bitwarden 客戶端

所有官方客戶端皆可連向自架伺服器。請從正式商店安裝 Bitwarden desktop、mobile 或 browser 客戶端,不需要使用特殊的 Vaultwarden 版本。

登入前,請點擊登入畫面上的設定圖示(標示為 Self-hostedRegion → Self-hosted),將 Server URL 設定為 https://vault.example.com 並儲存。接著使用註冊時的 Email 與 master password 登入;客戶端應會立即連線,並提供自動填入與儲存憑證的功能。

若客戶端顯示 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 push 機制,說明如下。

Step 7 — 為 login endpoint 設定 Fail2ban jail

Vaultwarden 會將每次登入失敗的紀錄寫入 LOG_FILE 設定的檔案中,這正是防禦暴力破解所需的資訊。若尚未安裝 Fail2ban,請參閱 Fail2ban SSH hardening guide 了解安裝與基本操作;本節將為 Vaultwarden 新增一個 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 確認狀態。

以下三個 Docker 細節會影響防護是否生效。首先,若日誌顯示每次失敗嘗試的 IP 皆為 IP: 127.0.0.1 或代理伺服器位址,則 Vaultwarden 會誤封代理伺服器。請將 IP_HEADER 設定為代理伺服器實際傳送的 header(Traefik 請用 X-Forwarded-For、上述 nginx 設定請用 X-Real-IP、Cloudflare 後方請用 CF-Connecting-IP)。其次,正確的 iptables chain 取決於您的代理伺服器:若 Traefik 是以容器執行並對外發布埠號,流量會經過 Docker 的 FORWARD 路徑,因此 ban 規則必須設在如上所示的 DOCKER-USER;若您在 Step 1 選擇 host-nginx 方案,連線會在主機的 INPUT chain 終止,使用 DOCKER-USER ban 將無法偵測到連線,此時請刪除 chain = DOCKER-USER 行,讓 Fail2ban 使用預設的 INPUT chain。第三,請使用 banaction = iptables-allports 而非預設的埠號模式——此 jail 未定義埠號,在 DOCKER-USER 使用全埠號 (all-ports) ban 規則,能有效封鎖該違規者對主機上所有發布服務的存取。

Step 8 — 備份 vault,然後進行還原

vw-data 磁碟區即為您的密碼管理員。它包含 db.sqlite3 (所有項目)、attachments/sends/ 目錄、用於簽署登入階段的 rsa_key.* 檔案,以及來自管理介面的 config.json。若備份遺漏其中任何項目,在需要時將會失敗。

若在 Vaultwarden 寫入時複製 db.sqlite3,可能會擷取到寫入一半且毀損的檔案,因此請進行冷快照 (cold snapshot) —— 停機時間僅需幾秒:

#!/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 進行每晚備份至另一台伺服器或物件儲存空間,這能為您加密封存檔並對重複的快照進行去重 (deduplication)。管理介面中的 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 會在此透過 plain http 進行解密 —— 這是唯一允許的操作。使用主密碼登入並確認項目是否存在:若項目皆在,代表您的資料庫、RSA keys 與主密碼皆已成功完成往返測試,您可以在幾分鐘內於新的 VPS 上重建環境。按下 Ctrl-C 停止容器並刪除 /tmp/vw-restore

錯誤模式與顯示的字串

瀏覽器主控台顯示 Cannot read properties of undefined (reading 'importKey') 由於 Vault 是透過 http 載入,導致 crypto.subtle 為 undefined;請僅透過 https:// 進行存取,並在 proxy 設定 HTTP-to-HTTPS 重新導向。

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

/admin 拒絕正確的密碼。 Argon2 hash 遺失了轉義字元 — 每個 $ 在 Compose 中都必須是 $$ — 或者您輸入的是 hash 本身而非其代表的明文。

跨裝置同步緩慢;主控台顯示 WebSocket connection to 'wss://vault.example.com/notifications/hub' failed Proxy 未轉發 Upgrade/Connection 標頭;Traefik 會自動處理,但 nginx 需要 Step 1 中的兩行 upgrade 設定。Vault 功能仍正常,僅在開啟時才進行同步。自 v1.31.0 起,舊有的專用 port 3012 已移除,因此不需要獨立的 WebSocket 路由。

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

Upgrades

拉取新映像檔並重新建立容器;具名磁碟卷 (named volume) 與所有資料將會保留:

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 僅在安全上下文(secure context)中可用;因此若使用 plain 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 金庫?

先暫時停止 container,並將整個 vw-data volume 進行封存——包含 db.sqlite3attachments/sends/config.json 以及 rsa_key.* 檔案——接著將封存檔複製到伺服器之外,建議透過 nightly cron 執行。在伺服器運行時直接複製運作中的 SQLite 檔案會有快照損毀的風險,因此請在關閉狀態下進行備份。最重要的一點是,請務必先將備份還原至一個暫時的 container 並嘗試登入,以確保在正式依賴備份前,該備份是有效的。

自架密碼真的安全嗎?

是的,只要您落實本指南涵蓋的三項要點:使用真實的 HTTPS、關閉註冊功能並設定強大的 admin token,以及進行測試過的備份。您的金庫是使用主密碼在用戶端進行加密,因此伺服器永遠不會看到您的明文密碼——若沒有主密碼,被盜取的 db.sqlite3 也毫無用處。代價是您必須自行負責軟體更新與備份,這也是為什麼 Fail2ban 與還原測試在本文中是必要的步驟。

#vaultwarden#passwords#security#docker#self-hosting