将 Vaultwarden 服务迁移到新域名后,出现了两个问题:附件无法下载,以及容器每次重启都会把已登录的客户端全部登出。本文记录排查过程与最终配置。
问题现象
附件下载报错:Web 页面上所有密码、文本凭据读取正常,但点击下载附件时右上角弹出错误 发生错误。Failed to fetch。
容器重启后掉会话:修改配置并重启 Vaultwarden 容器后,客户端及网页端的所有登录会话全部失效,被强制登出。
根因分析
附件下载失败:虽然公网流量通过 Cloudflare Tunnel 映射到了新域名 https://bw.iiii.fun,且 docker-compose.yml 中也写了 DOMAIN,但由于此前通过 /admin 管理后台保存过配置,挂载目录中的 vw-data/config.json 固化了旧域名 "domain": "https://nbw.ksnb.fun"。
Vaultwarden 优先读取 config.json,在生成附件直链时返回了旧域名的绝对路径,导致浏览器在发起 fetch 请求时触发跨域安全限制(CORS)或旧域无法访问,从而报错 Failed to fetch。
重启后掉登录:修改 domain 配置后,Bitwarden 签发的 JWT Token 中的发行者字段(iss)与服务端当前生效的域名不再一致,Vaultwarden 校验失败并主动废除了所有旧域名的会话凭证。改域后重新登录一次,后续正常重启便不会再掉登录。
解决步骤
- 修改宿主机持久化目录下的
vw-data/config.json文件,将"domain"字段修改为实际访问的完整公网地址:"domain": "https://bw.iiii.fun"。 - 执行
docker compose restart vaultwarden重启服务。 - 客户端重新输入主密码完全登录一次。之后附件可正常下载,后续重启也不再发生掉会话。
配置项有多处来源时,config.json 的优先级高于环境变量,这一点是本次问题的关键。通过 /admin 后台保存过的配置都会写进这个文件。
docker-compose.yml 配置
针对 Cloudflare Tunnel 反代环境增加了 depends_on 依赖控制,避免 Vaultwarden 启动过快导致 MySQL 未就绪。所有数据库密码、SMTP 凭据及超管 Token 请替换为实际值:
version: '3'
services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: always depends_on: - mysqldb environment: # 管理后台超级 Token(建议使用 openssl rand -base64 48 生成强随机字符串) ADMIN_TOKEN: "YOUR_ADMIN_TOKEN_HERE" WEBSOCKET_ENABLED: "true" DATABASE_URL: "mysql://bitwarden:YOUR_MYSQL_PASSWORD@bitwarden-mysql/bitwarden" SIGNUPS_ALLOWED: "false"
# SMTP 邮件通知配置 SMTP_FROM: "noreply@example.com" SMTP_HOST: "smtp.example.com" SMTP_PORT: "465" SMTP_SECURITY: "force_tls" SMTP_USERNAME: "noreply@example.com" SMTP_PASSWORD: "YOUR_SMTP_PASSWORD"
# 完整外部访问公网域名 DOMAIN: "https://bw.iiii.fun" volumes: - ./vw-data:/data networks: - bitwarden - web ports: # 仅绑定本地回环端口供 Cloudflare Tunnel 或反代调用 - 127.0.0.1:19280:80
mysqldb: image: mysql:8.0.32 container_name: bitwarden-mysql restart: always environment: - MYSQL_ROOT_PASSWORD=YOUR_ROOT_PASSWORD - MYSQL_USER=bitwarden - MYSQL_PASSWORD=YOUR_MYSQL_PASSWORD - MYSQL_DATABASE=bitwarden volumes: - ./mysql:/var/lib/mysql networks: - bitwarden
networks: web: external: true bitwarden: external: false