返回文章索引
TRANSMISSION / VIOLET27 分钟阅读

Lucky STUN + Cloudflare 302 实战:用域名跟随动态公网端口

通过 Lucky STUN 获取公网映射,再用 Webhook 更新 Cloudflare Single Redirect,让用户从固定域名以 302 临时跳转到最新 IP 与端口;同时讲清 303 限制、TLS、App 兼容性与 Tunnel 替代方案。

#Lucky#STUN#Cloudflare Redirect Rules#Cloudflare Tunnel#Homelab

这篇文章介绍的重点不是普通 DDNS,也不是让 Cloudflare 反向代理 STUN 的动态端口,而是下面这条组合链路:Lucky 通过 STUN 获得 公网 IP:动态端口,映射改变时再由 Webhook 修改 Cloudflare Single Redirect 的目标地址。

用户只需要记住 go.example.com。浏览器先访问 Cloudflare,Cloudflare 返回一次临时重定向,再由浏览器直接连接最新的 STUN 地址。这与参考文章的 301 方案是同一种思路,但本文把状态码改为 302:NAT 映射本来就是临时值,不应该让浏览器或搜索引擎把某个 IP 和端口当作永久地址。

本文基于 Lucky 2.27.2 与 Cloudflare Dashboard 的实际界面,并用双方官方文档校正能力边界。正文示例只使用 example.com192.0.2.0/24 等文档值;实拍配图中的真实域名、IP、端口、实例名称、流量和 Token 均未展示,存在后台数据的区域已做不可逆强模糊处理。

先给结论:浏览器访问的临时入口使用 302;不要使用 301。Cloudflare Single Redirect 当前正式支持 301、302、307、308,不支持 303。如果是 App、API、远程桌面或非 HTTP 协议,这种跳转通常不合适,应改用 307、Cloudflare Tunnel、VPN 或其他原生传输方案。

先看清这条链路

第一次请求

浏览器访问 https://go.example.com/photos?id=42
  └─ Cloudflare Single Redirect 返回 302
       Location: http://203.0.113.10:45678/photos?id=42
        └─ 浏览器直接连接 STUN 映射
             └─ Lucky 内置转发(或路由器端口转发)
                  └─ 内网 Web 服务

映射变化

Lucky 得到 203.0.113.10:51234
  └─ STUN Webhook PATCH Cloudflare Rulesets API
       └─ 下一次访问收到新的 Location

这里的“纯域名访问”只表示用户输入的起始地址不带端口。跳转完成后,浏览器地址栏仍会显示真实 IP 或目标域名及端口。Cloudflare 只处理第一次请求,后续业务流量不会经过 Cloudflare,因此 WAF、缓存、Access、隐藏源站等能力不会覆盖第二段连接。

方案用户入口第二段流量适合场景
STUN 直连IP/域名加动态端口访问者直连家庭网络TCP/UDP 或明确知道端口的客户端
STUN + Cloudflare 302固定 HTTPS 域名,随后显示动态地址访问者直连家庭网络浏览器 GET 页面、临时入口、能接受暴露地址
Cloudflare Tunnel固定 HTTPS 域名经 Cloudflare TunnelWeb、API、管理后台、需要 Access/WAF 的服务

第一条路径:配置 Lucky STUN

1. 先判断网络条件

Lucky 的 STUN 官方文档把适用环境描述为 NAT1,并明确提醒公网端口无法保证固定、变化没有规律。不同工具对 NAT 类型的命名可能不完全一致,所以不要只凭路由器页面上的“NAT 类型”文字判断,应以实际穿透日志和外网测试为准。

开始前依次检查:

  1. 如果是光猫拨号,按 Lucky 文档把光猫 DMZ 指向主路由;如果是路由器拨号,则跳过这一步。
  2. 最省事的部署位置是主路由。Lucky 位于普通局域网主机时,还要处理主路由到 Lucky 的端口映射、UPnP 或 DMZ。
  3. 确认系统内核和防火墙允许所需的 TCP/UDP 行为;代理软件、双重 NAT 和多 WAN 都可能改变结果。
  4. 不要把 Lucky 管理后台本身作为第一条公网测试规则。先选择一个无敏感数据、可以随时关闭的测试服务。

2. 开启 STUN 模块

进入:

Lucky → 内网穿透 → STUN 内网穿透 → 设置

打开 STUN 模块,并检查“全局 STUN 服务器列表”。Lucky 界面会给出一个公开的可用 STUN 服务器列表作为参考。服务器越多并不一定越稳定;应保留网络可达、经过测试的条目,不要把来源不明的地址批量粘贴进生产配置。

实拍界面;画面只包含公开 STUN 服务器地址,没有私有网络地址或访问凭据。

Webhook 会把公网映射变化发送给外部系统。只有确实需要自动化时才启用,并确保 URL、请求头和消息体中的密钥不会出现在日志或截图里。

3. 用简易模式添加第一条规则

进入“穿透规则列表”并选择“添加穿透规则”。第一次测试建议使用简易模式。

表单保持空白;背景中的真实规则、IP、端口和流量统计已做不可逆强模糊。

字段建议
规则名称使用不暴露设备型号、位置或业务名称的内部备注
穿透类型根据服务选择 IPv4-TCP 或 IPv4-UDP;一条规则只处理一种协议
穿透通道本地端口手动填写时使用未占用且全局唯一的端口;Lucky 文档建议大于 10240。配合 UPnP 时可按文档使用 0 让 Lucky 选择端口
防火墙自动放行仅在 Lucky 运行于支持其防火墙控制的路由器、且你理解规则变化时开启
UPnP / NAT-PMPLucky 位于局域网主机且主路由已启用相应能力时使用;不用时关闭,避免扩大自动映射范围
不使用 Lucky 内置端口转发第一次测试保持关闭。确认穿透有效后,再评估用路由器转发换取更高性能
禁用通道有效性检测保持关闭,它是调试选项
目标地址填内网 IP 或主机名,不要带 http://https://
目标端口填服务实际监听端口;使用 Lucky 内置转发时必填

一个完全虚构的 TCP 示例:

穿透类型:IPv4-TCP
穿透通道本地端口:12080
目标地址:192.0.2.10
目标端口:8080

这里的 192.0.2.10:8080 使用 RFC 5737 文档保留地址,只是教程占位符。发布文章、截图或求助日志前,应把真实内网地址与端口一起打码;二者组合往往足以识别某项服务。

4. 定制模式先不要“调参优化”

Lucky 2.27.2 的定制模式包含指定 IP/网卡、IP 黑白名单、TCP 连接数、UDP 会话数、加密、STUN 等待时间、心跳间隔、重试间隔和日志级别等选项。这些参数互相关联,不应因为看到默认值就照搬到其他网络。

只有出现可复现问题时才逐项调整:

  • 多 WAN 或策略路由环境,才指定穿透通道 IP 或网卡;
  • 连接数与 UDP 会话数根据服务并发和设备资源设上限;
  • 等待、心跳、重试参数一次只改一项,并保留前后日志;
  • “穿透失败自动重试”可以提高可用性,但也要避免错误配置持续制造请求;
  • TLS 或流加密不能替代应用自身的认证和授权。

5. 用真正的外网验证

保存后先看该规则的独立日志,再关闭 Wi-Fi,用移动网络或另一条宽带访问。至少验证:

  1. Lucky 是否获得公网映射;
  2. 访问时日志是否出现连接;
  3. 防火墙或路由器是否把流量送到正确的 Lucky 端口;
  4. 内网服务是否监听了正确地址,而不是只监听另一个网络命名空间;
  5. 公网端口变化后,客户端或自动化能否感知新地址。

如果日志完全没有访问记录,先查运营商 NAT、光猫/路由器映射和防火墙,而不是反复修改目标服务。

核心方案:用 Cloudflare 302 跟随 STUN 动态端口

STUN 给出的是“公网 IP + 公网端口”。A/AAAA 记录只能保存 IP,不能把普通浏览器 URL 的端口一起动态更新。参考方案巧妙之处在于:DNS 只负责让固定入口到达 Cloudflare,真正的 IP:端口 写在重定向规则的 Location 里,并由 Lucky Webhook 持续更新。

1. 为什么选 302,而不是 301 或 303

状态码语义与方法行为是否适合本文
301永久移动;客户端和搜索引擎可能长期记住目标不适合动态 NAT 映射
302临时移动;常见浏览器适合用它处理 GET 页面推荐作为浏览器入口
303明确要求后续使用 GET,常用于 POST 后查看另一资源Single Redirect 当前不可选
307临时移动,并要求保留原请求方法和请求体App/API 确需保留方法时评估
308永久移动并保留方法不适合动态 NAT 映射

Cloudflare 的 Single Redirect 设置文档当前列出的状态码只有 301、302、307、308。Cloudflare 的 3xx 说明页介绍了 303 的 HTTP 语义,但这不代表 Rulesets API 的 http_request_dynamic_redirect 动作接受 303。把下面 JSON 的 status_code 改成 303 很可能会被 API 校验拒绝。

如果业务必须返回 303,需要由 Cloudflare Worker 或自己的源站生成该响应;那已经不是本文这套 Single Redirect 配置。对于普通浏览器 GET 页面,302 更直接。对于必须保留 POST/PUT 请求体的 API,优先重新评估架构,而不是盲目把 302 换成 307。

2. 准备两个不同角色的域名

建议把入口和直连目标分开:

域名Cloudflare 状态用途
go.example.com已代理(橙云)用户输入的固定入口,触发 Redirect Rule
origin-direct.example.com仅 DNS(灰云,可选)希望目标仍使用域名和有效 TLS 证书时,由 Lucky DDNS 更新 IP

Cloudflare 官方明确要求 Single Redirect 所匹配的入口主机名必须经过代理。因此 go.example.com 必须是橙云记录。规则命中后 Cloudflare 直接返回 302,不会请求这个记录指向的源站;但未命中的路径可能继续回源,所以生产环境应让规则覆盖完整主机名,并为漏网请求设计安全失败,而不是指向真实管理后台。

目标可以有两种写法:

最简单: http://公网IP:动态端口/path
更适合 HTTPS: https://origin-direct.example.com:动态端口/path

直接跳到 HTTPS IP 往往会遇到证书主机名不匹配。若需要 HTTPS,推荐让 Lucky DDNS 更新灰云目标域名,并让源站证书包含 origin-direct.example.com。无论采用哪一种,第二段连接都会暴露目标 IP 和端口,也不会得到 Cloudflare WAF 或 Access 保护。

3. 在 Cloudflare 创建 302 动态重定向

进入:

Cloudflare Dashboard → 目标 Zone → Rules → Redirect Rules
→ Create rule → Single Redirects

先建立一条占位规则:

规则名:Lucky STUN temporary redirect
匹配表达式:(http.host eq "go.example.com")
类型:Dynamic
目标表达式:concat("http://192.0.2.10:12080", http.request.uri.path)
状态码:302
保留查询字符串:开启

192.0.2.10 是文档保留地址。占位规则只是为了让 Cloudflare 创建规则集和规则 ID,不应拿它做真实连通性测试。部署后先用 curl -I 确认返回状态是 302,并检查 Location 是否保留路径与查询参数。

4. 创建最小权限 API Token

为 Lucky 单独创建 Token:

Permissions:Zone → Single Redirect → Edit
Resources:Include → Specific zone → example.com

不要使用 Global API Key,也不要把 DNS、Workers、账户管理等无关权限一起勾选。这个 Token 能修改入口跳往何处,一旦泄漏可以被用于钓鱼重定向,应按生产密钥管理。

Webhook 还需要三个非密钥标识:ZONE_ID、动态重定向规则集的 RULESET_ID、目标规则的 RULE_ID。可以从 Dashboard 编辑规则时的请求中查看,也可以调用 Rulesets API 列出 Zone 规则集,查找 phase 为 http_request_dynamic_redirect 的条目,再读取其中规则。截图或文章仍建议统一打码这些标识。

5. 在 Lucky 规则里配置 Webhook

编辑对应 STUN 穿透规则,开启 Webhook。不要使用 Dashboard 的内部 /api/v4/ 地址,应调用公开 API:

请求地址:
https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/rulesets/<RULESET_ID>/rules/<RULE_ID>

请求方法:PATCH

请求头:
Authorization: Bearer <CLOUDFLARE_API_TOKEN>
Content-Type: application/json

请求体使用 302:

{
  "action": "redirect",
  "description": "Lucky STUN temporary redirect",
  "enabled": true,
  "expression": "(http.host eq \"go.example.com\")",
  "action_parameters": {
    "from_value": {
      "status_code": 302,
      "target_url": {
        "expression": "concat(\"http://#{ipAddr}\", http.request.uri.path)"
      },
      "preserve_query_string": true
    }
  }
}

在 Lucky STUN Webhook 中,#{ipAddr} 会在触发时替换为当前的公网地址与端口,因此生成的 Cloudflare 表达式类似:

concat("http://203.0.113.10:45678", http.request.uri.path)

如果采用“灰云目标域名 + HTTPS”模式,请同时用 Lucky DDNS 更新目标域名,并把目标表达式改为:

"expression": "concat(\"https://origin-direct.example.com:#{port}\", http.request.uri.path)"

#{ipAddr}#{port} 是 Lucky Webhook 模板变量,不是 Cloudflare Rules 语言变量。引号必须是英文半角字符,JSON 中表达式内部的双引号必须保留反斜杠转义。

把 Webhook 的成功判断设置为响应中包含:

"success": true

不同界面或响应序列化可能包含空格,不应仅凭一次手动测试就认定长期有效。最终要同时查看 Lucky Webhook 日志与 Cloudflare API 响应,并重新读取规则确认目标已更新。

6. 从外网验证完整链路

不要只看浏览器“能打开”。用移动网络或另一条宽带分两段验证:

curl -sS -D - -o /dev/null "https://go.example.com/photos?id=42"

预期关键响应:

HTTP/2 302
location: http://203.0.113.10:45678/photos?id=42

接着检查:

  1. 状态码确实是 302,不是仍被旧规则返回 301;
  2. Location 中的 IP、端口、路径和查询字符串正确;
  3. 让 STUN 重新获得映射后,Webhook 是否把规则改成新端口;
  4. 浏览器无痕窗口和另一台设备没有命中过去的永久缓存;
  5. 第二段直连是否具有应用自身的登录、限速、防爆破和 TLS 保护。

测试时使用 curl -I 只能验证 HEAD;部分服务对 HEAD 和 GET 的处理不同。需要完整验证页面时,使用上面的 -D - -o /dev/null 发出 GET,但不要在终端输出私密响应正文。

7. 这套 302 方案明确不解决什么

  • 它不会真正隐藏端口;跳转后地址栏和 Location 都会显示端口。
  • 它不会隐藏家庭公网 IP;访问者会直接连接目标。
  • 它不会让 App、电视客户端、远程桌面或自定义协议自动兼容 HTTP 重定向。
  • 它不会把第二段请求放到 Cloudflare WAF、Cache 或 Access 后面。
  • 它不会自动解决 Cookie Domain、CORS、CSRF、绝对 URL、Host 校验或 HTTPS 证书问题。
  • 它只适合 HTTP/HTTPS 入口;UDP 不可能通过 HTTP 302 重定向建立会话。

因此,Cloudflare Redirect 不是“给 STUN 端口套橙云”。橙云只覆盖固定入口的第一次 HTTP 请求。

替代方案:在 Lucky 中运行 Cloudflare Tunnel

如果目标是 Web、API 或后台管理页面,Cloudflare Tunnel 通常更简单。cloudflared 从内网主动连接 Cloudflare,不需要公网 IP,也不需要在路由器开放入站端口。完整模型可参考 Cloudflare Tunnel 官方说明

1. 在 Cloudflare 创建远程管理隧道

进入 Cloudflare Dashboard:

Networking → Tunnels → Create a tunnel → Cloudflared

给隧道一个不包含家庭地址、设备序列号或个人姓名的名称。创建后,控制台会生成一条安装命令,其中包含 Tunnel Token。Lucky 只需要 Token 本身,不要把整条命令保存到笔记或截图中。

实拍界面;未填写名称、未生成 Token,也没有提交创建操作。账号侧栏处于折叠状态。

Tunnel Token 能让新的 cloudflared 连接到这条隧道,应按密码管理:

  • 不写进 Markdown、Shell 历史、Git、Dockerfile 或公开环境变量;
  • 不放在浏览器端代码;
  • 每条隧道使用独立 Token;
  • 泄漏后立即在隧道详情中刷新 Token,并清理旧连接。Cloudflare 也建议定期轮换 Tunnel Token

2. 在 Lucky 添加 Tunnel 实例

进入:

Lucky → 内网穿透 → Cloudflared → 添加实例

选择“隧道”模式,然后配置:

Token 与 API Token 输入框保持空白;背景中的真实实例信息已做不可逆强模糊。

字段建议
实例备注使用唯一但不泄漏设备或位置的信息
令牌粘贴 Tunnel Token;发布前必须完全打码
边缘网络类型一般使用自动;只有 IPv4/IPv6 链路确有问题时才固定
高可用连接数保持合理数量;更多连接不等于无限可用性
连接协议自动、HTTP/2、QUIC 均需结合网络;UDP 受限时优先测试 HTTP/2
边缘绑定 IP单网卡环境留空;多 WAN 定向出口时再配置
不验证后端 TLS 证书生产环境应关闭。优先填写正确的源服务器名或部署可信 CA

如果希望 Lucky 自动管理应用路由和 DNS,再提供一个单独的 Cloudflare API Token。Lucky 当前界面要求:

  • Account → Cloudflare Tunnel → Edit;
  • Zone → DNS → Edit;
  • 账户和 Zone 均缩小到实际使用范围。

不需要自动管理时可以不填 API Token,改在 Cloudflare Dashboard 手动维护路由和 DNS。最小权限永远比“为了省事选 All”更安全。

3. 添加应用路由

在 Tunnel 实例右侧进入“应用路由”,添加:

主机名:app.example.com
路径:/*
目标服务:http://192.0.2.10:8080

目标服务必须包含协议。HTTP、HTTPS、TCP 等值的含义可以查阅 Cloudflare Tunnel 路由文档。对于普通浏览器访问,优先使用 HTTP/HTTPS 应用路由;非 HTTP 公共主机名通常要求客户端也运行 cloudflared,任意 TCP/UDP 公网代理则可能需要 Spectrum 或私网路由方案。

当一条主机名有多条路径规则时,把具体规则放在通配规则之前。例如 /api/* 应先于 /*,否则前面的宽泛规则可能提前匹配。

高级选项只在需要时使用:

  • 源服务器名:源站证书上的主机名与目标地址不一致时填写;
  • Host 请求头:源站按虚拟主机分流时覆盖;
  • HTTP/2 连接:只有源站确实支持时开启;
  • 连接、TLS、保活超时:依据日志和服务特征调整;
  • 不验证后端 TLS 证书:只能作为临时排错手段。Cloudflare 官方也把 noTLSVerify 定义为生产环境不推荐的最后手段。

4. 配置 CNAME

每条 Tunnel 会得到形如下面的目标:

<TUNNEL_ID>.cfargotunnel.com

app.example.com 创建 CNAME 指向它。通过 Cloudflare Dashboard 添加 Published application route 时,DNS 记录通常会自动创建;从 Lucky 管理路由时,是否自动写入取决于 API Token 和当前配置。保存后务必回到 DNS 页面确认,而不是假设已经成功。

Tunnel UUID 不是等同于 Token 的认证密钥,但仍然属于基础设施标识。截图中没有展示它的必要,应统一打码。

5. 给管理后台加 Cloudflare Access

Tunnel 只解决“如何到达源站”,不会自动决定“谁可以访问”。管理后台、监控页面和私人服务建议再创建 Access 应用:

Zero Trust → Access controls → Applications
→ Create new application → Self-hosted and private

把 Tunnel 的公网主机名加入应用,创建 Allow 策略,并用邮箱、身份提供商、设备状态或 Service Token 限制访问。Cloudflare Access 的策略由 Action、Include、Require、Exclude 等条件组成,详见官方 Access 策略文档

不要在 Access 保护的 URL 中依赖端口号;Cloudflare 的端口文档说明 Access 会移除 URL 里的端口。对外使用标准 HTTPS 主机名,把内网端口只保留在 Tunnel 的目标服务中。

常见故障怎么定位

现象优先检查
STUN 没拿到映射NAT 条件、光猫/路由器拨号位置、双重 NAT、代理软件、内核与防火墙
STUN 有映射但外网无日志公网端口、防火墙、路由器转发、运营商入站限制
STUN 有日志但服务打不开目标地址、目标端口、协议类型、服务监听地址
域名能解析但端口不对A/AAAA 只保存 IP;检查公网端口变化通知或客户端配置
入口仍返回 301Cloudflare 是否还有更靠前的 Redirect/Page Rule;读取当前规则的 status_code
Webhook 返回 API 错误Token 是否有 Single Redirect: Edit、Zone/规则 ID 是否正确、JSON 引号与转义是否完整
302 的 Location 仍是旧端口Lucky 是否触发 Webhook、成功判断是否误判、Cloudflare 规则是否被其他自动化覆盖
浏览器正确、App 无法登录App 是否支持重定向;Host、Cookie、认证回调和 TLS 是否绑定原域名
HTTPS 跳到 IP 后证书报错改用灰云目标域名与匹配的源站证书,或改用 Tunnel;不要关闭客户端证书校验
Tunnel 显示连接但返回 502Lucky 到目标服务的网络可达性、协议、端口和源站监听
HTTPS 源站出现 x509 错误优先设置正确的源服务器名或 CA,不要长期启用跳过验证
Tunnel 域名返回 1016CNAME 是否属于同一 Cloudflare 账户、隧道是否运行、目标 UUID 是否正确
路径访问到错误服务检查路由顺序,让具体路径位于 /* 之前
QUIC 连接失败检查出站 UDP;必要时切换 HTTP/2 对照测试

排错时一次只改一个变量,并保留时间点。日志可以帮助定位,但分享前先删掉公网 IP、内网 IP、端口、域名、请求路径、Cookie、Authorization、Token 和实例备注。

发布截图前的隐私检查清单

Lucky 的规则列表密度很高,一张未处理的截图可能同时暴露整个家庭网络拓扑。发布前逐项检查:

  • Lucky 后台地址、端口与安全入口路径;
  • 公网 IP、映射端口、内网 IP、目标端口;
  • 域名、主机名、实例名、规则名和设备名;
  • Tunnel Token、Cloudflare API Token、Access Service Token;
  • Account ID、Zone ID、Tunnel UUID 与 CNAME 目标;
  • 请求路径、Webhook 地址与请求头;
  • 日志、访问者 IP、流量统计与启用状态;
  • 浏览器标签页、书签栏、账号头像和邮箱。

仅仅模糊 Token 的中间几位还不够。安全做法是用重新绘制的示意图,或者把所有真实值替换为 app.example.com192.0.2.10<TOKEN> 这类占位符。

最后的选择建议

如果只是给自己或少量可信用户提供一个浏览器 GET 页面入口,能够接受跳转后暴露 IP 与端口,并且已经验证 NAT 条件,可以选择 STUN + Cloudflare 302。它解决的是“记住固定入口”,不是反向代理、安全网关或真正的端口隐藏。

如果请求必须保留方法,可在确认客户端行为后评估 307;不要把 303 直接填进 Single Redirect JSON。必须使用 303 时,应由 Worker 或源站生成,并单独设计动态目标存储和鉴权。

如果你要发布的是 Web 页面或 API,并希望地址始终保持标准 HTTPS 域名,同时使用 Access 和 WAF,选择 Cloudflare Tunnel。它不要求 STUN 成功,也不会因为公网映射端口改变而修改访问 URL。

如果你明确需要直接 TCP/UDP、延迟和点对点路径更重要,且网络满足 Lucky 的 STUN 条件,可以选择 STUN;同时接受公网端口可能变化、灰云直连暴露源地址,以及需要自行完成应用认证和防火墙控制。

STUN 直连、STUN + 302 入口和 Cloudflare Tunnel 都能帮助“从外网访问内网”,但只有后者让业务流量经过 Cloudflare。先确定协议、客户端和安全边界,再选择入口方式,通常比照抄一份 301 请求体更可靠。

参考资料

文章结束
READER CHANNEL

留言

00
还没有留言。成为第一个发出回应的人。