家庭 NAS 远程访问终极方案:Docker + Cloudflare Tunnel 实战教程
前言:为什么写这篇文章
家里的 NAS 跑了一堆 Docker 服务——WordPress 博客、Homepage 导航页、Aria2 下载器、微信文章采集器、直播数据工具……在家用着很爽,但一出门就全断了。
想从外面访问?传统方案是端口转发 + DDNS。但现实很骨感:
- 运营商封了 80 和 443 端口,HTTP/HTTPS 直接死路一条
- 公网 IP 虽然有,但只有一个非标端口勉强能通
- DDNS + 非标端口的方式,每个服务都要记一个
域名:端口,丑且难用 - STUN 穿透不稳定,连着连着就断了
折腾了一圈后,我找到了一个零成本、零端口依赖、自动 HTTPS的方案:Cloudflare Tunnel。
本文记录完整的部署过程、踩过的坑、以及最终的架构设计。
一、架构概览
互联网用户
│
▼
Cloudflare Edge(自动 HTTPS + CDN)
│
▼ Cloudflare Tunnel(加密隧道)
│
NAS 上的 cloudflared 客户端
│
├── wp.example.com → WordPress (端口 8080)
├── home.example.com → Homepage (端口 3000)
├── sync.example.com → 微信文章采集 (端口 9000)
└── ...更多服务
核心思路:不需要打开任何入站端口。cloudflared 从 NAS 内部主动向 Cloudflare 建立出站连接(就像你打开一个网页一样),然后 Cloudflare 通过这条隧道把外部请求转发到内网服务。
优势:
- ✅ 不依赖任何端口:运营商封 80、443、甚至全封都没关系
- ✅ 自动 HTTPS:Cloudflare 边缘节点自动提供证书,零配置
- ✅ 标准域名访问:wp.example.com 而不是 nas.example.com:12345
- ✅ 零成本:Cloudflare Tunnel 免费额度完全够用
- ✅ 安全:NAS 没有任何端口暴露在公网上
二、准备工作
你需要的东西
| 项目 | 说明 |
|---|---|
| 一台 NAS | 能跑 Docker 就行。我用的是群晖 + Lucky 管理面板 |
| 一个域名 | 需要托管在 Cloudflare(免费)。我用 example.com |
| Cloudflare 账号 | 免费注册即可 |
| Docker 服务 | 你想从外面访问的任何服务 |
我的环境
- NAS IP:
192.168.x.x(内网地址) - 管理面板:Lucky 2.26.2(内置 Docker 管理和 cloudflared 模块)
- 已有服务:WordPress(:8080)、Homepage(:3000)、微信文章采集(:9000) 等
- 域名:
example.com,DNS 托管在 Cloudflare
三、部署步骤
第 1 步:在 Cloudflare 创建 Tunnel
- 登录
- 进入 Networks → Tunnels
- 点击 Create a tunnel
- 选择 Cloudflared 类型
- 给 Tunnel 起个名字,比如
nas-tunnel - 创建后会生成一个 Token(很长的一串 Base64 字符串),保存好
也可以用 API 创建(适合自动化):
curl -X POST "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/cfd_tunnel" \
-H "Authorization: Bearer {API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"name":"nas-tunnel","tunnel_secret":"YOUR_32BYTE_SECRET_BASE64"}'
第 2 步:配置 Tunnel 路由
告诉 Tunnel 把哪些域名转发到哪些内网服务:
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/cfd_tunnel/{TUNNEL_ID}/configurations" \
-H "Authorization: Bearer {API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"config": {
"ingress": [
{"hostname": "wp.example.com", "service": "http://192.168.x.x:8080"},
{"hostname": "home.example.com", "service": "http://192.168.x.x:3000"},
{"hostname": "sync.example.com", "service": "http://192.168.x.x:9000"},
{"service": "http_status:404"}
]
}
}'
注意:最后一条 http_status:404 是 catch-all 规则,必须有,否则 cloudflared 会报错。
第 3 步:配置 DNS
为每个子域名创建 CNAME 记录,指向 Tunnel 地址:
# 格式:{TUNNEL_ID}.cfargotunnel.com
# 必须开启 Cloudflare 代理(橙色云朵)
curl -X POST "https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/dns_records" \
-H "Authorization: Bearer {API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "wp",
"content": "YOUR-TUNNEL-ID.cfargotunnel.com",
"proxied": true
}'
对每个子域名(wp、home、wpsync……)重复这一步。
第 4 步:在 NAS 上部署 cloudflared
这一步有两种方式:
方式 A:用 Lucky 内置模块(推荐)
如果你的 NAS 用 Lucky 管理面板:
- 进入 Lucky → 内网穿透 → Cloudflared
- 填入上一步获得的 Token
- 点击启动
Lucky 会自动管理 cloudflared 进程,开机自启,断线重连。
方式 B:Docker 部署
docker run -d --name cloudflared \
--restart always \
cloudflare/cloudflared:latest \
tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN
方式 C:Docker Compose
version: "3.9"
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
command: tunnel --no-autoupdate run --token YOUR_TUNNEL_TOKEN
restart: always
第 5 步:验证
# 检查 WordPress
curl -sI https://wp.example.com
# 应该返回 HTTP/2 200
# 检查 Homepage
curl -sI https://home.example.com
# 应该返回 HTTP/2 200 或 307(跳转到登录页)
在浏览器里直接打开 https://wp.example.com,看到 WordPress 页面就成功了!🎉
四、踩坑记录
坑 1:端口写错,页面打不开
症状:配置了 Tunnel 但页面一直加载不出来。
原因:我一开始把 Homepage 的端口写成了 XX(Lucky 管理面板的端口),实际应该是 3000(Homepage 服务本身的端口)。
教训:service 里填的是内网服务的真实端口,不是管理面板的端口。先用 curl http://192.168.x.x:端口 在内网确认能通,再填到 Tunnel 配置里。
坑 2:旧 DNS 记录冲突
症状:配置了 CNAME 但域名解析异常。
原因:wp.example.com 之前有一条 A 记录指向旧的公网 IP,和新的 CNAME 冲突了。
教训:添加 Tunnel CNAME 之前,先删掉同名的旧 A 记录。
坑 3:换域名后服务内部配置没跟着改
症状:微信文章采集服务(sync.example.com)能打开 API 文档,但同步文章到 WordPress 时一直超时。
原因:这个服务的环境变量 WP_URL 被设成了 https://sync.example.com(它自己的地址),而不是 WordPress 的地址 https://wp.example.com。它在尝试向自己发 XML-RPC 请求,当然 404。
报错信息:
ERROR - WP: XML-RPC 发布失败: <ProtocolError for sync.example.com/xmlrpc.php: 404 Not Found>
教训:换域名后,不只要改 Tunnel 和 DNS,还要检查所有服务内部的配置文件和环境变量中是否引用了旧域名。特别是服务之间互相调用的地址。
最佳实践:服务间互相调用尽量用内网 IP(如
http://192.168.x.x:8080),而不是走公网域名绕一圈。走内网更快、更稳、不受 Tunnel 状态影响。
坑 4:cloudflared 配置更新不即时生效
症状:在 Cloudflare 后台更新了 Tunnel 路由(新增了 sync.example.com),但访问一直返回 404。
原因:NAS 上的 cloudflared 实例是在几小时前建立连接的,新的路由配置需要等 cloudflared 下一次心跳拉取。
解决:等几分钟自动生效,或者重启 cloudflared 容器/服务强制拉取最新配置。
坑 5:Lucky GUI 的 CodeMirror 编辑器无法自动化
症状:想通过浏览器自动化(Playwright)在 Lucky 的 Docker Compose 编辑器里填写 YAML,但怎么都注入不进去。
原因:Lucky 2.26.2 的 YAML 编辑器使用了 CodeMirror(contentEditable div),标准的 DOM InputEvent、clipboard paste 操作都不生效,cmView 属性也不暴露。
教训:遇到这类富文本编辑器,放弃 GUI 自动化,直接 SSH 进终端操作更快更稳。
五、进阶:重流量服务怎么办?
Cloudflare Tunnel 非常适合轻量级 Web 服务(博客、导航页、API 等),但对于重带宽场景(远程看电影、大文件传输),有两个限制:
- 带宽瓶颈:所有流量都经过 Cloudflare 边缘节点中转
- ToS 限制:Cloudflare 免费版不鼓励大量非 HTML 内容的代理
推荐方案:Tailscale
对于重流量需求,建议在 NAS 上额外部署 Tailscale:
version: "3.9"
services:
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale
hostname: nas-tailscale
network_mode: host
privileged: true
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- TS_STATE_DIR=/var/lib/tailscale
- TS_ACCEPT_DNS=true
- TS_USERSPACE=false
volumes:
- ./tailscale-state:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
restart: always
分工原则:
- Cloudflare Tunnel:轻量 Web 服务、需要公开访问的页面
- Tailscale:看电影、传文件、远程桌面等重流量私密访问
两者互不冲突,可以同时运行。
六、最终架构总结
| 域名 | 内网服务 | 访问方式 | 用途 |
|---|---|---|---|
wp.example.com |
192.168.x.x:8080 |
Cloudflare Tunnel | WordPress 博客 |
home.example.com |
192.168.x.x:3000 |
Cloudflare Tunnel | Homepage 导航 |
sync.example.com |
192.168.x.x:9000 |
Cloudflare Tunnel | 微信文章采集 |
| NAS 内网 IP | 所有端口 | Tailscale P2P | 重流量/私密访问 |
成本
- Cloudflare Tunnel:免费
- Tailscale:免费(个人使用 100 台设备额度)
- 域名:约 ¥50/年
- 总计:几乎零成本
安全性
- NAS 零端口暴露在公网上
- 所有 Web 流量自动 HTTPS(Cloudflare 边缘证书)
- 私密服务走 Tailscale 端到端加密
- 比传统端口转发 + DDNS 安全得多
七、写在最后
折腾 NAS 远程访问这件事,我走过不少弯路:DDNS + 端口转发 → STUN 穿透 → frp/nps 内网穿透 → 最终落地到 Cloudflare Tunnel + Tailscale。
这套方案的核心优势是简单和稳定:
- 不需要公网 IP
- 不需要端口转发
- 不需要自建中转服务器
- 不需要折腾证书
- 运营商随便封端口,完全不受影响
如果你也在为 NAS 远程访问发愁,强烈推荐试试这个方案。有问题欢迎留言交流。

评论