QNAP NAS 部署 Vaultwarden:使用 Cloudflare Tunnel 实现外网访问与手机同步
一、项目背景
我使用 QNAP NAS 部署了 Vaultwarden,并通过 Bitwarden 客户端管理密码。
在局域网内,Vaultwarden 可以正常使用。但如果手机关闭 Wi-Fi,只使用移动数据,就无法直接访问 NAS 上的服务。
最初的访问方式涉及 NAS 的局域网 IP、端口和 Nginx Proxy Manager。后来排查发现,路由器 WAN IPv4 地址属于私有地址段,普通的公网端口转发方案并不适合当前网络环境。
因此,我决定使用 Cloudflare Tunnel,让 NAS 主动连接 Cloudflare,再通过公开域名访问 Vaultwarden。
本文记录完整的配置流程、工作原理和故障排查方法。
安全提示:本文使用
vault.example.com作为示例域名。实际操作时,请替换为自己拥有的域名。不要公开 Cloudflare Tunnel Token、Vaultwarden 管理令牌、密码或其他敏感信息。
二、运行环境
本文基于以下环境编写,其他版本的界面和操作可能略有不同。
| 项目 | 配置 |
|---|---|
| NAS | QNAP TS-216-2G |
| 系统 | QTS 5.2.9.3451 |
| Docker Engine | 27.1.2-qnap8 |
| Docker Compose | 2.29.1-qnap2 |
| CPU 架构 | linux/arm64 |
| Vaultwarden 镜像 | vaultwarden/server:1.37.3 |
| Vaultwarden 容器名 | vaultwarden |
| Vaultwarden 宿主机端口 | 8080 |
| Vaultwarden 容器内部端口 | 80 |
| Docker 网络 | vaultwarden_default |
| 外网访问方案 | Cloudflare Tunnel |
假设 Vaultwarden 已经部署完成,能够在局域网中正常打开。本文主要讲解如何在现有部署上增加 Cloudflare Tunnel,而不是从零安装 Vaultwarden。
三、理解整体架构
首先,需要理解整个请求是如何从手机到达 NAS 上的 Vaultwarden 的。
yaml
手机 / 浏览器
|
| HTTPS
v
Cloudflare 云端
|
| 已建立的 Cloudflare Tunnel
v
NAS 上的 cloudflared 容器
|
| HTTP: vaultwarden:80
v
NAS 上的 Vaultwarden 容器
整个过程涉及三个关键部分。
1. Cloudflare 云端
负责接收访问公开域名的请求,并通过已经建立的 Tunnel 将请求转发给 NAS 上的连接器。
2. cloudflared 容器
运行在 NAS 上,主动与 Cloudflare 建立连接。收到转发请求后,根据配置将请求发送到指定的内部服务。
3. Docker 网络
让 cloudflared 容器能够通过容器名称找到 Vaultwarden,而不必依赖固定的容器 IP 地址。
需要注意,Cloudflare Tunnel 并不是直接连接 Vaultwarden 容器。实际的内部访问过程是:
Cloudflare → cloudflared → Docker 网络 → Vaultwarden
四、第一步:确认 Vaultwarden 已正常运行
通过 SSH 登录 QNAP NAS,执行:
swift
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"
确认输出中存在 Vaultwarden 容器。
例如:
bash
NAMES IMAGE PORTS
vaultwarden vaultwarden/server:1.37.3 0.0.0.0:8080->80/tcp
这里有两个不同的端口:
8080:NAS 上的宿主机端口。80:Vaultwarden 容器内部的 HTTP 服务端口。
这两个端口不能混为一谈。
在本文的方案中,Cloudflare Tunnel 与 Vaultwarden 通过 Docker 网络直接通信,因此服务目标使用容器内部的 80 端口,而不是 NAS 的 8080 端口。
五、第二步:在 Cloudflare 创建 Tunnel
进入 Cloudflare 控制台,找到 Zero Trust 中的 Networks / Tunnels 页面。
不同版本的 Cloudflare 控制台,菜单名称可能有所不同。
创建一个新的 Tunnel,选择 Docker 作为连接器的运行方式。
创建完成后,Cloudflare 会提供一条运行 cloudflared 的 Docker 命令,其中包含 Tunnel Token。
Token 用于让本地连接器认证并加入对应的 Tunnel。它相当于敏感凭据,不能随意公开。
六、第三步:在 QNAP 上运行 cloudflared 容器
由于本方案需要让 cloudflared 直接访问 Vaultwarden,所以除了运行容器,还需要将它加入 Vaultwarden 所在的 Docker 网络。
先确认网络名称:
bash
docker network ls
假设现有网络为:
vaultwarden_default
可以使用以下命令启动连接器:
css
docker run -d \
--name cloudflared \
--restart unless-stopped \
--network vaultwarden_default \
cloudflare/cloudflared:latest \
tunnel --no-autoupdate run --token '<TUNNEL_TOKEN>'
将 <TUNNEL_TOKEN> 替换为 Cloudflare 实际提供的 Token。不要将真实 Token 写入博客。
各参数的作用如下:
| 参数 | 作用 |
|---|---|
-d |
让容器在后台运行 |
--name cloudflared |
指定容器名称 |
--restart unless-stopped |
在容器异常退出或 Docker 重启后自动尝试启动 |
--network vaultwarden_default |
将容器加入 Vaultwarden 所在的 Docker 网络 |
tunnel --no-autoupdate run |
运行 Tunnel 连接器 |
--token |
提供 Tunnel 的认证凭据 |
如果已经在 Cloudflare 页面创建并运行了连接器,不要重复创建同名容器。应当检查现有容器,并确认它是否已经加入正确的网络。
另外,Docker 容器的启动参数可能包含 Token。具有 Docker 管理权限的人可能通过容器检查命令读取相关参数,因此不要把 Docker 管理权限随意授予其他用户。
七、第四步:确认两个容器位于同一个 Docker 网络
分别执行:
arduino
docker inspect -f '{{json .NetworkSettings.Networks}}' vaultwarden
arduino
docker inspect -f '{{json .NetworkSettings.Networks}}' cloudflared
在我的实际配置中,两个容器都加入了:
vaultwarden_default
当时的容器 IP 分别为:
makefile
vaultwarden: 172.29.0.3
cloudflared: 172.29.0.5
这些 IP 只是当时的运行结果,重建容器后可能改变。
真正需要确认的是:两个容器都位于同一个 Docker 网络中,并且 Vaultwarden 可以通过 vaultwarden 这个网络名称解析。
Docker 提供内部 DNS 服务,因此同一网络中的容器可以通过服务名称或网络别名访问彼此。
例如:
arduino
http://vaultwarden:80
这里的 vaultwarden 不是公网域名,而是 Docker 网络内用于定位容器的名称。
八、第五步:确认 Tunnel 已成功连接
在 Cloudflare 控制台中查看 Tunnel 状态。
当页面显示:
arduino
Tunnel connected successfully
说明连接器已经成功连接到 Cloudflare。
也可以在 NAS 上查看容器日志:
bash
docker logs --tail 50 cloudflared
如果需要持续观察日志:
docker logs -f cloudflared
按 Ctrl+C 可以退出持续查看日志,不会停止容器。
需要注意:Tunnel 已连接,不代表 Vaultwarden 一定能够访问。连接器与 Cloudflare 云端之间的连接,以及连接器与内部应用之间的连接,是两个不同的环节。
九、第六步:配置公开应用路由
这是整个过程最重要的配置之一。
进入 Cloudflare Zero Trust:
- 打开 Networks。
- 进入 Tunnels。
- 选择已经创建的 Tunnel。
- 点击 Routes。
- 点击 Add route。
- 选择 Published application。
注意不要选择 Private hostname 或 Private CIDR,因为本文需要的是公开网站访问,而不是私有网络路由。
添加公开应用路由。
假设使用的域名为 vault.example.com,则配置如下:
| 配置项 | 示例 |
|---|---|
| Hostname | vault.example.com |
| Path | 留空或使用默认值 |
| Service Type | HTTP |
| Service URL | vaultwarden:80 |
如果界面将主机名拆分成两个字段,则分别填写子域名和域名。
保存路由。
Cloudflare 会根据路由配置处理对应的公开域名和 Tunnel 转发。保存后,应检查 DNS 中是否存在正确指向 Tunnel 的记录,并确认没有冲突的旧记录。
特别注意:如果之前存在指向 192.168.x.x 之类私有 IP 地址的 DNS 记录,就需要检查它是否与新路由冲突。不要在 Tunnel 尚未验证成功前,随意删除现有记录。
十、第七步:正确填写服务协议
Cloudflare 公开域名使用 HTTPS,并不意味着 Tunnel 到 NAS 内部的服务也必须使用 HTTPS。
在本方案中,正确配置是:
arduino
公开访问:
https://vault.example.com
内部服务:
http://vaultwarden:80
Cloudflare 负责外部 HTTPS 连接,而 Tunnel 将请求转发到 Docker 网络中的 Vaultwarden HTTP 服务。
这里的内部 HTTP 流量不应被误认为是公网明文访问。它发生在 NAS 的 Docker 网络内部。不过,如果内部网络存在其他不可信容器或访问者,仍需考虑相应的隔离与安全措施。
十一、第八步:排查 Bad Gateway
我在实际配置中遇到了 Bad Gateway。排查日志后发现,原因并不是 Tunnel 没有连接,也不是两个容器不在同一个网络,而是服务协议填写错误。
错误配置为:
arduino
https://vaultwarden:80
日志中出现:
sql
tls: first record does not look like a TLS handshake
这条信息说明,连接器正在尝试使用 TLS/HTTPS 连接目标服务,但目标端口提供的并不是 TLS 服务。
Vaultwarden 容器的 80 端口在当前配置中提供 HTTP 服务,因此正确地址应该是:
arduino
http://vaultwarden:80
修改方法:
- 返回 Cloudflare Tunnel 的 Routes。
- 找到对应的 Published application。
- 编辑 Service URL。
- 将
https://vaultwarden:80改为http://vaultwarden:80。 - 保存并重新测试。
修正后,https://vault.example.com 可以正常访问 Vaultwarden。
这个故障说明:即使 Tunnel 显示连接成功,内部服务协议、端口或目标地址配置错误,仍然会导致 Bad Gateway。
十二、第九步:从手机移动网络验证
配置完成后,进行一次真正的外网测试。
-
关闭手机 Wi-Fi。
-
关闭 Tailscale。
-
确认手机使用移动数据。
-
在手机浏览器打开:
arduinohttps://vault.example.com -
确认出现 Vaultwarden 登录页面。
-
打开 Bitwarden / Vaultwarden 兼容客户端,设置自托管服务器地址为:
arduinohttps://vault.example.com -
登录并测试同步。
不要在手机 App 的服务器地址中添加旧的端口 18443。通过 Cloudflare Tunnel 使用标准 HTTPS 域名时,通常不需要在公开地址中添加端口号。
如果浏览器可以访问,但 App 无法登录或同步,应进一步检查客户端服务器地址、Vaultwarden 的 DOMAIN 配置,以及应用自身的日志。
十三、第十步:检查 Vaultwarden 的 DOMAIN 配置
Vaultwarden 应当使用与实际公开访问地址一致的 HTTPS 域名。
例如:
ini
DOMAIN=https://vault.example.com
不过,修改前需要检查现有部署方式。某些配置文件可能会覆盖 Compose 中设置的环境变量。
如果当前登录、同步和浏览器扩展都正常,可以先备份配置,再确认是否需要调整 DOMAIN。不要为了修改域名而直接删除数据目录或重建整个应用。
同时,建议不要无必要地向公网暴露 Vaultwarden 管理界面,也不要在不需要注册的情况下长期开放用户注册。
十四、是否还需要 Nginx Proxy Manager?
如果以前使用 Nginx Proxy Manager 的 18443 → 443 端口映射访问 Vaultwarden,那么在新的 Cloudflare Tunnel 路由已经正常工作的情况下,Vaultwarden 的外部访问不再依赖这条旧路径。
但是,不应该仅凭 Vaultwarden 已经正常访问,就直接删除整个 Nginx Proxy Manager 容器。
它可能还用于个人网站或其他反向代理规则。正确做法是先检查所有 Proxy Hosts 和其他应用的依赖,再决定是否删除旧规则或端口映射。
Cloudflare Tunnel 与 Nginx Proxy Manager 可以共存,并不是必须二选一。
十五、总结
本次部署中,真正起作用的是三个部分:
- Cloudflare Tunnel:建立 NAS 与 Cloudflare 云端之间的连接。
- Published application 路由:把公开域名映射到内部应用地址。
- Docker 网络 :让
cloudflared通过容器名称找到 Vaultwarden。
最终的访问链路为:
arduino
手机
→ https://vault.example.com
→ Cloudflare
→ cloudflared
→ http://vaultwarden:80
→ Vaultwarden
这套方案不需要直接向公网开放 NAS 的入站端口,也不要求手机开启 Tailscale。
最值得记住的经验是:Tunnel Connected 只代表连接器已连接 Cloudflare,并不代表内部服务已经配置正确。 当出现 Bad Gateway 时,应同时检查服务地址、协议、端口和 Docker 网络,而不是立即重建容器。
完成后,建议再验证容器重启后的连接状态、Vaultwarden 备份和恢复流程,并确保所有 Token 与密码都没有出现在公开博客中。