飞牛 NAS 自建密码箱 Vaultwarden 实录:双重 NAT 也拦不住一张免费证书
上个周末我把家里那台 NAS 的容器列表数了一遍,23 个。
第 24 个是昨天半夜加进去的,它只干一件事:替我记密码。
先说个丢人的事
我今年 36,做机电这行 14 年,能背下来一整套消防设施的联动逻辑。
但上个月有一天,我连续点了三次「忘记密码」。
一个是网盘,一个是老家的宽带账号,还有一个是某个连我自己都想不起来为啥要注册的论坛。最后那个我干脆放弃了——反正注册完也没用过。
浏览器自带的「记住密码」我用过,确实省事。问题是换台设备就没了,而且那东西本质上还是躺在浏览器里,重装系统、换电脑、手机丢了,都得重来一遍。
后来我想明白了:这事不该靠脑子,该靠一台机器。
为什么不用现成的商业密码管理器
倒不是说它们不好。1Password、Bitwarden 官方、各家浏览器自带的,都挺成熟。
但我就是不太想把所有账号的钥匙交给一个我控制不了的服务器。这不是阴谋论,是很简单的账:
- 密码箱这东西一旦被攻破,损失不是「一个账号」,是全部账号
- 我没有能力去审计那家公司到底怎么存、怎么加密、会不会哪天被收购
- 而这事,恰恰是我自己家里那台 24 小时开着的 NAS 最适合干的活
所以我决定自建。花一个晚上。
选型:为什么是 Vaultwarden
自建密码管理器有几个主流选择,我简单说下我怎么筛的:
| 方案 | 为什么没选 / 选了 |
|---|---|
| Bitwarden 官方自建版 | 太重,要拖一堆容器 + SQL Server,我这台小机器伺候不起 |
| KeePass / KeePassXC | 最安全、也最"本地",但就是个加密文件,跨设备同步要自己折腾 WebDAV,手机端体验一般 |
| Passbolt / Psono | 偏团队协作,个人用太绕 |
| Vaultwarden ✅ | Bitwarden 服务端的 Rust 轻量重写版,一条 docker 起来,内存占用一百多兆 |
Vaultwarden 最香的一点是:它兼容 Bitwarden 的官方客户端。
也就是说,服务器是我自己的,但手机 App、电脑浏览器扩展,我用的还是 Bitwarden 那些成熟客户端——iOS、安卓、Windows、Mac、Chrome、Edge,全都有。
存进去的密码、验证码(TOTP)、附件,电脑上改一下,手机上几秒钟就同步过去了。
真正的难点不是装它,是那张证书
Vaultwarden 本体五分钟就能跑起来。我真正折腾了一晚上的,是证书。
原因是两个坎叠在一起:
第一个坎:Bitwarden 客户端要求 HTTPS。不是"建议",是硬要求。
网页版的密码箱走的是一套浏览器加密接口(WebCrypto),而浏览器只在 https 或者 localhost 下才开放这套接口。你用 http://192.168.1.100:8080 打开,页面能显示,但一登录就崩。
手机 App 松一点,会弹「不安全连接」的警告让你硬上,但我不想天天看那个警告。
第二个坎:我家是双重 NAT。
公网 IP 在光猫上,拨号也在光猫上,我自己的路由器 WAN 口拿的是光猫内网地址。这意味着——路由器上做端口映射,对外根本无效。
而申请免费证书的标准姿势(HTTP 验证)需要一个能被公网访问的地址。这路直接堵死。
解法:换成 DNS 验证
证书颁发机构(Let's Encrypt)验证域名归属有两种方式:
- HTTP 验证:我给你一个文件,你放到
http://你的域名/.well-known/...,我访问一下能看到就算你的 → 需要公网可达 ❌ - DNS 验证:我给你一条 TXT 记录,你加到你域名的解析里,我查一下能查到就算你的 → 只用到 DNS,不需要公网 IP,不需要开任何端口 ✅
DNS 验证这条路,完美绕开双重 NAT。
而且我的域名解析托管在腾讯云 DNSPod,它有 API,所以这件事可以全自动,连 TXT 记录都不用我手动加。
开工(可收藏的命令清单)
下面这一段是纯操作,想自己抄的可以直接复制。
完整版(含架构图、验收清单、9 条踩坑)我整理成文档放在 NAS 上了,需要的话评论区说一声。
① 起 Vaultwarden
# 国内拉镜像记得换源,不然卡死
sudo docker pull docker.m.daocloud.io/vaultwarden/server:latest
sudo docker tag docker.m.daocloud.io/vaultwarden/server:latest vaultwarden/server:latest
mkdir -p /vol1/1000/docker/vaultwarden
sudo docker run -d --name vaultwarden --restart unless-stopped \
-p 8080:80 \
-v /vol1/1000/docker/vaultwarden:/data \
-e SIGNUPS_ALLOWED=true \
-e WEBSOCKET_ENABLED=true \
-e DOMAIN="https://pass.你的域名.com:8443" \
-e ADMIN_TOKEN="你自己生成的随机串" \
-e TZ=Asia/Shanghai \
vaultwarden/server:latest② 加一条内网解析
# 用 DNSPod API 加一条 A 记录
# pass.你的域名.com -> 192.168.1.100(你家 NAS 的内网 IP)
# 这一步用腾讯云 API 密钥,脚本是 TC3-HMAC-SHA256 签名,二十来行 Python③ 用 acme.sh 签证书(DNS 验证)
sudo docker run --rm \
-v /vol1/1000/docker/acme:/acme.sh \
--env-file /vol1/1000/docker/acme/account.env \
neilpang/acme.sh \
--issue --server letsencrypt --dns dns_tencent \
-d pass.你的域名.com --keylength 2048 -m 你的邮箱④ nginx 做 HTTPS 前端
server {
listen 8443 ssl;
server_name pass.你的域名.com;
ssl_certificate /certs/pass.你的域名.com/fullchain.cer;
ssl_certificate_key /certs/pass.你的域名.com/pass.你的域名.com.key;
client_max_body_size 128M;
location / {
proxy_pass http://172.17.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
# 实时同步的 WebSocket,不加会同步延迟
location /notifications/hub {
proxy_pass http://172.17.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}⑤ 自动续期(不然 90 天后过期)
用一个 systemd timer,每周跑一次 acme.sh --cron,续完顺手让 nginx 重新加载证书。
踩的坑(一共 9 个,挑几个说)
| 坑 | 现象 | 怎么解 |
|---|---|---|
| 镜像拉不动 | docker pull 卡在那不动 | 换国内源,最后是 docker.m.daocloud.io 拉通的 |
| http 下页面用不了 | 页面能开,一登录就崩 | 必须上 HTTPS,浏览器的加密接口限制 |
| 双重 NAT | HTTP 验证签证书必失败 | 改 DNS 验证,完全不需要公网 |
| fake-ip 绕路 ⚠️ | 域名解析正常,就是连不上 | 旁路由 Clash 把它当国外流量了,得加直连规则 |
sudo -S 吃 stdin | 写 systemd 配置文件,结果是 0 字节 | 别用管道喂 sudo,先写 /tmp 再 sudo cp |
| 容器重建丢数据 | 账号全没了 | 必须挂载 -v ...:/data 持久化 |
第四个坑最阴——我一开始以为是证书问题,查了半天。
后来才想明白:我家里所有设备都走旁路由的 Clash,而那玩意儿是 fake-ip 模式。我把域名解析指向了一个内网 IP,但 Clash 只看域名规则,判定「这是个外网域名」→ 直接扔给日本节点 → 日本节点当然连不上我家的 192.168.1.100。
解决办法是在 OpenClash 的规则头部插两条直连:
- DOMAIN-SUFFIX,你的域名.com,DIRECT
- DOMAIN,pass.你的域名.com,DIRECT⚠️ 顺带提醒自己也提醒你:机场订阅一更新,这两条就被冲掉了,得重新加。
用起来什么样
现在是这样:
- 电脑浏览器装 Bitwarden 扩展,设置里填自建服务器地址
- 手机装 Bitwarden,右上角齿轮 → 「自托管环境」→ 填地址
- 网页版当管理后台用
电脑上改一条,手机上几秒就同步。验证码也一起进去了,不用再单独装个 TOTP App。
它有个明确的局限,我提前说清楚:
我故意把域名的解析指向内网 IP,所以外网打不开。
这是权衡后的选择——公网暴露一个密码箱,风险大过我需要的便利。而且手机 App 有本地加密缓存,出门在外照样能翻密码,只是不能新增、不能同步。
真要外网也能用,也有办法(端口映射 / 内网穿透 / Cloudflare Tunnel),但那是另一个话题了,等我哪天想动了再说。
最后一个提醒
主密码一定要记牢。
这东西的加密是端到端的——服务器管理员看不到你的密码,也救不了你的密码。
我自己是在保险柜里放了一张纸条。
那晚折腾到十一点半,我把最后一个账号导进箱子,关电脑之前,顺手把冰箱上贴着的那张写满账号密码的便签撕了。
字写得挺潦草的,撕的时候还挺舍不得。
本文同步发布于博客「壹根辣条」,技术细节(含完整部署文档、验收清单、9 条踩坑)整理在飞牛 NAS 上,需要可以留言。