首页 / 资讯中心 / 文章详情

NAS自建Vaultwarden密码库:HTTPS、自动填充与迁移避坑

NAS自建Vaultwarden密码库:HTTPS、自动填充与迁移避坑 ★ FEATURED ARTICLE
密码库从云上搬回自己家的 NAS这件事这两年在折腾圈里热度一直没降。原因很实在主流的托管式密码管理器该涨价的涨价、该改套餐的改套餐而一台常年通电的家用 NAS跑一个 Vaultwarden 容器几乎不占什么资源内存占用常在几十兆到一百多兆这个量级。但真正动手之后你会发现部署本身是最简单的一步——docker compose up -d一行命令几分钟就起来了。难的全都堆在后面客户端死活不肯连你那个自签证书的地址、浏览器扩展在登录页不弹填充框、几百条密码从别处搬过来之后 TOTP 全废了、附件传到一半卡死。这篇就把从容器落地到 HTTPS、自动填充、密码迁移这四段路完整走一遍重点全放在哪一步会卡住、为什么卡、怎么绕过去。看完你至少能省掉一个周末的反复试错。1. 自建密码库之前先把账算清楚1.1 三条路线摆在一张表里很多人一上来就问用哪个镜像其实更该先问我到底需不需要自建。密码管理这件事目前能走的路无非三条各自的代价差别很大我按自己实际用过的体感列一下维度纯浏览器自带密码管理托管式在线密码库NAS 自建Vaultwarden数据存放位置浏览器账号云端服务商机房自己的硬盘跨设备同步同浏览器生态内尚可全平台全平台TOTP 验证码通常不支持或很弱支持支持附件存储无付费扩容受限于硬盘故障恢复依赖厂商依赖厂商完全靠自己运维成本零零中等偏上风险点换浏览器就断档服务停售、涨价硬盘挂了、证书过期、误删这张表里最值得盯的是最后两行。托管方案的零运维不是白来的你付出的是主动权自建方案的主动权也不是白来的你付出的是运维时间。我在帮别人搭这套东西的时候第一句话通常是你能不能坚持每年做两次备份、证书过期了会不会发现。答不上来的老老实实先用托管方案更省心。1.2 自建之后你必须自己扛的四件事第一件是数据持久化。容器这东西天生是无状态的删了重建数据就没了。所有有价值的东西都在挂载进容器的那个数据目录里包括 SQLite 数据库文件、附件目录、密钥文件、配置文件。这个目录一旦丢了你的密码库就是彻底没了没有找回密码这回事——因为压根没有服务商掌握你的主密码。所以挂载路径必须是 NAS 上独立的一块存储空间不能在容器内部路径里凑合。第二件是传输加密。密码库的客户端对传输安全的要求比普通网站严格得多很多客户端在检测到证书不受信任时会直接拒绝连接而不是像浏览器那样给你一个继续访问的按钮。这就意味着自签证书这条路在移动端会非常难受后面第三章会详细拆。第三件是访问控制。刚部署完的服务默认是允许任何人注册账号的如果你的 NAS 在这个地址上对外开放那就是一个公开的密码库注册入口。必须第一时间关掉开放注册改成管理员邀请制。第四件是灾难恢复。备份文件放在同一块硬盘上不叫备份那叫给同一个故障准备了两份数据。至少要有一份异地副本哪怕是一个加密压缩包定期拷到移动硬盘里锁抽屉。注意这四件事里任何一件没做好自建密码库就还不如用记事本。尤其是第一件和第四件是所有后续操作的前提。2. 容器编排落地目录、权限与配置文件的逐行拆解2.1 数据目录规划比镜像版本更重要我见过太多人把数据目录随手建在系统盘或者某个临时共享文件夹里跑了半年发现系统盘满了或者某次整理文件时把目录挪了位置容器起来之后变成一个空的密码库。规划路径的时候按这个顺序想先在存储池里单独划一个共享文件夹名字最好是纯英文、无空格、无特殊字符比如vault-data。路径深度尽量浅类似/volume1/docker/vaultwarden/data这种结构避免后续在容器里挂载时因为路径里有中文或者空格导致挂载失败——这类失败有时候不报错只是静默挂了个空目录非常坑。然后在这个目录下只让容器往里写不要手动往里塞别的东西。跑起来之后你会看到里面自动生成几个文件db.sqlite3是主库db.sqlite3-wal和db.sqlite3-shm是 SQLite 的预写日志相关文件rsa_key.pem和rsa_key.pub.pem用于签名attachments目录存附件sends目录存临时分享config.json存面板配置。备份的时候整个目录打包就对了别只拷db.sqlite3——WAL 模式下单独拷主库文件有可能拿到一个不完整的状态。权限方面容器一般以非 root 用户运行需要确保数据目录对容器内用户的 UID 可写。在 NAS 的图形界面里最简单的做法是给这个共享文件夹一个明确的属主并且给足读写权限。如果跑起来之后日志里反复出现Permission denied或者数据库打不开十有八九就是这里的问题不用怀疑镜像。2.2 docker-compose 配置逐行说明NAS 上的容器管理套件基本都支持直接粘贴 compose 文件创建项目这比命令行敲参数直观得多。下面这份配置我用得最多逐行解释为什么这么写services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: unless-stopped environment: DOMAIN: https://vault.example.com SIGNUPS_ALLOWED: false INVITATIONS_ALLOWED: true ADMIN_TOKEN: 请替换为一段足够长的随机字符串 PASSWORD_ITERATIONS: 600000 IP_HEADER: X-Real-IP LOG_LEVEL: warn TZ: Asia/Shanghai volumes: - /volume1/docker/vaultwarden/data:/data ports: - 127.0.0.1:8080:80DOMAIN这一行必须写成最终访问用的完整地址带https://前缀而且不能带结尾斜杠。这个值会参与生成一些链接和校验逻辑写错了会出现客户端能登录但附件下载 404这种诡异现象。用域名访问就写域名别写 IP。SIGNUPS_ALLOWED设成false是硬性要求。注意它的语义是关闭自助注册但管理员通过面板发出的邀请依然可以注册只要INVITATIONS_ALLOWED保持为true。很多人把这两个搞混全关了之后发现自己也加不了新账号。ADMIN_TOKEN是访问/admin管理面板的凭据务必用一长串随机字符别用生日或者admin123。这个值明文写在 compose 文件里是有风险的更稳妥的做法是先进容器用哈希工具生成一个 argon2 哈希串把哈希写进配置。面板暴露在互联网上的时候明文令牌被撞出来的概率不低。PASSWORD_ITERATIONS控制客户端派生主密钥的迭代次数值越大越安全但解锁越慢。新版默认值已经在六十万这个量级手机端解锁大概会多等半秒到一秒我认为完全值得。老版本默认只有几万从旧服务迁移过来的账号可能需要手动触发一次密钥轮换才能升上去。IP_HEADER这一行只有在前面挂了反向代理时才需要作用是让服务端从指定的头里读真实客户端 IP。不设的话日志里全是反代容器的内网地址后面想按 IP 做失败登录限制就完全失效。ports那行我特意写成了127.0.0.1:8080:80意思是只监听本机回环地址外部通过反向代理访问。这样即使防火墙配置出了纰漏8080 端口也不会直接暴露在公网上等于多了一道保险。如果你的反向代理跑在另一个容器里需要改成监听 NAS 的内网地址比如192.168.1.10:8080:80。2.3 首次启动后立刻要做的三件事第一看日志确认启动成功。正常情况下会看到数据库迁移完成、监听端口就绪的提示。如果卡在数据库初始化九成是权限问题。第二浏览器打开http://NAS内网IP:8080确认页面能出。这一步只是为了验证容器活着别在这个地址上做任何实际操作因为明文 HTTP 环境下登录会把主密码暴露在链路上。第三访问/admin面板用ADMIN_TOKEN登录确认能进去。顺便在面板里检查一下邀请新用户的开关状态。很多人后面折腾半天无法注册就是这里没打开。3. HTTPS 不是加个证书就完事反向代理这一层的真实工作量3.1 为什么客户端对证书比浏览器苛刻浏览器遇到自签证书会给你一个高级-继续前往的选项点一下就过去了。但密码库客户端不是浏览器它内置的是严格的证书链校验逻辑很多版本根本不提供忽略按钮检测到不受信任就直接断连。更麻烦的是移动端——系统级的自动填充服务在后台发起请求时你连个报错界面都看不到只表现为同步失败。所以我的结论很明确局域网内自建也请尽量用受信任的证书。有三条路可选第一条是申请一个真实域名的免费 DV 证书用 ACME 协议自动签发和续期。这条路的前提是你有一个域名并且能用 DNS 验证的方式完成签发这样不需要把 NAS 的 80 端口暴露出去。这是我最推荐的方式因为证书在所有设备上开箱即信不需要任何额外配置。第二条是自建内部证书颁发机构给自己的内网域名签发证书然后把根证书手动安装到每一台设备上。桌面浏览器好办手机要进设置里装描述文件并手动信任平板、电视盒子这些设备就更麻烦。设备一多维护成本直线上升。第三条是干脆只用 HTTP然后靠其他手段保证链路安全。我不建议这么做因为主密码是要在登录时传输的虽然协议层做了派生处理但把明文 HTTP 暴露在局域网里仍然是没有必要的风险——同一个 WiFi 下的任何设备都能嗅探。3.2 反代配置的关键头与 WebSocket反向代理这一层最常见的问题不是证书而是少配了几个头。下面这份 Nginx 配置是我调整过很多次之后比较稳的版本server { listen 443 ssl; server_name vault.example.com; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; client_max_body_size 525M; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; 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_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 300s; } }client_max_body_size必须放开。密码库的附件上传上限默认是 500MBNginx 默认只允许 1MB不放开的话表现就是上传进度条走到百分之几就失败而且服务端日志里什么都看不到因为请求根本没到服务端。Upgrade和Connection这两个头是为了 WebSocket。新版服务端已经内置了实时同步通道缺了这两个头网页端会表现为另一台设备改了密码这边刷新才看得到或者干脆报连接错误。proxy_read_timeout拉长到 300 秒是因为大附件上传和全库导出这类操作耗时可能超过默认的 60 秒。如果你用 Caddy配置会短很多它默认就处理了 WebSocket 和证书签发vault.example.com { reverse_proxy 127.0.0.1:8080 request_body { max_size 525MB } }3.3 局域网域名 内部签发证书的落地路径不想折腾域名的话用内部域名加内部 CA 也能跑通但要把细节做对。假设你的内网域名是vault.home.lan步骤是在 NAS 上或者任意一台常开的机器上生成一个根证书和私钥根证书有效期设长一点比如十年。用这个根证书给vault.home.lan签发一个服务器证书必须把这个域名写进证书的 SAN 字段。只写 CN 不写 SAN 的证书现代客户端一律不认。把服务器证书和私钥配到反向代理上。把根证书装到每一台需要访问的设备上。第四步是真正的痛点。手机端要先下载根证书文件进系统设置里安装描述文件然后再到关于本机-证书信任设置里手动把完全信任的开关打开——少做这一步证书装了也白装。平板和其他设备同理。我一般会告诉对方设备超过三台就直接去买个域名走 ACME 路线省下来的时间远超那点域名费用。3.4 证书续期与一条自检命令自动签发的证书有效期一般是九十天续期失败是自建服务最常见的半年后突然不能用了的原因。给自己留一个检查手段定期从外网视角跑一次证书检查看剩余有效期。echo | openssl s_client -connect vault.example.com:443 -servername vault.example.com 2/dev/null \ | openssl x509 -noout -dates -subject输出里的notAfter就是到期时间。如果剩余不到二十天而自动续期没动静就要去查 ACME 客户端的日志了。常见原因是 DNS 验证用的 API 密钥过期或者验证记录还没生效就发起了挑战。4. 自动填充真正决定用得下去的环节4.1 扩展端填服务器地址的顺序很关键装好浏览器扩展之后很多人第一反应是直接输入账号密码登录结果怎么都登不进——因为扩展默认连的是官方服务器。正确的顺序是先点扩展图标在登录界面找到设置或者齿轮图标进入自托管环境或者服务器地址的设置项把环境从默认区域切换到自托管填入你的完整地址https://vault.example.com保存。然后刷新整个页面或者重开浏览器再回来看登录框。保存之后没生效的情况非常常见本质是扩展的后台进程还持有旧配置重启一下就对了。地址填写上有两个细节容易错一是结尾不能带斜杠https://vault.example.com/和https://vault.example.com在某些版本里行为不一致二是必须带协议头只写域名会被当成相对路径处理然后报一个语焉不详的网络错误。4.2 匹配规则与等价域名自动填充的核心逻辑是当前页面域名是否匹配条目里保存的 URI。默认是精确匹配主机名但实际使用中经常遇到一个服务有多个域名的情况比如主站在app.example.com登录接口却在login.example.com或者同一个网站在国内和国际用了两个不同后缀。这种情况用等价域名功能解决。在条目的编辑界面里可以把多个域名归到一组之后任何一个域名触发填充都能匹配到同一条记录。我给自己的常用服务都配了这个配完之后最直观的体感是再也不用点开扩展手动搜条目了。另一个细节是 URI 匹配策略。编辑条目时可以选主机匹配、基础域名匹配、起始字符匹配、正则匹配、精确匹配这几种。默认的主机匹配够用九成场景基础域名匹配适合那些有一堆子域名的内部系统正则匹配只在非常特殊的场景用写错了会导致填充框在不该出现的地方乱弹。4.3 移动端自动填充的开启路径安卓端的做法是把密码库客户端设为系统的自动填充服务。进入系统设置的语言和输入法或密码与账户相关页面找到自动填充服务选项选中客户端即可。选完之后第一次在应用里点输入框会弹出客户端的解锁界面验证指纹或主密码后才把凭据填进去。iOS 端的路径是系统设置里的密码→密码选项→自动填充密码把来源切换成你的客户端。iOS 的限制更多一些某些应用会禁用第三方自动填充遇到这种情况只能手动复制粘贴属于系统层面的限制不是配置问题。还有一层是客户端自身的解锁方式。默认每次调起都要输主密码体验很差。建议在客户端里开启生物识别解锁并把锁定超时设成一个合理的值比如十五分钟或一小时。设太短会频繁被打断设太长等于把解锁状态一直挂着看你的使用环境权衡。4.4 填不上时的排查顺序自动填充不生效是个高频问题我总结了一个从简到繁的排查顺序按这个走基本能定位现象最可能的原因处理动作扩展图标上一直转圈服务器地址填错或证书不受信检查地址是否带协议头、结尾无斜杠用浏览器直接访问确认证书正常能登录但列表为空连到了另一个服务器实例检查扩展的连接环境设置是否被重置登录页不弹填充框条目里的 URI 与当前域名不匹配查条目的 URI 字段必要时配置等价域名弹框出现但填充后密码错条目存的是旧密码核对条目必要时从浏览器再导入一次覆盖只在某个网站失效该站点用了 iframe 或多步登录手动复制或用正则匹配放宽规则移动端完全不弹系统未选自动填充服务回系统设置检查自动填充服务选项表格里第三条是最常见的。很多人导入密码之后条目里的 URI 存的是带www的完整地址而实际访问的是不带www的版本看起来就差一点但匹配逻辑上就是两个不同的主机名。5. 密码迁移从 CSV 到加密库的搬运细节5.1 导出前的三件准备工作密码迁移是整个流程里风险最高的一步因为中间会经过一个明文 CSV 文件。动手之前先做三件事先把目标库的注册和登录跑通确认能正常创建条目、能同步到手机。别等导完了才发现服务端配置有问题那样你得重新导一遍。确认导出源支持的格式。主流浏览器能导出 CSV其他密码管理器多半也支持导出 CSV 或者加密的 JSON。优先选加密 JSON 格式因为 Vaultwarden 支持直接导入自家的加密导出格式中间不落地明文安全得多。CSV 只在别无选择时用。准备一个安全的中间环境。导出的 CSV 是纯明文里面每一行都是账号密码。这个过程里不要用聊天软件传文件、不要放公共网盘、不要留在下载目录里。处理完之后用符合规范的方式把文件彻底删掉。5.2 CSV 字段映射与清洗浏览器导出的 CSV 字段一般就这几列name、url、username、password、note。而密码库的导入器期望的列名可能是login_uri、login_username、login_password、notes这种。直接导入会失败或者只导进一半数据。最稳的做法是先拿一份文件做小样本测试。把 CSV 用表格软件打开只留三五行导入一次看条目的各个字段有没有落到正确的位置。这一步花五分钟能省掉后面整个库重导的时间。清洗时重点处理这几类URL 列缺协议头。有些导出只写www.example.com导入器可能会把它当相对路径。批量在前面补https://更稳妥。多行备注。CSV 里带换行的备注字段很容易在导入时错位导致后面的行整列偏移。用表格软件打开时留意有没有行数比预期多的情况多出来的通常是备注里的换行被当成了行分隔。逗号和引号。密码里带逗号、带双引号的情况在 CSV 里会被转义成双写引号导入器的容错能力参差不齐最好先扫一遍。空密码行。有些导出的 bookmarks 类记录密码为空导入后会成为一堆无意义的条目提前删掉。重复条目。同一个站点存了三条不同密码的情况非常普遍导入后手动合并比重导一次省事。5.3 导入、校验与善后导入入口在客户端的工具→导入数据里选好文件格式和文件点导入。导入完成之后会显示成功条数把这个数字和源文件的行数对一下差一条都要查清楚是哪一条丢了。比条数更重要的是抽查。我一般会抽三类条目验证最常用的三五个站点的账号密码是否完整、带 TOTP 的那几个条目的验证码是否还能正常生成、带附件的条目附件是否还在。前两类是高频使用第三类最容易在导入时丢失。TOTP 是最容易出问题的一块。浏览器导出的 CSV 里通常不包含 TOTP 密钥你之前存在浏览器里的验证码要么已经丢了要么需要从原密码管理器的加密导出里才能带过来。如果原服务商支持导出带 TOTP 的加密 JSON务必用那个格式重导一次比事后一个一个手动补强太多。善后环节确认导入无误之后把中间 CSV 文件安全删除别只是拖进回收站。同时去原来存放密码的地方浏览器、旧管理器把已保存的凭据清掉避免留下一个长期的明文副本。最后把主密码换一次——迁移过程中字符经过了好几道手换一次最安心。注意导入完成后不要立刻清空源数据。留一到两周的观察期实际用一遍再删避免某些冷门站点的密码在导入时丢失而你没发现。6. 长期运行的维护动作备份、升级与加固6.1 备份要能恢复才算备份备份这件事的标准不是有没有备份文件而是能不能恢复。我的做法是每周自动打包一次整个数据目录压缩包用强加密密码保护存到 NAS 上另一块独立的硬盘里。每月手动把最新的一份拷到移动硬盘上那块硬盘平时不插在机器上。每季度做一次恢复演练——起一个临时容器把备份包解压到临时目录挂载进去登录确认条目数量对得上然后删掉临时容器。没做过恢复演练的备份我心里从来不把它当备份。打包命令大概是这个样子tar -czf /volumeUSB1/backup/vw-$(date %Y%m%d).tar.gz \ -C /volume1/docker/vaultwarden data如果不想停机直接拷目录在 WAL 模式下有极小概率拿到不一致的状态。更稳的做法是先用 SQLite 的在线备份命令导出一份一致的数据库副本再打包附件目录sqlite3 /volume1/docker/vaultwarden/data/db.sqlite3 \ .backup /volumeUSB1/backup/db-snapshot.sqlite3这个命令在数据库被写入的同时也能安全导出不会破坏一致性。6.2 升级与回滚的节奏容器化部署的升级很简单拉新镜像、重建容器数据目录不动配置不动。但有两个前提第一升级前先备份。这一步不能省。新版镜像偶尔会带数据库结构变更变更一旦执行就没法简单往回退除非你有升级前的备份。第二看更新说明里的破坏性变更。有些版本会调整环境变量的名称或者改变默认行为。我就遇到过某次升级之后管理员面板突然进不去原因是新版对令牌的校验方式变了需要重新生成。这类变更通常在发布说明里写了但没人看。回滚的方式是把镜像标签从latest改成一个明确的老版本号重建容器如果数据库结构已经变了就把升级前的备份还原回去。所以正确的做法其实是不要用latest标签而是固定一个具体的版本号定期手动改版本号升级。这样出问题时你能精确知道该退回哪个版本。6.3 加固清单跑起来之后的加固按优先级从高到低关闭自助注册改成邀请制。这一条前面反复提过是必须的。开启两步验证。至少给管理员账号开用 TOTP 加上一串恢复码。恢复码要打印出来或者存在离线的地方。限制失败登录次数。服务端支持配置登录失败后的锁定策略把失败次数阈值和锁定时长设一下能挡住大部分暴力尝试。管理面板路径保护。面板路径虽然固定但令牌够长基本能防住别把令牌写进任何会同步的笔记里。反向代理层加一层访问限制。如果这个库只在自己家里用可以在反代上配置只允许内网网段访问出门在外的时候再单独放开能砍掉绝大部分扫描流量。保持镜像更新。自建服务最怕的是装好就忘了跑了两年的老版本往往有一堆已知问题。定个日历提醒每两个月检查一次。7. 几个我实际踩过的坑第一个坑是附件上传卡死。当时配置都写了client_max_body_size但还是传不上去。查了很久发现在反代后面还有一层 NAS 自带的网关那一层的请求体限制没放开。教训是链路上有几层代理就要把每一层的限制都放开只改最外层没用。判断方法是看请求到底走到了哪一层的日志。第二个坑是管理员令牌失效。某次升级之后/admin一直提示令牌错误我以为是配置被覆盖了重新生成了一遍还是不行。最后发现是新版对令牌的格式做了校验纯随机的短字符串不再被接受需要换成哈希形式。这个错误的提示信息非常模糊只说无效令牌浪费了两小时。第三个坑是时间不同步导致 TOTP 报错。NAS 的 NTP 同步出过一次问题系统时间慢了几分钟结果所有 TOTP 验证码都不对客户端提示验证码无效。我一开始以为是密钥导入错了反复重导了好几次。后来在服务端日志里看到时间戳异常才反应过来。TOTP 对时间偏差的容忍窗口通常只有三十秒左右服务器时间不准验证码必然失败。排查这类问题第一步永远是看时间date系统时间如果偏差超过半分钟先去修 NTP 再来谈别的。第四个坑是移动端自签证书装了也白装。给手机签了证书、装了根证书客户端还是连不上。最后发现是因为访问地址用的是 IP 而不是域名而证书的 SAN 里只写了域名。证书校验的是你访问的这个地址和证书里列的地址是否一致用 IP 访问就必须在 SAN 里也有那个 IP。所以要么统一用域名要么把 IP 也加进 SAN。第五个坑是导出条目里的密码带尾部空格。有些网站的注册表单会保留输入的尾部空格导致保存的密码多了个空格而导入方可能在处理时把它去掉了登录就失败。这类问题极难发现因为界面上看不出差异。在浏览器里手动登录一次对照一下就能确认。最后一个经验迁移完成后的一周内把所有常用站点手动登录一遍。密码库的价值不在于存了多少条而在于每一条都真的能用。我一般会列一个清单把前十高频的站点逐个登一次确认无误之后才算迁移结束。剩下那些一年用一次的长尾站点出问题的概率确实存在但代价可控——真出问题了走一次密码重置流程就行。
阅读完成 · 觉得有帮助?
咨询建站