QNAP NAS 部署 Vaultwarden:使用 Cloudflare Tunnel 实现外网访问与手机同步

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:

  1. 打开 Networks。
  2. 进入 Tunnels。
  3. 选择已经创建的 Tunnel。
  4. 点击 Routes。
  5. 点击 Add route。
  6. 选择 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

修改方法:

  1. 返回 Cloudflare Tunnel 的 Routes。
  2. 找到对应的 Published application。
  3. 编辑 Service URL。
  4. 将 https://vaultwarden:80 改为 http://vaultwarden:80。
  5. 保存并重新测试。

修正后,https://vault.example.com 可以正常访问 Vaultwarden。

这个故障说明:即使 Tunnel 显示连接成功,内部服务协议、端口或目标地址配置错误,仍然会导致 Bad Gateway。

十二、第九步:从手机移动网络验证

配置完成后,进行一次真正的外网测试。

  1. 关闭手机 Wi-Fi。

  2. 关闭 Tailscale。

  3. 确认手机使用移动数据。

  4. 在手机浏览器打开:

    arduino 复制代码
    https://vault.example.com
  5. 确认出现 Vaultwarden 登录页面。

  6. 打开 Bitwarden / Vaultwarden 兼容客户端,设置自托管服务器地址为:

    arduino 复制代码
    https://vault.example.com
  7. 登录并测试同步。

不要在手机 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 可以共存,并不是必须二选一。

十五、总结

本次部署中,真正起作用的是三个部分:

  1. Cloudflare Tunnel:建立 NAS 与 Cloudflare 云端之间的连接。
  2. Published application 路由:把公开域名映射到内部应用地址。
  3. Docker 网络 :让 cloudflared 通过容器名称找到 Vaultwarden。

最终的访问链路为:

arduino 复制代码
手机
  → https://vault.example.com
  → Cloudflare
  → cloudflared
  → http://vaultwarden:80
  → Vaultwarden

这套方案不需要直接向公网开放 NAS 的入站端口,也不要求手机开启 Tailscale。

最值得记住的经验是:Tunnel Connected 只代表连接器已连接 Cloudflare,并不代表内部服务已经配置正确。 当出现 Bad Gateway 时,应同时检查服务地址、协议、端口和 Docker 网络,而不是立即重建容器。

完成后,建议再验证容器重启后的连接状态、Vaultwarden 备份和恢复流程,并确保所有 Token 与密码都没有出现在公开博客中。

相关推荐
嘿嘿嘿9151 小时前
Java 第四周学习笔记:多态、接口、内部类与 String
后端
彩雲演文1 小时前
51单片机万年历Proteus仿真跑通实录:LCD12864B+DS1302+DHT11,带农历和闹钟(效果图+接线表+全套源码)
后端
ThinkerQAQ_1 小时前
并发编程(十):BlockingQueue 与 Channel 的实现——从 Runtime 到 CPU
后端
霂鳯1 小时前
新手勇闯网络安全 | PHP基础(四)
后端
海岳云舟1 小时前
程序常驻后台运行的原理和方法
后端·全栈
彩雲演文1 小时前
Proteus仿真LCD12864B不显示/黄屏?别急着改接线,真凶是BusyCheck死循环(51单片机实战排查)
后端
Rain的Java大神之路2 小时前
Java 三层架构,90% 的人只答对一半:Controller 不是瘦,Service 不是透传,Dao 不是只写 SQL
java·后端·面试
小羊没烦恼!2 小时前
浅谈SQL Server中的事务日志(二)----事务日志在修改数据时的角色
前端·数据库·后端·python·web安全·oracle
IT_陈寒3 小时前
一文搞懂Vite的核心概念
前端·人工智能·后端