Bitwarden 自建教程:密码库放自己 NAS 上

AI 摘要997笔记AI

在群晖 NAS 上部署 Vaultwarden 自建密码管理器,比云服务器或官方服务端更可控:数据存本地硬盘,FRP 按需穿透,云服务器只转发不存数据,容器仅绑回环并经 DSM nginx 反代。整套约 40MB 内存,一下午跑通;结论是个人与家庭自建更安全,服务关停也只是开关。

此内容由AI根据文章内容自动生成。

密码管理器是那种「平时完全想不起来,出事就是灭顶之灾」的东西。主流方案让你把全家的密码托付给云厂商的服务器——这没什么错,Bitwarden 云端免费版甚至足够好。但我最终还是选择在自己家里搭了一套,不是为了省钱,也不是为了折腾,而是因为密码库这种数据,容不得它存在于别人的服务器上。

我的做法是:把 Vaultwarden 部署在家里的群晖 NAS(Synology DS1621+)上,数据永远躺在自己的硬盘里;对外只留一个 FRPC 内网穿透,需要的时候用、不需要随手停掉。整套东西从零到能用,一个下午搭完,资源占用不到 40 MB 内存。这篇文章完整记录这个过程,包括踩过的坑——你照着做应该能一次跑通。

如果你也在折腾家里的服务,可以先看看这篇把 Nginx 访问日志做成可视化仪表盘,和本文用的是同一台 NAS。

一、先说结论:为什么不搭在云服务器上

网上搜「Bitwarden 自建」,出来的东西几乎清一色是「买台云服务器,装 Docker,公网 IP 直接访问」。方案本身能跑通,但有两个问题被系统性忽略了。

1. 你会不续费,然后数据就没了

云服务器有生命周期。轻量应用服务器有到期时间,活动机型有续费价,促销机型第二年可能直接翻倍。Bitwarden 的密码库不是「随便重新注册一下就行」的东西——里面是你所有账号的唯一凭据,服务停一天你就一天登不进去。更现实的情况是:某天服务商跑路、某个端口被封、或者你就是不想再续费了,此时数据在人家硬盘上,取回来的难度取决于人家心情。

NAS 不一样。它本来就在你家,因为你买它是为了存照片、存文件。它不会因为「续费到期」而停止工作——只要你还用电,它就一直在。你的数据生命周期和你的数据重要性是绑定的,不和某个第三方的商业决策绑定。

2. 密码库值得放在你信任的物理位置

Bitwarden 官方服务器流量是加密的(客户端到服务端全程 TLS,服务端到服务端也加密),这点没问题。但服务端仍然是第三方在运维,它能看到你的元数据:什么时候登录、从哪个 IP 登录、有几个条目、条目标题在客户端加密前可能经过缓存。这不是「Bitwarden 不可信」,而是你把它放到了一个你无法审计的位置。

我的取舍:数据存自己硬盘 + 内网直连日常用 + 需要在外网用时才开FRP。这三件事组合起来,让「服务端关停」从一个故障降级成了「一个可以随时按下的开关」。

二、选型:Vaultwarden,不是官方 bitwarden/server

Bitwarden 有两条自建路线,差别大到不需要纠结。两条路线用的都是同一个官方客户端——浏览器扩展、桌面端、手机 App、CLI 全都通用,区别只在服务端。

对比项 官方 bitwarden/server Vaultwarden(本文采用)
技术栈 C# / .NET Rust 单二进制
容器数量 约 12 个 + 独立数据库 1 个
最低内存 4 GB(推荐 8 GB) 128 MB(实测空闲约 40 MB)
磁盘要求 30 GB+ 5~ 10 GB
数据库 MSSQL / PostgreSQL SQLite(可换 PG)
高级功能 需付费 license 全部免费
TOTP / Send / 组织 付费版才有 免费解锁
厂商支持 / 审计 有,含 SOC 2 社区维护,无正式审计
缺失的企业功能 — SSO(SAML/OIDC)、SCIM、Duo、部分管理策略
冷启动时间 约 38 秒 约 1.2 秒

结论很清楚:个人 / 家庭 / 小团队自建,Vaultwarden 是唯一理性选择。官方那套的 footprint(占地)只有在「企业要 SSO、要审计报告、要厂商担责」时才值得。资源占用差 30 倍,而两者用的是同一套官方客户端。

Vaultwarden 用 Rust 重实现了 Bitwarden 的服务端 API,所以官方客户端完全兼容。迁移到官方云端或反过来,只需改登录页的服务器地址,密码条目不需要重新分发。

三、最终架构:NAS + DSM 反代 + FRP 按需开

先看最终长什么样。这是我实际跑通的链路:

手机 / 电脑(官方 Bitwarden 客户端)
        │  https://ssl.997wan.com
        ▼
FRPC 内网穿透(按需启动,不需要就停)
        ▼
云服务器 frps(只做转发,不存任何数据)
        ▼
爱快 iKuai 端口转发 443 → 192.168.11.224
        ▼
NAS:DSM 自带 nginx (443)
        │  http://127.0.0.1:8080
        ▼
vaultwarden 容器 (80)
        ▼
SQLite  /volume1/vaultwarden/vw-data/

关键设计有三条:

1. 云服务器只做转发,一个字节的数据都不经过

FRP 的定位是「打通内外网的网络通路」,不是「把服务搬到云上」。密码库在跑完整条链路时,数据是从你的 NAS 直连出去的,云服务器只是让客户端能连到 NAS 的443 端口。这意味着云服务器被停止服务、欠费、跑路,都只影响你「在外网能不能访问」,不影响数据本身。数据在 NAS 上,NAS 一直在。

2. 容器只绑回环地址,公网流量一律经 nginx

compose 里端口只写 127.0.0.1:8080:80,不写 8080:80。这样即使内网有人扫到 NAS 的 8080,也无法绕过反代直接访问 API。所有对外流量必须经过 nginx(也就是能强制 HTTPS、能加安全头的那一层)。

3.放弃 Caddy,改用 DSM 自带 nginx

我最初的设计是像大多数人一样:Caddy + Let’s Encrypt 自动签证书。探测 NAS 之后发现一个硬约束——NAS 的 80 和 443 已经被 DSM 自带的 nginx 占满了(进程 /usr/bin/nginx -c /etc/nginx/nginx.conf.run)。Caddy 根本抢不到这两个端口。

所以改为复用 DSM 的反向代理能力:少一个容器,TLS 也直接走 DSM 现有的证书体系。这是「先探测再设计」省下来的事——如果不先看 NAS 上有什么就动手,多半要返工。

四、部署过程实录

项目 实测值
NAS 型号 Synology DS1621+
DSM 版本 7.4.1
实际 LAN IP 192.168.11.224(不是 .220,那是另一个共享地址)
内存 / 磁盘 62 GiB 总量,可用 59 GiB;volume1 剩余 22 TB
容器运行时 Container Manager,Docker 24.0.2,Compose v2.20.1
Vaultwarden 版本 1.37.4
资源占用 空闲约 40 MB 内存
冷启动 约 1.2 秒

1. 建目录并写好 docker-compose.yml

先建工作目录,再放 compose 文件。整个项目放在 /volume1/vaultwarden:

mkdir -p /volume1/vaultwarden
cd /volume1/vaultwarden

然后是核心的 docker-compose.yml。我把它完整贴出来,每一行后面都解释了为什么这么写:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    env_file: .env
    environment:
      DOMAIN: ${DOMAIN}
      ADMIN_TOKEN: ${ADMIN_TOKEN}
      SIGNUPS_ALLOWED: ${SIGNUPS_ALLOWED}
      INVITATIONS_ALLOWED: "false"
      ORG_CREATION_ALLOWED: "false"
      WEBSOCKET_ENABLED: "true"
      LOG_LEVEL: info
      # 必须保持 false:true 表示数据只在内存,容器一重启全部密码消失
      I_REALLY_WANT_VOLATILE_STORAGE: "false"
      SEND_ENABLED: "true"
      MAX_ATTACHMENT_SIZE: 10
      # 前面有 nginx 时必填,否则审计日志里全是反代 IP
      TRUSTED_PROXIES: 127.0.0.1/32,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
    volumes:
      - ./vw-data:/data
    # 只绑回环。NAS 的 80/443 已被 DSM 自带 nginx 占用,
    # 绝不能在这里抢,公网流量统一走 nginx 反代。
    ports:
      - "127.0.0.1:8080:80"
    healthcheck:
      test: ["CMD", "/healthcheck.sh"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

三个必须解释的参数:

  • I_REALLY_WANT_VOLATILE_STORAGE 必须保持 false。这个变量名字起得很直白——设为 true 意味着数据只在内存里,容器一重启所有密码都没了。日志里那句警告就是它。
  • WEBSOCKET_ENABLED 必须 true。实时同步(多端改密码立刻互相同步)走的是 WebSocket,关掉的话能用但各端数据会不一致。
  • TRUSTED_PROXIES 前面有 nginx 反代时必填。否则审计日志里记录的全是你自己 nginx 的 IP,而不是真实客户端 IP,日志价值归零。

2. 生成 .env(ADMIN_TOKEN 用随机数)

DSM 上可以用一条命令生成随机 ADMIN_TOKEN 并写入 .env:

ADMIN_TOKEN=$(head -c 48 /dev/urandom | base64 | tr -d '\n=/+' | head -c 48)
cat > /volume1/vaultwarden/.env <<EOF
DOMAIN=https://ssl.997wan.com
ADMIN_TOKEN=$ADMIN_TOKEN
SIGNUPS_ALLOWED=true
INVITATIONS_ALLOWED=false
ORG_CREATION_ALLOWED=false
EOF
chmod 600 /volume1/vaultwarden/.env

SIGNUPS_ALLOWED=true 是为了让你能创建第一个账号。建完账号第一件事就是改成 false,否则任何人访问你的域名都能注册一个新账号进你的库。这一步别忘。

3. 拉镜像并启动

DK=/var/packages/ContainerManager/target/usr/bin/docker
cd /volume1/vaultwarden
$DK compose pull
$DK compose up -d
DSM 上 docker 必须用绝对路径:/var/packages/ContainerManager/target/usr/bin/docker。原因是 sudo 会重置 PATH,直接敲 docker 会得到 command not found。这个坑我在部署过程中撞了两次。

启动后验证:

$DK compose ps
$DK exec vaultwarden /healthcheck.sh
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/alive

/alive 返回 200 且容器状态是 healthy,就算部署成功。真实启动日志长这样:

|                       Starting Vaultwarden                        |
|                           Version 1.37.4                           |
[NOTICE] You are using a plain text `ADMIN_TOKEN` which is insecure.
Please generate a secure Argon2 PHC string by using `vaultwarden hash`.
[2026-10-07 11:08:05.556][vaultwarden::auth][INFO] Private key 'data/rsa_key.pem' created correctly
[2026-10-07 11:08:06.353][start][INFO] Rocket has launched from http://0.0.0.0:80

看到 Rocket has launched 和 rsa_key.pem 生成成功,就成了。

4. DSM 反向代理(这一步最容易漏)

进「控制面板 → 登录门户 → 高级 → 反向代理」,新建规则:

项目 值
名称 vaultwarden
来源 HTTPS / ssl.997wan.com / 443
目标 HTTP / 127.0.0.1 / 8080
自定义标头 X-Forwarded-Proto: https
X-Forwarded-For: $remote_addr
X-Forwarded-Host: $host
WebSocket 必须勾选
WebSocket 不勾 = 多端不同步。这是最容易漏的一项。实时同步走的是 /notifications/hub 这个 WebSocket 端点,漏了它表现为「能登录、能存密码,但手机和电脑上的数据不一致」,很容易被误判成数据丢了。

五、踩坑记录:DSM 上特有的 11 个问题

Synology DSM 不是标准 Linux,官方 docker-compose 教程在这里基本全线失效。以下每一条都是我实际卡住过的。

# 现象 原因与解法
1 SSH 账号密码怎么都登录不上 可登录的账号是 wen(不是 admin),属 administrators 组;密码认证失败时改走 DSM 任务计划(root 执行脚本)
2 sudo: docker: command not found sudo 重置 PATH。必须 sudo sh -c '...' 或用绝对路径
3 root 也删不掉文件 volume1 挂载带 synoacl。覆盖写要用 install -m 755 /tmp/x /target/x;SMB 映射盘(Z:)同样不能删/覆盖已有文件
4 没有 crontab 命令 DSM 没有 crontab 客户端。定时任务直接写 /etc/crontab(DSM 7.4 用 synoschedtask,但加 root 行同样生效)
5 /var/log/ 写不进去 没有写权限,自建日志目录
6 SFTP 连不上 SFTP subsystem 未启用。改用 hex 管道:printf '%s' <hex> | xxd -r -p > /tmp/f
7 连续密码失败后 SSH 突然全断 触发 NAS 自动封锁:22 端口开着但握手秒断、5001 也返回 HTTP 000。只能等自动解封,切勿暴力重试
8 compose 报变量解析失败 compose 要求 .env 存在才能解析变量 → 必须先写 .env 再 pull
9 Bind mount failed: '/volume1/vaultwarden/vw-data' does not exist 数据目录不存在。先 mkdir 再 up
10 备份脚本找不到 sqlite3 DSM 默认没装 sqlite3 → 备份脚本要走停容器冷备份分支
11 打包备份失败 密钥文件名是 rsa_key.pem(不是目录 rsa_keys/),按官方教程写路径会漏掉密钥
第 7 条特别值得注意:如果你连着试了几次密码就开始怀疑账号被锁,其实是 NAS 的防暴力破解机制在起作用。表现是「22 端口明明显示开放,但连上去立刻断开」,很容易被误判成服务挂了。等它自己解封就行。

六、备份:这是整套方案的命门

自建密码库的所有风险,最后都会收敛到一件事:能不能恢复。服务器格式化、勒索加密、手贱删错目录——任一发生,没备份就永久丢失,而且因为密码库本身是加密的,丢了就是彻底丢了。

备份必须包含密钥,这是关键

Vaultwarden 的数据目录里有两样东西:

  • db.sqlite3 —— 密码库数据库
  • rsa_key.pem —— 服务器 RSA 密钥
rsa_key.pem 丢了,库里所有已加密数据永远解不开——包括别人已经保存进去的条目。这个文件必须和数据库一起备份。只备数据库等于没备。

我的备份脚本

DSM 没有 sqlite3,所以走「停容器冷备份」分支。大约 15 秒不可用,但绝对不会产生半写状态——对一个密码库来说,保守比可用重要:

#!/bin/bash
# cron: 17 4 * * *(每天 04:17,避开整点高峰)
set -euo pipefail

BASE_DIR="/volume1/vaultwarden"
DATA_DIR="$BASE_DIR/vw-data"
BACKUP_DIR="$BASE_DIR/backups"
KEEP_DAYS=30

mkdir -p "$BACKUP_DIR"
[ -d "$DATA_DIR" ] || { echo "[ERR] 数据目录不存在: $DATA_DIR"; exit 1; }

STAMP="$(date +%F)"

# ---------- SQLite 在线热备份(有 sqlite3 时走这里)----------
if command -v sqlite3 >/dev/null 2>&1; then
    sqlite3 "$DATA_DIR/db.sqlite3" ".backup '$BACKUP_DIR/db-$STAMP.sqlite3'"
    echo "[ok] 数据库热备份完成"
else
    # DSM 默认没装 sqlite3,退化为停容器冷备份
    echo "[warn] 无 sqlite3,改用停容器冷备份"
    DOCKER=/var/packages/ContainerManager/target/usr/bin/docker
    cd "$BASE_DIR"
    $DOCKER compose stop vaultwarden
    sleep 3
    cp "$DATA_DIR/db.sqlite3" "$BACKUP_DIR/db-$STAMP.sqlite3"
    $DOCKER compose start vaultwarden
    sleep 10
fi

# ---------- 打包:数据库 + RSA 密钥(缺密钥等于没备)----------
TARGET="$BACKUP_DIR/vw-backup-$STAMP.tar.gz"
tar czf "$TARGET" -C "$DATA_DIR" db.sqlite3 rsa_key.pem rsa_key.pub

# ---------- 清理过期备份 ----------
find "$BACKUP_DIR" -name 'vw-backup-*.tar.gz' -mtime +$KEEP_DAYS -delete

echo "[ok] $STAMP 备份完成: $TARGET"

挂进 DSM 的 /etc/crontab:

17 4 * * * root /volume1/vaultwarden/backup.sh >> /volume1/vaultwarden/logs/backup.log 2>&1

用 04:17 而不是 04:00 是有意的——整点是各类定时任务高峰,错开几分钟避免 IO 打架。保留 30 天。

再补一层异地

同一块盘上的备份不算备份。DSM 自带的 Hyper Backup 可以把 /volume1/vaultwarden 整个目录纳入,另一个存储池或另一台机器都行。这是最后一道保险。

七、外网访问:FRP 按需开,不需要就停

回到开头那个问题。云服务器方案的隐含前提是「服务必须一直在线」,而我的做法把这件事变成可选项。

FRP 的拓扑很简单:

# NAS 上跑 frpc(客户端)
[common]
server_addr = 你的云服务器IP
server_port = 7000
token = 一段双方约定的字符串

[ssl-vaultwarden]
type = tcp
local_ip = 127.0.0.1
local_port = 443
remote_port = 443

云服务器上跑 frps,NAS 上跑 frpc,一条隧道把家里 NAS 的 443 端口接到云服务器的公网 IP 上。所有访问流量都是这个转发链路。

「随时可以停掉」意味着什么

场景 操作 结果
平时在家里 / 公司内网 什么都不用做 客户端直连 https://ssl.997wan.com,走本地解析,不经过云服务器,速度快
出门要用 NAS 上启动 frpc 外网可访问
不想暴露了 / 云服务器到期 systemctl stop frpc 或直接关云服务器 外网立刻不可达,数据仍在 NAS 上,一个字节都没动
哪天想续费了 重启云服务器 + frpc 重新可用

这就是把服务端放在自己家里的核心价值:「服务是否在线」从一个持续的成本和风险,变成了一次可以随时做的开关操作。云服务器到期那天你不需要做任何抢救动作,因为数据从来就没在那里。

如果你连公网 IP 都不想暴露,还有两条路:Tailscale(组网方案,走 WireGuard 打洞,不经过任何第三方中转)或者 Cloudflare Tunnel(走 Cloudflare 边缘)。但两种都比 FRPC 麻烦一些——FRPC 的优势是标准化配置、不依赖第三方服务、随时可停。

八、日常运维命令速查

DK=/var/packages/ContainerManager/target/usr/bin/docker

$DK ps --filter name=vaultwarden                    # 状态
$DK logs --tail 100 vaultwarden                     # 日志

cd /volume1/vaultwarden && $DK compose restart     # 重启
cd /volume1/vaultwarden && $DK compose up -d       # 启动
cd /volume1/vaultwarden && $DK compose pull && $DK compose up -d   # 升级

bash /volume1/vaultwarden/backup.sh                # 手动备份
tail -20 /volume1/vaultwarden/logs/backup.log      # 备份日志
ls -la /volume1/vaultwarden/backups/               # 备份产物

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/alive   # 健康检查

升级流程是固定的:先 pull 拉新镜像,再 up -d 重建。Vaultwarden 的数据库结构在升级时自动迁移,不需要手动处理。升级前确保当天备份已经生成——这是唯一需要记住的纪律。

九、安全清单

部署完逐条核对:

  • 容器只绑 127.0.0.1(不绑 0.0.0.0)
  • .env 权限 600
  • ADMIN_TOKEN 用48 位随机字符生成,不是手写
  • I_REALLY_WANT_VOLATILE_STORAGE=false(数据落盘而非内存)
  • SIGNUPS_ALLOWED=false(建完账号立刻改)
  • DSM 反向代理勾选 WebSocket
  • 备份包含 rsa_key.pem
  • Hyper Backup 纳入 vw-data(异地备份)
  • 保管库开启两步验证

一个可以后面再做的改进

启动日志会警告 You are using a plain text ADMIN_TOKEN which is insecure.。改成 Argon2 哈希即可:

cd /volume1/vaultwarden
/var/packages/ContainerManager/target/usr/bin/docker exec vaultwarden \
  vaultwarden hash --plaintext -
# 把输出的整行 $argon2id$... 替换 .env 里的 ADMIN_TOKEN,再重启容器

ADMIN_TOKEN 只管 /admin 后台,而 .env 权限已经锁 600,风险有限。可以先把上面 9 条做完再说这个。

十、故障排查

现象 原因 处理
客户端提示「服务器不安全」 没有 HTTPS 走完 DNS + 反代;客户端拒绝明文 HTTP
能登录但多端不同步 反代没开 WebSocket DSM 反代勾选 WebSocket 支持
打开白屏 用 IP + http 访问 必须用 https://ssl.997wan.com。web 保管库要求安全上下文,用裸 IP + http 会直接拒绝
容器起不来,Bind mount failed vw-data 不存在 mkdir -p /volume1/vaultwarden/vw-data
sudo: xxx: command not found sudo 重置 PATH 用绝对路径或 sudo sh -c '...'
收不到验证邮件 没配 SMTP .env 加 SMTP_HOST / SMTP_PORT / SMTP_FROM / SMTP_USERNAME / SMTP_PASSWORD
忘记主密码 — 无法找回。这就是必须开两步验证 + 离线导出加密备份的原因

十一、值得不值得

最后说点实在的。把成本摊开看:

Vaultwarden 自建 Bitwarden 云端免费版 1Password
成本 免费(已有 NAS) 免费 ~$36/年
数据位置 自己的 NAS Bitwarden 云 1Password 云
TOTP / Send / 组织 全部免费 付费版才有 付费
维护成本 自己(每天 1 次 cron) 免维护 免维护
厂商审计 无 有第三方审计 有
服务可控性 随时可停可重建 依赖厂商 依赖厂商
客户端 官方 Bitwarden App 官方 App 1Password App

说实话:Bitwarden 云端免费版对多数人已经够用,有审计、有厂商维护、不用操心备份和升级。自建换来的是数据主权 + 免费解锁 TOTP/Send/组织功能 + 服务可停可重建,代价是你得自己保证备份和升级。这个交换是否划算,取决于你有多在意「密码存在别人的服务器上」。

如果你决定自建,命门只有一个:必须有可验证的的异地备份。服务器格式化、勒索加密、手贱删错目录——任一发生,没备份就永久丢失。建议每季度做一次恢复演练:把备份解压到临时目录,确认 db.sqlite3 能打开、rsa_key.pem 在、条目数量对得上。没验证过的备份不算备份。

适合谁

推荐:已经有 NAS 或愿意买一台、有基本命令行能力、能接受自己负责备份的人。

不推荐:没有 NAS 只能租云服务器、不想折腾任何运维、或者单纯因为「听说自建更安全」就临时起意——那你用云端免费版,它真的够用。

如果你手上的服务比较多、都在同一台机器上,别忘了这几篇可能对你有用:DoH / DoT 延迟实测对比、Nginx 访问日志可视化仪表盘、WordPress 大图与灯箱放大。

上一篇 不埋点也能做网站访问统计看板:解析 nginx 日志全流程
下一篇 常用的运维工具:SSH 远程连接工具怎么选?12 款横评与搭配方案