好文集ChatGPT Plus¥119

别再给1Password交钱了!自建密码库保姆级教程:零订阅费,不用公网IP,手机电脑浏览器全同步

文章目录

密码管理器也要年年续费?自己搭一个密码库,不用公网IP,不用开端口,手机、电脑、浏览器全都能同步。

1Password这类密码管理器,好用是好用,就是每年都得续费。 我现在用的是自建的Vaultwarden,客户端直接用官方的Bitwarden App和浏览器插件,平时该怎么用还是怎么用。

Bitwarden官方免费版其实也能同步,但密码库存在别人的服务器上。自建的话,整个库都在自己机器上,备份、迁移都自己说了算。当然,如果不想折腾服务器,直接用官方的也挺好。

需要的只有一台常开的机器和一个域名,VPS、NAS、家里的旧电脑都行。我找了台闲置的云服务器从零重装了一遍,每一步都截了图,跟着做完你会得到:

  • 一个用自己域名访问的密码库,iPhone、安卓、Mac、Windows、浏览器插件全都能用;

  • 1Password、iCloud钥匙串、Chrome里存的密码,都能直接导进来;

  • 几种常见的备份方案,机器挂了也能原样找回来。

每日叠甲:这是我自己的用法,不一定是最优解。有更好的玩法,欢迎在评论区补充。

整体结构

文章配图

前面说的不用公网IP、不用开端口,靠的都是Cloudflare Tunnel。 Tunnel是服务器主动连出去的,所以家里宽带只有内网地址也没关系,服务器也不用对外开任何端口,扫端口的人根本找不到你的密码库。

顺带还有个好处:HTTPS证书Cloudflare自动给,不用自己折腾Nginx和Let's Encrypt。

准备工作

  • 一台常开的机器:Vaultwarden本身只占几十MB内存,配置再低的机器都带得动。

  • 装好Docker和Docker Compose。

  • 一个托管在Cloudflare的域名:这篇用vault-demo.suversal.com演示,配置时换成你自己的即可。

一、部署Vaultwarden

新建一个专用目录,写好docker-compose.yml:

mkdir -p ~/vaultwarden && cd ~/vaultwarden
nano docker-compose.yml

填写Vaultwarden这个服务:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault-demo.suversal.com"
      SIGNUPS_ALLOWED: "true"
      TZ: "Asia/Shanghai"
    volumes:
      - ./vw-data:/data
    ports:
      - "127.0.0.1:8081:80"
    networks:
      - tunnel

networks:
  tunnel:
    external: true

五个地方要注意:

1. DOMAIN一定要填,写成你最终访问的完整地址,带https://。不填的话,WebAuthn两步验证、附件下载这些功能会出问题。

2. SIGNUPS_ALLOWED先开着,等自己的账号注册完马上关掉,后面第三步会讲。

3. 端口写成127.0.0.1:8081:80,不要只写8081:80。

先说这串怎么读:服务器IP:服务器端口:容器端口。Vaultwarden在容器里监听的是80,映射到服务器的8081上。8081是我随便挑的,服务器上没被占用就行,换成别的也可以。

这一点很多教程没提:Docker映射的端口会绕过ufw和iptables的规则,你以为防火墙挡住了,其实整个公网都能直接访问。

加上127.0.0.1之后,这个端口只有本机能访问,外面进来只能走Tunnel。

4. ./vw-data:/data是数据存放的位置。

左边是服务器上的目录,也就是~/vaultwarden/vw-data,右边是容器里的路径。所有数据都写在这个目录里,容器删了重建也不会丢,后面备份的就是它。

5. networks是把它接到一个叫tunnel的共享网络上。

后面cloudflared会单独放一个目录,也接到这个网络上,这样它就能找到Vaultwarden,第二步会细讲。external: true的意思是这个网络不归这个compose管,要提前手动建好。

先建网络,再启动:

docker network create tunnel
docker compose up -d
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8081/

返回200就说明跑起来了。

看一眼数据目录:

$ ls vw-data
db.sqlite3  db.sqlite3-shm  db.sqlite3-wal  rsa_key.pem  tmp

记住这几个文件,后面做备份的时候会用到:不是只有db.sqlite3一个文件。

文章配图

到这一步,Vaultwarden服务其实已经跑起来了,只是暂时只有服务器自己能访问。

有朋友可能会想,直接把服务器安全组放行,用http://公网IP:8081访问不就完了?

不建议:一是网页版只在HTTPS下才能正常注册登录,二是密码库直接挂在公网端口上,谁都能扫到。所以下一步再花五分钟用Cloudflare套一层,自动有HTTPS,服务器也不用开任何端口。

二、用Cloudflare Tunnel把它安全地发布出去

1. 在Cloudflare后台创建隧道

Cloudflare后台最近改版了,Tunnels已经从Zero Trust挪到了联网→Tunnels。

点「创建隧道」,名字随便起:

文章配图

2. 拿到Token

创建后,我们这里依旧用Docker部署,页面上会给一条docker run ... --token eyJ...的命令。

点右边的复制按钮,把--token后面那一长串单独存下来。这个Token等于这条隧道的钥匙,不要发给别人,也不要截图外传。

文章配图

3. 给cloudflared单独建一个目录

我没有把cloudflared和Vaultwarden写在同一个compose里,而是单独放一个目录。

因为Tunnel不只是给密码库用的,以后家里的NAS、图床、导航页想发布出去,都可以走这同一条隧道。单独管理的话,改Vaultwarden、重建Vaultwarden都不会影响别的服务。

Cloudflare Tunnel真的是非常好用的服务,后续有机会再详细给大家介绍更多用法。

先把Token写进.env文件:

mkdir -p ~/cloudflared && cd ~/cloudflared
echo 'TUNNEL_TOKEN=粘贴你的Token' > .env
chmod 600 .env

docker compose启动时会自动读取同目录下的.env,compose文件里的${TUNNEL_TOKEN}就会被换成真正的Token。这样Token不用写进docker-compose.yml,这个文件截图、发给别人看都没关系。

然后新建~/cloudflared/docker-compose.yml:

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    environment:
      TUNNEL_TOKEN: ${TUNNEL_TOKEN}
    networks:
      - tunnel

networks:
  tunnel:
    external: true

--no-autoupdate是不让cloudflared在容器里自己升级,要更新就重新拉镜像。

最后的networks和Vaultwarden那边一样,接到同一个tunnel网络上,这是两个目录能互相找到的关键。以后别的服务想走这条隧道,也是在它的compose里加上这几行。

启动并看日志:

docker compose up -d
docker logs cloudflared 2>&1 | grep Registered

看到4行Registered tunnel connection就是连上了(cloudflared会同时连4个Cloudflare节点,互为备份),回到网页也会显示「隧道连接成功」:

文章配图

文章配图

4. 添加路由

点「继续」回到列表,进入刚建的隧道→添加路由→已发布的应用程序,这样填:

这样就算服务器没有公网IP,也能通过Cloudflare Tunnel从外网访问到这个服务。

文章配图

Service URL填的是http://vaultwarden:80,不是localhost:8081,这里最容易填错。

  • vaultwarden是什么:它不是公网域名,是Vaultwarden那个compose里给服务起的名字。两个容器都接在tunnel网络上,Docker会在这个网络里把服务名解析成对应容器的内网地址,所以cloudflared直接用这个名字就能找到Vaultwarden。

  • 为什么不是localhost:虽然都在同一台服务器上,但每个容器都有自己独立的网络,相当于服务器里的一台小虚拟机。cloudflared在自己的容器里访问localhost,找的是它自己,不是服务器,自然连不上。

  • 为什么是80不是8081:8081是映射到服务器上的端口,只在服务器本机有效。容器之间走内部网络,直接用Vaultwarden在容器里监听的80。

整条链路是这样的:

浏览器 → vault-demo.suversal.com → Cloudflare → Tunnel → cloudflared容器 → http://vaultwarden:80 → Vaultwarden容器

如果你的cloudflared不是用Docker跑的,而是直接装在服务器上,那就反过来,要填http://localhost:8081。

保存后Cloudflare会自动加一条DNS记录,指向这条隧道。如果这个子域名之前已经有记录,会提示冲突,先去DNS页面把旧记录删掉再保存:

文章配图

浏览器打开你的域名https://vault-demo.suversal.com,能看到Vaultwarden的登录页就成功了:

文章配图

三、注册主账号,然后马上关闭注册

点「Create account」注册你自己的账号:

文章配图

主密码一定要记牢。

密码库是用主密码在本地加密的,服务器上只有密文。这是它安全的原因,也意味着主密码忘了,谁都救不回来,Vaultwarden没有重置功能。

注册完登录进去,就是熟悉的Bitwarden界面:

文章配图

接着立刻关闭注册,不然知道你域名的人都能来注册:

cd ~/vaultwarden
sed -i 's/SIGNUPS_ALLOWED: "true"/SIGNUPS_ALLOWED: "false"/' docker-compose.yml
docker compose up -d

如果你是在Mac上操作,sed -i要改成sed -i '',或者直接用编辑器把true改成false。

改完环境变量要用docker compose up -d重建容器,只restart是不会生效的。

验证一下:

docker exec vaultwarden env | grep SIGNUPS
# SIGNUPS_ALLOWED=false

再用一个新邮箱去注册页试试,会直接提示「Registration not allowed」:

文章配图

建议顺手在网页版的Settings→Security→Two-step login里给账号开两步验证,恢复代码单独存好。

到这里,服务端其实就已经搭好了:有自己的域名,有HTTPS,不开任何端口,也只有你一个人能登录。

下面讲怎么用起来:先把手机和浏览器连上,再把原来的密码导进来,最后说说怎么备份。

四、手机App和浏览器插件连接自建服务器

官方的Bitwarden App和浏览器插件都能直接用,区别只在登录前选一下服务器。

1. 下载官方客户端

装的都是Bitwarden官方的客户端,Vaultwarden本身没有App:

Windows、Linux客户端和其他浏览器,在官方下载页都能找到。

2. 登录自建服务器

以浏览器插件为例,手机App的步骤也一样。安装后打开插件:

在登录页找到「登录到」→选自托管→服务器URL填你的域名,比如https://vault-demo.suversal.com→保存后用邮箱和主密码登录。注意只填域名,不要加:8081,也不要填IP。

文章配图

登录后打开对应的网站,就会弹出自动填充提示。我这里已经导入了数据,导入方法见第五步:

文章配图

3. 日常怎么用:手机和电脑

iPhone**:先把Bitwarden设成系统的自动填充,设置→通用→自动填充与密码,打开Bitwarden(iOS 17及以下在设置→密码→密码选项里)。

设好之后,在App或网页里点一下登录框,键盘上方就会出现Bitwarden里的账号,Face ID确认一下就填进去了。

如果系统自带的「密码」也开着,iOS会弹窗让你选用Bitwarden还是「密码」,保存新密码时也会问存到哪里。想省事的话,把「密码」关掉,只留Bitwarden。

顺手在App的设置→账户安全里打开「使用Face ID解锁」,不然每次都要输一遍主密码。

文章配图

安卓:在系统设置里搜「自动填充服务」,选Bitwarden。各家手机的路径不太一样,直接搜最快。

电脑:主要靠浏览器插件。点一下登录框就会弹出匹配的账号,Chrome里也可以按Ctrl+Shift+L(Mac是Cmd+Shift+L)直接填进去。

注册新网站时,插件会提示要不要保存密码,点保存之后,手机上也会同步过来。插件里还能直接生成随机密码,长度、符号都能自己调。

文章配图

4. 把URI匹配检测改成「主机」

Bitwarden默认的匹配方式是「基础域名」,也就是说wrap.suversal.com、nav.suversal.com、vault.suversal.com都会被当成同一个网站suversal.com。

这也是我用iCloud密码时最难受的一点:它只能这样匹配,没法改,也是我换到Vaultwarden的原因之一。

如果你和我一样,在同一个域名下挂了好几个自建服务,每次自动填充都会把所有子域名的账号全弹出来,很容易填错。

改法:设置→自动填充→默认URI匹配检测→主机。改完之后只有主机名完全一致才会匹配,互不干扰。

五、把已有密码数据导进来

用苹果全家桶的朋友,账号密码大概率都存在iCloud钥匙串里。不用一条条手动搬,Apple自带导出功能,导成CSV之后Vaultwarden可以直接导入。

1. 在Mac上导出

导出要在Mac上操作。iPhone上存的密码只要开了iCloud钥匙串同步,Mac上就都有,不用单独导。

macOS 15及以上:打开「密码」App→菜单栏文件→导出所有密码到文件…,用Touch ID或开机密码确认。

文章配图

导出会得到一个Passwords.csv,里面所有密码都是明文,导完马上处理掉,后面会讲。

2. 导入到Vaultwarden

打开网页版,进入Tools→Import:

  • Vault:选My vault

  • File format:选Safari and macOS (csv)

  • Select the import file:选刚才导出的Passwords.csv

点Import就完成了。

文章配图

文章配图

能带过来的有:网站、用户名、密码、备注,还有验证码。

通行密钥(Passkey)带不过来,CSV里根本没有这一项,用到通行密钥的网站要在新密码库里重新添加一次。

3. 保障安全:导完立刻删掉CSV

这一步很关键也很容易忘:

  • 把Passwords.csv删掉,废纸篓也要清空;

  • 如果中途用隔空投送、微信或者网盘传过这个文件,那边也要删干净。

另外,导入只追加,不去重,同一个文件导两次就会有两份。导入前先确认库里是空的,或者只导一次。

从其他地方搬过来也一样

Chrome、Edge、1Password、官方Bitwarden都是同样的流程:先在原来的地方导出,再到Tools→Import里选对应的格式。

六、备份:常见的几种方案

密码库放在自己机器上,最怕硬盘损坏或者服务挂了,所以一定要有异地备份。

这一步每个人的环境差别很大,我说一下原则、我自己用的方案和其他常见做法,挑一个适合自己的就行。

先记住三个原则

  • 备份整个vw-data目录,不只是db.sqlite3。附件、密钥这些文件都在里面。

  • 数据库要先导出再复制。服务运行时直接复制sqlite文件,可能拷到写了一半的库。用sqlite3 db.sqlite3 ".backup 备份路径"导出一份一致的副本,或者先停容器再复制。

  • 传出去之前先加密,解压密码另外存一份,不要只存在这个密码库里,不然服务器挂了,密码也跟着锁在里面。

我用的方案:每天自动加密推到GitHub

整体分两部分:先做一次性准备,之后每天凌晨自动跑。

文章配图

大概步骤:

  1. 新建一个GitHub私有仓库,Visibility一定选Private,用来放备份。

  2. 给服务器生成一把Deploy Key,添加到这个仓库,勾上「Allow write access」。它只对这一个仓库有效,服务器万一被攻破,泄露的也只是这个仓库。

  3. 随机生成一个解压密码,存在服务器上给脚本用,同时自己另外存一份。

  4. 写一个备份脚本:sqlite3 .backup导出数据库→连同整个vw-data用7z AES-256加密→git push推到仓库,顺手删掉30天前的旧备份。

  5. 用cron设成每天凌晨3点自动执行,隔几天看一眼日志有没有报错。

跑起来之后,GitHub仓库里每天会多一个加密包:

文章配图

用GitHub的好处是免费、自带历史版本,加密之后就算仓库泄露,没有解压密码也打不开。

装在Mac上的话

  • Time Machine:vw-data在用户目录下,Time Machine会自动备份进去。不过和前面原则里说的一样,直接拷运行中的数据库不一定完整,适合当第二份。

  • 定时脚本放到iCloud云盘:每天用sqlite3导出、7z加密之后,丢进iCloud云盘的文件夹,自动同步上云。

  • 定时脚本推到GitHub:和上面的方案一样,脚本在Mac上照样能用。

  • Mac上定时任务建议用launchd而不是cron:cron遇到电脑睡眠会直接跳过那一次,launchd会在唤醒后补跑。

最省事的兜底:手动导出

不管服务端怎么备份,都建议隔一段时间在网页版Tools→Export vault导出一份,格式选加密的JSON(密码保护),存到U盘或者另一个网盘里。

它不依赖服务器,哪天Vaultwarden彻底不用了,这份文件也能直接导入官方Bitwarden或者别的密码管理器。

备份完一定要恢复一次

没恢复过的备份不算备份。不管哪种方案,恢复的思路都一样:

停掉容器→解压备份→把原来的vw-data整个挪走→放入备份的vw-data→docker compose up -d。

注意是整个目录替换,不要直接覆盖进去。旧目录里有db.sqlite3-wal这类临时文件,留着的话会被套到恢复的库上,数据可能错乱。挪走的旧目录先别删,恢复出问题还能退回去。

我在演示机上模拟了一次硬盘损坏,把整个vw-data挪走,只靠GitHub上的备份恢复,用户和密码条目一条不少,直接用原来的主密码登录:

文章配图

具体的备份脚本和恢复命令,有需要的朋友可以评论区留言,我再单独整理。

国内服务器的特别说明

这次演示用的是腾讯云上海的机器,比海外VPS多踩了几个坑,用国内机的朋友可以参考:

  • Docker Hub拉不动:要先配镜像加速。我这台机器已经配好了,拉Vaultwarden镜像只用了7秒。

  • GitHub能连但很慢:HTTPS经常超时,SSH没问题,备份要推到GitHub的话走SSH更稳。

  • 为什么还是用Tunnel:国内服务器直接用80/443端口对外提供网站,域名需要备案。Tunnel不开入站端口,就没有这个问题,而且比直接暴露在公网上更安全。

  • 代价是慢:流量要绕到Cloudflare的海外节点,我这台小水管第一次打开网页版要等半分钟左右。日常用App和插件影响不大,只是同步慢一点。

如果没有特殊需求,海外VPS或者家里的电脑会比国内云服务器省心很多。

最后检查一遍

搭完之后,对着这几条过一遍:

  • 端口绑定的是127.0.0.1,公网直连端口打不开

  • SIGNUPS_ALLOWED已经改成false,并且用新邮箱试过注册不了

  • 账号开了两步验证,恢复代码单独存好

  • 导入用的CSV已经删掉,废纸篓也清空了

  • 选好了一种备份方案,解压密码另外存了一份

  • 真的从备份恢复过一次

另外两个常被问到的点:

  • 怎么更新:隔一段时间在~/vaultwarden和~/cloudflared里各跑一次docker compose pull && docker compose up -d,新版客户端偶尔需要新版服务端。

  • 管理后台/admin:不设置ADMIN_TOKEN时是关闭的,个人用不需要打开。

写在最后

到这里,一个完全由自己掌控的密码库就搭好了。总结一下整个过程:

步骤做了什么最容易踩的坑
部署Vaultwarden一个compose文件,Docker启动端口要绑127.0.0.1,DOMAIN一定要填
发布到公网cloudflared单独部署,走Cloudflare TunnelService URL填http://vaultwarden:80,不是localhost
关闭注册SIGNUPS_ALLOWED改成false要用docker compose up -d重建,restart不生效
连接客户端官方Bitwarden App和浏览器插件登录前选自托管;URI匹配改成「主机」
导入密码Apple密码导出CSV,网页版导入导完立刻删掉CSV
备份选一种方案,异地加密保存解压密码另存一份,一定要恢复一次

整套搭下来,服务器不用开任何端口,也不用自己管证书,日常用起来和官方Bitwarden几乎没区别。

cloudflared单独部署之后,以后别的服务想发布出去,接上tunnel网络、再加一条路由就行。

感谢看到这里,如果这篇文章对你有帮助的话,欢迎关注、点赞、收藏。如果有其他想看的干货文章或者文章中存在错误的地方,也欢迎在评论区指出。我们下篇文章再见。

全文完
查看原文