编程

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 是反代与容器间的隐式契约:删它之前先想清楚谁依赖这个名字。

参考文档

  1. dani-garcia. Vaultwarden Wiki[EB/OL]. https://github.com/dani-garcia/vaultwarden/wiki (访问日期: 2026-09-04).
  2. F5, Inc. nginx documentation: resolver[EB/OL]. https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver (访问日期: 2026-09-04).

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注