自建密码管理器:Vaultwarden 部署与使用指南

为什么要自建

现在多数人用浏览器自带的密码管理器,或者 1Password、LastPass 这样的商业服务。这些方案各有短板:

  • 浏览器管理器:跨浏览器不互通,换设备就断档。
  • 商业服务:密码存在别人的服务器上,LastPass 历史上出过多次数据泄露事件。
  • 同步焦虑:手机、平板、笔记本和台式机之间,密码不一致是最常见的痛点。

自建方案可以把数据留在自己的 NAS 上,同时享受全平台自动填充。一次配置,长期受益。

Vaultwarden 是什么

Vaultwarden 是 Bitwarden 服务端的 Rust 重写版本。与官方的 Bitwarden Server(需要 .NET + SQL Server,内存起步 2GB)不同,Vaultwarden 用 SQLite 做存储,空闲时内存占用不到 20MB,在树莓派上都能跑。

它完全兼容 Bitwarden 的官方客户端——浏览器插件、桌面端、手机 App 全部通用。也就是说,你部署的是一个轻量的 Vaultwarden 服务端,但所有用户使用的都是 Bitwarden 原厂客户端。

对比 官方 Bitwarden Vaultwarden
运行时 .NET + SQL Server Rust + SQLite
内存占用 ~2 GB ~20 MB
高级功能(TOTP/组织) 付费解锁 全部免费
硬件要求 中高配 VPS 树莓派即可

环境准备

需要一台 24 小时开机的 Linux 主机或 NAS。下面以群晖 DSM + Container Manager 为例,其他平台只需调整数据卷路径。

前置要求:

  • Docker 和 Docker Compose 已安装(群晖 DSM 7.2 自带 Container Manager)
  • 一个域名(本文以 vault.example.com 为例)
  • 域名的 DNS 已解析到 NAS 的 IP

群晖上创建好目录结构:

mkdir -p /volume1/docker/vaultwarden/data

data/ 目录下会存放 SQLite 数据库和附件,这是最重要的目录——备份就备份它。

Docker Compose 部署

/volume1/docker/vaultwarden/ 下创建 docker-compose.yml

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    volumes:
      - /volume1/docker/vaultwarden/data:/data
    ports:
      - "127.0.0.1:8088:80"
    environment:
      - DOMAIN=https://vault.example.com
      - SIGNUPS_ALLOWED=false
      - ADMIN_TOKEN=${ADMIN_TOKEN}
      - WEBSOCKET_ENABLED=true
      - LOG_FILE=/data/vaultwarden.log
      - LOG_LEVEL=warn
      - TZ=Asia/Shanghai

关键参数解释:

  • 127.0.0.1:8088:80:仅监听本地回环,外部流量必须走 Nginx 反向代理,杜绝直连风险。
  • SIGNUPS_ALLOWED=false:部署完成后关闭注册。首次使用时先设为 true,注册好账号后再改回 false
  • ADMIN_TOKEN:管理面板密码的 argon2id 哈希。用下面命令生成:
# 生成 ADMIN_TOKEN(替换 your-secret-password)
docker run --rm vaultwarden/server /vaultwarden hash --preset owasp

把输出的哈希值写入 .env 文件:

echo 'ADMIN_TOKEN=$argon2id$v=19$m=65540,t=3,p=4$...' > /volume1/docker/vaultwarden/.env

然后启动:

cd /volume1/docker/vaultwarden
docker compose up -d

访问管理面板:https://vault.example.com/admin,用刚才生成的 ADMIN_TOKEN 登录。

Nginx 反向代理配置

Vaultwarden 需要 WebSocket 支持才能实现实时同步,Nginx 配置需要特别注意。

在群晖 Container Manager 中为 Nginx 创建容器(或复用已有),挂载以下配置:

server {
    listen 443 ssl http2;
    server_name vault.example.com;

    # SSL 证书(下节详述)
    ssl_certificate     /etc/nginx/certs/vault.example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/vault.example.com/privkey.pem;

    client_max_body_size 128M;

    location / {
        proxy_pass http://172.17.0.1:8088;
        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;
    }

    # WebSocket 支持(通知实时推送的关键)
    location /notifications/hub {
        proxy_pass http://172.17.0.1:8088;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /notifications/hub/negotiate {
        proxy_pass http://172.17.0.1:8088;
    }
}

# HTTP → HTTPS 强制跳转
server {
    listen 80;
    server_name vault.example.com;
    return 301 https://$server_name$request_uri;
}

172.17.0.1 是 Docker 默认 bridge 网络的网关地址,容器通过它访问宿主机端口。如果你的 Docker 网络不同,用 ip addr show docker0 确认。

HTTPS 证书配置

既然是密码管理器,HTTPS 是硬性要求——客户端拒绝通过 HTTP 连接。

推荐使用 acme.sh 申请 Let’s Encrypt 免费证书,支持 DNS API 自动续期。以阿里云 DNS 为例:

# 安装 acme.sh
curl https://get.acme.sh | sh

# 设置阿里云 API 密钥
export Ali_Key="LTAI5t..."
export Ali_Secret="..."

# 申请泛域名证书
acme.sh --issue --dns dns_ali -d example.com -d '*.example.com'

# 安装证书到 Nginx 目录
acme.sh --install-cert -d example.com \
  --key-file       /volume1/docker/nginx/certs/vault.example.com/privkey.pem \
  --fullchain-file /volume1/docker/nginx/certs/vault.example.com/fullchain.pem \
  --reloadcmd      "docker exec nginx nginx -s reload"

acme.sh 会自动添加 cron 任务,证书过期前自动续期。

如果域名托管在其他 DNS 服务商,acme.sh 支持 超过 150 种 DNS API,包括 Cloudflare、DNSPod、Namecheap 等。

客户端配置

部署完成并配置好 HTTPS 后,下载 Bitwarden 官方客户端:

平台 下载
浏览器插件 Chrome / Firefox / Edge
桌面端 Windows / macOS / Linux
移动端 iOS App Store / Android Google Play

每个客户端首次打开时,点击左上角齿轮图标,在"自托管"中填入你的服务器地址:

https://vault.example.com

之后用你注册的邮箱和主密码登录即可。所有密码数据端到端加密,服务端只能看到密文。

实用技巧

开启 TOTP 两步验证:Vaultwarden 内置 TOTP 生成器。给每个网站添加 TOTP 密钥后,登录时自动填充验证码,不用再切换到 Google Authenticator。

密码库自动锁定:桌面端设置 15 分钟自动锁定,浏览器插件设置"浏览器重启时锁定"。避免离开电脑后密码库敞开。

紧急访问:在组织设置中指定受信任的联系人,对方可以在你指定的等待期(如 7 天)后获得只读访问权限——真正的"数字遗产"方案。

备份策略

密码库的价值极高,备份需要严格对待:

#!/bin/bash
# /volume1/scripts/backup-vaultwarden.sh
BACKUP_DIR="/volume1/backup/vaultwarden"
DATA_DIR="/volume1/docker/vaultwarden/data"
TIMESTAMP=$(date +%Y%m%d_%H%M)
RETENTION_DAYS=30

# 停止容器保证数据一致性
docker stop vaultwarden

# 打包数据目录
tar -czf "$BACKUP_DIR/vaultwarden_$TIMESTAMP.tar.gz" -C "$DATA_DIR" .

# 重启容器
docker start vaultwarden

# 删除 30 天前的旧备份
find "$BACKUP_DIR" -name "vaultwarden_*.tar.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup done: vaultwarden_$TIMESTAMP.tar.gz"

加入 crontab 每日凌晨执行:

0 3 * * * /bin/bash /volume1/scripts/backup-vaultwarden.sh >> /volume1/scripts/backup.log 2>&1

备份 3-2-1 原则:至少 3 份副本,存储在 2 种不同介质上,其中 1 份在异地。除了 NAS 本地备份,建议用 Syncthing 或 rclone 再同步一份到另一台设备或云存储。

安全加固建议

部署密码管理器,安全方面不能马虎:

  • 关闭注册后务必设置 SIGNUPS_ALLOWED=false,从源头杜绝未授权访问。
  • 定期更新镜像:docker compose pull && docker compose up -d,Vaultwarden 的更新节奏大约是每月一次。
  • 数据库加密:如果你的 NAS 支持文件系统加密(群晖有 Shared Folder Encryption),把 /volume1/docker/vaultwarden/data 放在加密文件夹里。
  • 主密码要强:12 位以上,包含大小写字母、数字和特殊字符。这串密码记在脑子里,不要存在任何地方。
  • 设置失败锁定:管理面板中可配置"多次登录失败后临时封禁 IP"。
  • 查看审计日志:管理面板的 “Diagnostics” 页面能查看所有登录记录,定期检查是否有机器的异常尝试。
  • 关闭不必要的端口映射:不要暴露 Vaultwarden 的直接端口(8088),只有 Nginx 的 443 对外开放。

常见问题排查

客户端提示"发生错误,无法连接服务器"

检查三个地方:

  1. 域名是否能正常解析:nslookup vault.example.com
  2. Nginx 是否在运行:docker ps | grep nginx
  3. HTTPS 证书是否过期:浏览器打开 https://vault.example.com 看证书详情

WebSocket 连接失败(无法实时同步)

浏览器插件 F12 看 Console,如果有 WebSocket 报错,确认 Nginx 配置中的 /notifications/hub/notifications/hub/negotiate 两个 location 块是否正确。

忘记主密码怎么办

无法找回。密码库是端到端加密的,主密码丢失就意味着数据永久不可恢复。这也是为什么备份里要包含一份用不同方式加密的导出

定期在桌面端执行:文件 → 导出密码库 → .json(加密),用另一个独立的密码加密后,存到安全的地方。

更新后启动失败

看日志:docker logs vaultwarden。最常见的原因是 SQLite 数据库版本不兼容,Vaultwarden 通常会自动迁移,但如果从很老的版本跳升,可能需要手动操作。建议至少每季度更新一次,每次只跨一个小版本。


自建密码管理器是"数据主权"最直接的体现之一。从注册到全平台同步,整个流程大约半小时,却能用很多年。如果这篇文章对你有帮助,欢迎在评论区交流你的部署经验或踩过的坑。

评论

评论功能基于 Giscus(GitHub Discussions)——在 repo 启用 Discussions 后,到 giscus.app 获取仓库 ID 填入 src/_data/site.json 即可启用。