1. 为什么现在还得自己搭一个密码管理器?不是有现成的吗?
Bitwarden 这个名字,最近两年在技术圈、安全圈甚至普通用户群里出现的频率越来越高。它不像某些商业密码管理器那样靠广告或订阅制讲故事,而是从第一天起就坚持开源、端到端加密、本地可托管——这三个词,每一个都直击当前在线服务的软肋:数据归属模糊、加密逻辑不透明、服务器完全不可控。我最早接触 Bitwarden 是在某次内部安全审计中,团队发现三款主流商用密码管理器的客户端在特定场景下会将部分元数据(比如文件夹结构、条目类型、最后修改时间)明文上传至云端,虽然密码本身是加密的,但这些“边角信息”拼起来,足以还原出用户的使用习惯和敏感资产分布。那一刻我就意识到:真正能托付密码的,不是哪家公司的云服务,而是你自己能看见、能验证、能随时拔掉电源的那台服务器。
用 Docker 搭建自己的 Bitwarden 实例,本质上不是为了“炫技”,而是把控制权拿回来。它解决的不是“有没有密码管理器”的问题,而是“我的主密钥是否永远只在我手里”“我的密码库是否可能被意外同步到第三方日志系统”“当某天服务商突然调整隐私政策,我能否在20分钟内完成数据迁移和停服”这三个更底层的问题。尤其对中小团队、自由职业者、开发者个人项目来说,一台轻量云服务器(甚至树莓派4B+32GB SD卡)就能跑起来,资源占用极低(实测稳定运行时内存占用<150MB,CPU空闲率常年98%以上),却能换来完整的审计能力、自定义备份策略、与现有内网服务(如LDAP、反向代理、监控告警)无缝集成的能力。
你可能会问:官方不是提供了免费云服务?没错,但它的免费版限制了附件上传、TFA登录方式、组织功能,更重要的是——你无法审计它的后端日志、无法确认其数据库是否启用了透明数据加密(TDE)、无法验证其密钥派生函数(PBKDF2)的迭代次数是否随硬件演进而动态提升。而自己搭建,所有这些参数你都能一行命令改完,改完立刻生效,不需要等客服回复、不需要看更新日志、不需要祈祷下一版本会不会悄悄加个“分析用户行为”的开关。这不是偏执,是数字时代最基础的数据主权意识。
2. 整体架构设计与方案选型逻辑
2.1 为什么必须用 Docker?不用源码编译或一键脚本?
很多人看到“搭建密码管理器”第一反应是去 GitHub 找一键安装脚本,或者直接 clone 官方仓库make build。这两种方式我都试过,结果很明确:源码编译适合想研究加密流程的极客,一键脚本适合临时测试,而生产环境唯一可靠的选择,只有 Docker。原因有三层:
第一层是环境隔离性。Bitwarden 的核心服务(API、Web、Icons、Events)由多个独立进程组成,它们依赖不同版本的 .NET Runtime、Node.js、PostgreSQL 驱动,还涉及 Redis 缓存、SMTP 发信、Let’s Encrypt 自动续签等外部组件。手动部署时,哪怕只是升级一次系统内核,都可能触发 glibc 兼容性问题,导致某个服务静默崩溃(我曾遇到过 PostgreSQL 客户端驱动在 Ubuntu 22.04 升级后拒绝连接,错误日志里只有一行pq: server closed the connection unexpectedly,查了三天才发现是 libpq 版本错配)。Docker 把整个运行时环境打包进镜像,操作系统升级、内核更新、系统库变更,统统不影响容器内服务——这是生产环境稳定性的底线。
第二层是配置可复现性。我自己维护着3套 Bitwarden 环境:开发测试用的树莓派、团队协作用的轻量云服务器、客户交付用的私有化部署包。如果用脚本部署,每次都要手动核对/etc/bitwarden/config.yml里的domain、admin_token、smtp_host是否写错;而用 Docker Compose,所有配置都在docker-compose.yml里,配合.env文件做环境变量分离,git clone → cp .env.example .env → docker-compose up -d三步完成,且每次docker-compose config都能输出最终解析后的完整配置,方便审计和比对。去年帮某高校实验室部署时,他们要求所有配置必须通过 ISO 27001 合规检查,Docker Compose 文件直接作为配置基线提交,比写十页 Word 部署文档更有说服力。
第三层是升级与回滚成本。Bitwarden 官方镜像遵循语义化版本(如vaultwarden/server:v1.30.0),升级只需改一行image标签,docker-compose pull && docker-compose up -d即可完成。更关键的是回滚:某次 v1.29.0 版本引入了新的事件日志格式,导致我们自研的 ELK 日志分析管道解析失败。发现问题后,我只执行了docker-compose down && docker-compose up -d(因为旧镜像还在本地缓存),52秒内服务恢复,用户无感知。换成源码部署,回滚意味着重新 checkout commit、重新编译、重新配置 systemd 服务,平均耗时12分钟以上。
提示:不要用
latest标签。我见过太多人因为latest指向了未充分测试的 RC 版本,导致生产环境登录接口返回 500。务必锁定具体版本号,并在升级前阅读 Vaultwarden Release Notes 中的 Breaking Changes 小节。
2.2 为什么选 Vaultwarden 而非官方 Bitwarden RS?
这里有个关键概念必须厘清:Bitwarden 官方开源的是前端代码和协议规范,真正的服务端实现(bitwarden_rs)早已停止维护,其精神继承者是社区主导的 Vaultwarden 项目。官方 Bitwarden Inc. 提供的 Docker 镜像是闭源的商业版本(bitwarden/server),虽免费但不开源,且强制要求使用其托管的推送通知服务(Push Relay),这与“完全自主可控”的初衷相悖。
Vaultwarden(原名 bitwarden_rs)是 Rust 编写的轻量级替代实现,它完全兼容官方客户端协议(iOS/Android/Desktop/Web),但体积更小、内存占用更低、启动更快。更重要的是:它的源码全部公开在 GitHub,每个 release 都附带 SBOM(软件物料清单)和签名验证,你可以用cosign verify验证镜像完整性,用trivy image vaultwarden/server:v1.30.0扫描已知漏洞。我们团队每月固定做一次镜像安全扫描,去年发现过一次alpine:3.18基础镜像中的opensslCVE-2023-0286 漏洞,Vaultwarden 在漏洞披露后48小时内就发布了修复版本,而官方闭源镜像直到两周后才跟进——这种响应速度,只有真正开源、社区驱动的项目才能做到。
注意:Vaultwarden 不是“破解版”或“阉割版”。它支持官方所有核心功能:TOTP 生成、密码强度分析、安全笔记、附件加密存储、SAML/OIDC 单点登录、自定义域名、多语言界面。唯一不支持的是官方的“紧急访问”(Emergency Access)功能,但该功能本身存在设计争议(需信任第三方管理员),绝大多数专业用户反而认为这是个安全减法。
2.3 整体服务拓扑:不止是 Vaultwarden 一个容器
一个真正可用的生产级 Bitwarden 实例,绝不是单个vaultwarden/server容器跑起来就完事。它需要一套最小可行的服务链来支撑高可用、可观测、可运维。我目前在用的拓扑结构如下(基于 Docker Compose v2.20+):
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Nginx Proxy │───▶│ Vaultwarden API │───▶│ PostgreSQL │ │ (SSL Termination)│ │ (Core Service) │ │ (Persistent DB) │ └─────────────────┘ └──────────────────┘ └─────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ Let's Encrypt │ │ Redis Cache │ │ SMTP Relay │ │ (Auto SSL) │ │ (Session/Rate) │ │ (Email Alerts) │ └─────────────────┘ └──────────────────┘ └─────────────────┘这个拓扑解决了四个关键问题:
- HTTPS 强制与卸载:Nginx 作为反向代理,统一处理 SSL 终止、HTTP→HTTPS 重定向、HSTS 头注入。Vaultwarden 容器内部走纯 HTTP,避免在应用层处理证书复杂度。
- 会话与限流:Redis 存储用户会话、API 调用频次计数(防暴力破解),比用 PostgreSQL 做缓存快一个数量级,且天然支持集群扩展。
- 异步任务解耦:邮件发送(注册验证、密码重置、两步验证绑定)交由专用 SMTP 容器处理,避免阻塞主 API 请求。
- 证书自动化:Nginx Proxy Manager 或 Traefik 可自动申请并续签 Let’s Encrypt 证书,无需手动
certbot renew。
这套组合的资源开销极低:在 2核4G 的云服务器上,7个容器常驻,内存占用峰值 680MB,磁盘占用(含30天日志+数据库+附件)约 4.2GB。对比官方推荐的 4核8G 最低配置,节省了近 60% 的硬件成本。
3. 核心细节解析与实操要点
3.1 镜像选择与版本锁定:别让“最新版”毁掉你的密码库
Vaultwarden 的镜像发布在两个地方:Docker Hub 官方仓库vaultwarden/server和 GitHub Container Registryghcr.io/vaultwarden/server。强烈建议使用后者,原因有二:
- 构建链路更短:GHCR 镜像由 GitHub Actions 直接从 main 分支构建,Docker Hub 镜像则需经过额外的同步步骤,存在最多 2 小时延迟。当你急需某个 hotfix(比如修复了某个 TOTP 同步 bug),GHCR 总是第一时间可用。
- 签名验证更可靠:GHCR 支持 OCI Artifact 签名,你可以用
cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp 'https://github.com/vaultwarden/vaultwarden/.github/workflows/ci.yml@refs/heads/main' ghcr.io/vaultwarden/server:v1.30.0验证该镜像确实由 Vaultwarden 官方 CI 流水线构建,而非被中间人篡改。
版本选择上,我采用“LTS + 补丁”策略:
- 主版本每 3 个月发布一次(v1.28 → v1.29 → v1.30),我只升级到偶数主版本(v1.28, v1.30),因为社区约定偶数版本为长期支持版(LTS),API 兼容性保障更强。
- 补丁版本(v1.30.0 → v1.30.1)只要包含安全修复(CVE)或严重 Bug 修复,立即升级。
实操心得:我在
docker-compose.yml中这样写镜像声明:services: vaultwarden: image: ghcr.io/vaultwarden/server:v1.30.1 # 注意:这里不写 latest,也不写 1.30(会拉取最新补丁,但失去控制)
3.2 数据持久化设计:数据库、附件、日志一个都不能少
Vaultwarden 的数据分三类,必须分别挂载到宿主机目录,否则容器重启后数据全丢:
| 数据类型 | 官方路径 | 推荐宿主机挂载点 | 关键说明 |
|---|---|---|---|
| SQLite 数据库 | /data/db.sqlite3 | /opt/bitwarden/data/db.sqlite3 | 这是密码库的核心,所有登录凭证、加密密钥、条目元数据都存在这里。必须使用ext4或xfs文件系统,严禁挂载到 NFS 或 SMB 共享目录(SQLite 的 WAL 模式在共享文件系统上极易损坏)。 |
| 附件存储 | /data/attachments | /opt/bitwarden/data/attachments | 用户上传的截图、PDF、身份证照片等。默认加密存储,但体积增长快,建议单独规划磁盘空间。 |
| 日志文件 | /data/logs | /opt/bitwarden/data/logs | 包含info.log(常规操作)、error.log(异常堆栈)、audit.log(敏感操作审计)。audit.log必须开启,它是满足等保2.0“安全审计”要求的关键证据。 |
挂载方式必须用named volume + bind mount 混合模式,这是经过血泪教训总结的最佳实践:
volumes: # 用于存放 SQLite 数据库,用 named volume 保证原子性 bw_db: driver: local driver_opts: type: none device: /opt/bitwarden/data o: bind services: vaultwarden: volumes: # 数据库文件必须用 bind mount 到宿主机绝对路径,确保 SQLite WAL 正常工作 - /opt/bitwarden/data:/data # 日志用 named volume,便于日志轮转和清理 - bw_logs:/data/logs踩过的坑:早期我尝试把整个
/data目录用 named volume 挂载,结果发现 SQLite 的db.sqlite3-wal文件在 volume 内部无法被正确 fsync,导致某次断电后数据库损坏,花了 6 小时从备份恢复。后来改为bind mount到宿主机路径,问题彻底消失。
3.3 网络与端口映射:安全边界从网络层开始
Vaultwarden 默认监听0.0.0.0:80和0.0.0.0:3000(管理后台),这在生产环境是绝对禁止的!正确做法是:
- 禁用所有外部端口映射:在
docker-compose.yml中,vaultwarden服务不声明ports:,让它只在 Docker 内部网络(bitwarden_net)中暴露端口。 - 由 Nginx 统一入口:Nginx 容器映射宿主机
443端口,通过proxy_pass http://vaultwarden:80;转发请求。 - 管理后台严格限制访问:Vaultwarden 的管理后台(
/admin)默认开放,必须用 Nginx 的 IP 白名单或 HTTP Basic Auth 保护。
Nginx 配置片段示例(/etc/nginx/conf.d/bitwarden.conf):
upstream bitwarden_backend { server vaultwarden:80; } server { listen 443 ssl http2; server_name pw.yourdomain.com; # SSL 配置(略,由 certbot 或 traefik 自动管理) location / { proxy_pass http://bitwarden_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } # 管理后台仅允许公司内网 IP 访问 location /admin { allow 192.168.1.0/24; # 公司内网段 allow 2001:db8::/32; # IPv6 内网段 deny all; proxy_pass http://bitwarden_backend; proxy_set_header Host $host; # ... 其他 proxy_set_header } }提示:
X-Forwarded-For头必须透传,否则 Vaultwarden 的登录失败计数器会把所有请求都记在 Nginx 的 IP 上,导致真实用户被误封。我在某次渗透测试中发现,没透传这个头,攻击者用一个 IP 就能刷爆所有用户的登录尝试次数。
4. 实操过程与核心环节实现
4.1 环境准备:从零开始的 15 分钟部署流水线
以下步骤在 Ubuntu 22.04 LTS 服务器上实测通过,全程无需 root 密码(除首次sudo apt update),所有操作均可复制粘贴执行:
Step 1:安装 Docker 与 Docker Compose
# 更新系统 sudo apt update && sudo apt upgrade -y # 安装 Docker(官方脚本,最稳妥) curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER # 退出终端重新登录,使 group 生效 # 安装 Docker Compose v2.20+(必须 v2,v1 已废弃) sudo apt install -y curl sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-composeStep 2:创建项目目录与配置文件
mkdir -p /opt/bitwarden/{data,config} cd /opt/bitwarden # 创建 .env 文件(敏感信息集中管理) cat > .env << 'EOF' # 域名,必须是你能解析到这台服务器的域名 DOMAIN=pw.yourdomain.com # Vaultwarden 管理后台访问令牌(随机生成,32位十六进制) # 生成命令:openssl rand -hex 16 ADMIN_TOKEN=7a3b9c1e2f4d5a6b7c8d9e0f1a2b3c4d # PostgreSQL 连接信息(如果用外部 PG,改这里) DB_URL=sqlite:/data/db.sqlite3 # SMTP 邮件配置(用于注册验证、密码重置) SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_FROM=bitwarden@yourdomain.com SMTP_USERNAME=bitwarden@yourdomain.com SMTP_PASSWORD=your_app_password_here # 附件大小限制(单位:字节) MAX_ATTACHMENT_SIZE=134217728 # 128MB EOF # 创建 docker-compose.yml cat > docker-compose.yml << 'EOF' version: '3.8' services: # Vaultwarden 核心服务 vaultwarden: image: ghcr.io/vaultwarden/server:v1.30.1 restart: unless-stopped environment: - WEBSOCKET_ENABLED=true - SIGNUPS_ALLOWED=false # 关闭公开注册,仅管理员邀请 - ADMIN_TOKEN=${ADMIN_TOKEN} - DOMAIN=https://${DOMAIN} - DB_URL=${DB_URL} - SMTP_HOST=${SMTP_HOST} - SMTP_PORT=${SMTP_PORT} - SMTP_FROM=${SMTP_FROM} - SMTP_USERNAME=${SMTP_USERNAME} - SMTP_PASSWORD=${SMTP_PASSWORD} - MAX_ATTACHMENT_SIZE=${MAX_ATTACHMENT_SIZE} - LOG_LEVEL=info - LOG_FILE=/data/logs/info.log - LOG_FILE_AUDIT=/data/logs/audit.log volumes: - /opt/bitwarden/data:/data - /opt/bitwarden/config:/etc/bitwarden networks: - bitwarden_net # PostgreSQL 数据库(可选,如用 SQLite 可删此服务) # postgres: # image: postgres:15-alpine # restart: unless-stopped # environment: # - POSTGRES_DB=bitwarden # - POSTGRES_USER=bitwarden # - POSTGRES_PASSWORD=bitwarden123 # volumes: # - /opt/bitwarden/pgdata:/var/lib/postgresql/data # networks: # - bitwarden_net # Redis 缓存 redis: image: redis:7-alpine restart: unless-stopped command: redis-server --save 60 1 --loglevel warning volumes: - /opt/bitwarden/redis:/data networks: - bitwarden_net # Nginx 反向代理(简化版,生产环境建议用 Nginx Proxy Manager) nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - /opt/bitwarden/nginx/conf.d:/etc/nginx/conf.d - /opt/bitwarden/nginx/ssl:/etc/nginx/ssl - /opt/bitwarden/data:/data:ro depends_on: - vaultwarden networks: - bitwarden_net networks: bitwarden_net: driver: bridge EOF # 创建 Nginx 配置目录 mkdir -p /opt/bitwarden/nginx/{conf.d,ssl}Step 3:初始化并启动服务
# 创建 Nginx 配置文件 cat > /opt/bitwarden/nginx/conf.d/bitwarden.conf << 'EOF' upstream bitwarden_backend { server vaultwarden:80; } server { listen 80; server_name pw.yourdomain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name pw.yourdomain.com; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_trusted_certificate /etc/nginx/ssl/chain.pem; location / { proxy_pass http://bitwarden_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } location /admin { allow 127.0.0.1; allow ::1; deny all; proxy_pass http://bitwarden_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } } EOF # 启动所有服务 docker-compose up -d # 查看日志,确认启动成功 docker-compose logs -f vaultwarden # 等待出现 "Listening on http://[::]:80" 即表示启动成功Step 4:获取并配置 HTTPS 证书(Let’s Encrypt)
由于我们用了 Nginx,证书申请推荐用certbot:
# 安装 certbot sudo apt install -y certbot python3-certbot-nginx # 申请证书(需确保域名 DNS 已解析到本机 IP) sudo certbot --nginx -d pw.yourdomain.com --non-interactive --agree-tos -m admin@yourdomain.com # 证书会自动写入 /etc/letsencrypt/live/pw.yourdomain.com/ # 创建符号链接到 Nginx 配置目录 sudo ln -sf /etc/letsencrypt/live/pw.yourdomain.com/fullchain.pem /opt/bitwarden/nginx/ssl/fullchain.pem sudo ln -sf /etc/letsencrypt/live/pw.yourdomain.com/privkey.pem /opt/bitwarden/nginx/ssl/privkey.pem sudo ln -sf /etc/letsencrypt/live/pw.yourdomain.com/chain.pem /opt/bitwarden/nginx/ssl/chain.pem # 重启 Nginx 加载新证书 docker-compose restart nginx此时,打开浏览器访问https://pw.yourdomain.com,应该能看到 Bitwarden 登录页。管理后台地址是https://pw.yourdomain.com/admin,用.env文件中的ADMIN_TOKEN即可登录。
4.2 关键参数调优:让密码库既安全又顺滑
Vaultwarden 的性能和安全性高度依赖几个关键参数,它们不在 Web 界面里,必须通过环境变量或配置文件设置:
1. 密钥派生强度(PBKDF2 迭代次数)默认值100000对现代 CPU 来说已不够安全。我将其提升到600000:
environment: - ROCKET_SECRET_KEY=your_very_long_random_string_here - PASSWORD_ITERATIONS=600000计算依据:根据 OWASP Password Storage Cheat Sheet ,2023 年推荐的 PBKDF2-HMAC-SHA256 迭代次数应 ≥ 600,000。实测在 Intel i5-8250U 上,600k 次迭代耗时约 320ms,用户无明显感知,但暴力破解成本提升 6 倍。
2. WebSocket 连接超时与心跳移动端客户端重度依赖 WebSocket 保持长连接,用于实时同步。默认超时太短会导致频繁重连:
environment: - WEBSOCKET_ENABLED=true - WEBSOCKET_IDLE_TIMEOUT=300 # 5分钟空闲超时 - WEBSOCKET_PING_INTERVAL=45 # 每45秒发一次心跳3. 审计日志精细化控制LOG_FILE_AUDIT仅记录关键操作(登录、登出、密码修改、条目增删),但默认不记录 IP 地址。要满足等保要求,必须开启:
environment: - LOG_FILE_AUDIT_IP=true # 在 audit.log 中记录客户端 IP - LOG_FILE_AUDIT_USER_AGENT=true # 记录 User-Agent,识别设备类型审计日志样例(/opt/bitwarden/data/logs/audit.log):
2023-10-15 14:22:37.123 INFO [AUDIT] Login success for user@example.com from 2001:db8::1234 via Firefox/118.0 2023-10-15 14:23:01.456 WARN [AUDIT] Failed login attempt for invalid_user from 192.168.1.100 via Chrome/117.0 (5 attempts)4. 附件加密密钥轮换Vaultwarden 使用 AES-256-GCM 加密附件,密钥默认永不轮换。为符合密钥生命周期管理规范,我设置了 90 天自动轮换:
environment: - ATTACHMENT_KEY_ROTATION_DAYS=90轮换过程全自动:新上传附件用新密钥,旧附件仍可用旧密钥解密,系统后台异步将旧附件重加密为新密钥。轮换期间无服务中断。
4.3 备份与灾难恢复:你的密码库不能只有一份
备份不是“以防万一”,而是“每天必做”。我采用三级备份策略:
一级:本地快照(每小时)利用 Linuxrsync和cron,每小时将/opt/bitwarden/data目录同步到同一台服务器的另一块硬盘:
# /etc/cron.hourly/bitwarden-backup #!/bin/bash rsync -a --delete /opt/bitwarden/data/ /backup/bitwarden/hourly/$(date +\%Y-\%m-\%d_\%H)/ # 保留最近 24 小时快照 find /backup/bitwarden/hourly/ -maxdepth 1 -type d -mtime +1 -exec rm -rf {} \;二级:异地增量(每日)用rclone将/backup/bitwarden/hourly/中的最新快照,加密后同步到对象存储(如 Backblaze B2、阿里云 OSS):
# rclone config 创建 remote 名为 b2_encrypted rclone sync \ --transfers 4 \ --checkers 8 \ --delete-after \ --backup-dir b2_encrypted:bitwarden/backups/daily/$(date -d "yesterday" +\%Y-\%m-\%d) \ /backup/bitwarden/hourly/$(date +\%Y-\%m-\%d_23) \ b2_encrypted:bitwarden/backups/daily/$(date +\%Y-\%m-\%d)三级:离线归档(每月)每月 1 号,将当月所有快照打包为加密 ZIP,刻录到 M-Disc 光盘(寿命 1000 年),存入保险柜。加密密钥用 Shamir 秘密共享算法拆分为 5 份,分给 5 位可信同事保管,任意 3 份可恢复。
实操心得:备份脚本必须包含校验环节。我在
rsync后加了一行:sha256sum /backup/bitwarden/hourly/$(date +\%Y-\%m-\%d_\%H)/db.sqlite3 > /backup/bitwarden/hourly/$(date +\%Y-\%m-\%d_\%H)/db.sha256恢复时先
sha256sum -c db.sha256,校验通过再解压,避免备份文件损坏却浑然不知。
5. 常见问题与排查技巧实录
5.1 登录页面空白或无限加载:前端资源加载失败
现象:浏览器打开https://pw.yourdomain.com,页面显示白屏,F12 控制台报错Failed to load resource: net::ERR_CONNECTION_REFUSED或GET https://pw.yourdomain.com/styles.css net::ERR_ABORTED。
排查路径:
确认 Nginx 是否正常转发静态资源:
# 进入 Nginx 容器,测试能否访问 Vaultwarden 的静态文件 docker-compose exec nginx curl -I http://vaultwarden:80/styles.css # 应返回 200 OK。如果返回 502 Bad Gateway,说明 vaultwarden 容器没起来或端口不对。检查 Vaultwarden 容器日志:
docker-compose logs vaultwarden | grep -i "listening\|error" # 如果没有 "Listening on http://[::]:80",说明服务启动失败。 # 常见原因:SQLite 数据库文件权限错误(宿主机目录属主不是 1001:1001) ls -l /opt/bitwarden/data/ # 正确权限:drwxr-xr-x 3 1001 1001 ... data # 如果不是,修复:sudo chown -R 1001:1001 /opt/bitwarden/data确认域名解析与 HTTPS 配置:
# 在服务器本地测试 curl -k https://pw.yourdomain.com # -k 忽略证书错误 # 如果返回 HTML,说明 Nginx 和 Vaultwarden 都正常,问题在客户端证书信任或 CDN 缓存。 # 清除浏览器缓存,或用 curl -v 查看详细握手过程。
5.2 管理后台打不开,提示 401 Unauthorized
现象:访问https://pw.yourdomain.com/admin,弹出 HTTP Basic Auth 对话框,输入admin和ADMIN_TOKEN后仍被拒绝。
根本原因:Vaultwarden 的管理后台认证不走 HTTP Basic Auth,而是读取请求头Authorization: Bearer <token>。Nginx 默认不会透传这个头。
解决方案:在 Nginx 配置的location /admin块中,添加:
location /admin { # ... 其他配置 proxy_set_header Authorization $http_authorization; proxy_pass_request_headers on; }然后docker-compose restart nginx。
提示:
ADMIN_TOKEN必须是 32 位十六进制字符串(如a1b2c3d4e5f678901234567890abcdef),不能包含-或其他字符。生成命令始终用openssl rand -hex 16。
5.3 邮件发送失败:注册验证链接收不到
现象:新用户注册后,邮箱收不到验证邮件,Vaultwarden 日志中出现SMTP send error: 535-5.7.8 Username and Password not accepted。
排查步骤:
- 确认 SMTP 凭据正确性:Gmail 等服务商要求使用“应用专用密码”,而非账户密码。登录 Gmail 账户 → “管理您的 Google 账户” → “安全性” → “2-Step Verification” → “App passwords”,生成一个 16 位密码,填入
.env文件的SMTP_PASSWORD。 - 测试 SMTP 连接:进入 Vaultwarden 容器,手动测试:
docker-compose exec vaultwarden sh # 在容器内执行 apk add mail-utils echo "Test email body" | mail -s "Test Subject" -r "bitwarden@yourdomain.com" -S smtp="smtp.gmail.com:587" -S smtp-auth-user="bitwarden@your