vaultwarden「Invalid claim」排障实录:latest 标签四年没更新
现象
自建 vaultwarden(Bitwarden 兼容服务端)突然无法使用:客户端登录请求 POST /identity/connect/token 返回 200(token 正常签发),紧接着的 GET /api/sync 却返回 401 Unauthorized,服务端日志:
[auth][ERROR] Unauthorized Error: Invalid claim
token 能签发、验签却失败,问题出在 JWT 的 claim 校验环节。逐项排除:
排除法四步
| 怀疑点 | 排查方式 | 结论 |
|---|---|---|
| DOMAIN 配置错位(iss 不匹配) | 确认 DOMAIN=https://vw.example.com 为静态 env,签发与校验同源 | 排除 |
| 反代丢头 | 核对 nginx 透传 Host / X-Forwarded-Proto / X-Forwarded-Host / X-Forwarded-Port | 排除 |
| 时钟漂移(exp/nbf 越界) | 容器内外时间一致,且与 Cloudflare/Google 的 HTTP Date 头吻合,NTP 服务 active | 排除 |
| 签名密钥不一致 | /data/rsa_key.pem 单一完整 | 排除 |
一条红鲱鱼
排查中发现日志时间戳从 CST(01:22)变成了 UTC(17:40),一度把「容器时区翻转」当作线索深挖。事后看这是红鲱鱼:JWT 的 exp/nbf 基于 Unix epoch,与时区无关,TZ 变化根本不影响 claim 校验。排障时把假线索记录下来同样重要——不写下来,下次还会沿着它走一遍。
真因:latest 标签四年没更新
docker inspect vaultwarden/server:latest --format '{{.Created}}'
# created = 4 years ago → 实际版本 1.23.1
server:latest 这个 tag 上游早已停止随构建更新,本机镜像停留在四年前的 1.23.1。四个版本年代跨度的差距,使其签发的 token claim 契约与现代 Bitwarden 客户端的校验逻辑不再兼容——签发和验证都是它自己,但客户端是新的。处置:
docker compose pull && docker compose up -d # 1.23.1 → 1.37.2
Invalid claim 消失,功能恢复。教训:latest 不代表「最新」,只代表「构建时指向的版本」。要么锁定具体版本号升级,要么定期 compose pull;长期不动的 latest 是定时炸弹。
连带故障:容器改名打挂反代
重建时 compose 里的 container_name: vaultwarden 被移除,容器自动改名为 xxx-warden-1,而 nginx 配置是 proxy_pass http://vaultwarden;——依赖 Docker 内嵌 DNS 按容器名解析,名字变了直接解析失败。
根治:nginx upstream 动态重解析
这里还藏着一个更普遍的问题:nginx 启动时解析 upstream 主机名并永久缓存 IP。容器日后重建拿到新 IP,nginx 仍打向旧 IP → 502,必须手动 reload 才恢复。修复:
location / {
resolver 127.0.0.11 valid=30s ipv6=off; # Docker 内嵌 DNS
set $upstream_vw vaultwarden; # 仅主机名走变量
proxy_pass http://$upstream_vw:80; # 端口保留在指令里
}
两个细节:127.0.0.11 是 Docker 内嵌 DNS(取自容器内 resolv.conf),valid=30s 控制缓存周期;只有主机名进变量、端口留在指令里——把 host:port 整段塞进变量会引入解析歧义。
效果:容器重建换 IP 后 ≤30 秒自愈,不再依赖人工 reload;代价是 IP 变化后最长有 30 秒 502 窗口(缓存未过期期间),可按需调小 valid。
经验总结
- 排除法要有证据闭环:每个怀疑点用实测排除,不留「应该是」;
- 红鲱鱼要记录:假线索写进排障记录,防止重复深挖;
- latest 标签必须治理:锁版本或定期 pull,四年前构建的 latest 完全可能存在;
- 反代容器普遍存在 upstream IP 缓存问题:
resolver 127.0.0.11+ 变量主机名是 Docker 环境的标准解法; - container_name 是反代与容器间的隐式契约:删它之前先想清楚谁依赖这个名字。
参考文档
- dani-garcia. Vaultwarden Wiki[EB/OL]. https://github.com/dani-garcia/vaultwarden/wiki (访问日期: 2026-09-04).
- F5, Inc. nginx documentation: resolver[EB/OL]. https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver (访问日期: 2026-09-04).