应用绑定固定公网域名
把 http://127.0.0.1:3000 变成 https://app.example.com —— 永久固定、自带 HTTPS、开机自启、零成本。
做完的判据只有一个:换台设备、换个网络,直接打开域名就能用,不需要你本机开着终端。
〇、适用与不适用
适用
- 本地已经有跑通的应用(选股面板、内部工具、演示站、自建 API),要给别人一个能直接打开的地址;
- 已有域名,想让某个子域指向本机;
- 域名买了但还没接入 Cloudflare(没改 NS);
- 域名能打开但报 502 / 522 / 证书错误;
- 服务在终端里跑得好好的,一关终端或一重启就没了。
不适用
| 情况 | 为什么 | 改用 |
|---|---|---|
| 应用本身还没跑通 | 隧道只做转发,origin 不通就是 502,白白浪费两小时排查 DNS | 先 curl 127.0.0.1:<port> 确认本地能开 |
| 要给别人上传文件 / 做大流量下载 | Tunnel 免费但不适合大文件分发 | 对象存储 + CDN |
| 需要固定 IP 白名单接入(部分第三方回调要求固定出口 IP) | Tunnel 是出站连接,没有固定公网 IP | 云服务器 + 弹性 IP |
要用 a.b.example.com 这种多级子域 |
Universal SSL 不覆盖二级以下子域,证书必然无效 | 换成一级子域 ab-example.com 或 ab.example.com;坚持要用得多级就得买 ACM |
| 完全没有域名 | 隧道本身不需要域名,但固定域名访问必须有 | 先注册域名(见 §七) |
| 想一键全自动、不碰控制台 | 建隧道、取 token、配 ingress 都可以走 API,但首次授权必须人工点一次 | 半自动,本技能就是这么设计的 |
一、三个先给结论
① 费用:一级子域永久免费。
Tunnel 免费、流量免费不限量、DNS 托管免费、HTTPS 证书免费。唯一付费场景是多级子域(x.y.example.com),需要 Advanced Certificate Manager。所以规则很简单:只用一级子域,永远零成本。
② 不要用 cloudflared tunnel login。
它会卡在授权页不动:点 Authorize 无跳转、进程不监听本地回调端口、cert.pem 永不生成,随后 CDP 直接超时。改走 Dashboard + 同源 API 取 token,见 references/tunnel-fixed-domain.md §2。
③ 顺序不能反:先固化本地服务,再建隧道。
隧道只负责"把请求转发到 localhost:port"。origin 不通时现象是「隧道 healthy 但域名打不开」,90% 的人会误判成 DNS 或证书问题,然后在一个根本不相关的地方耗掉半天。
二、全景八步
| # | 步骤 | 一句话要点 | 卡住的典型表现 |
|---|---|---|---|
| ① | 本地服务固化 | 进程不能绑会话,要有幂等保活 | 一关终端域名就 502 |
| ② | 建隧道 | Dashboard /dash/<account_id>/tunnels,不是 /networks/tunnels |
页面 404 |
| ③ | 取 tunnel token | 同源 API GET .../cfd_tunnel/<id>/token |
手工复制 paste 出错 |
| ④ | 装系统服务 | cloudflared service install <token> + Automatic |
重启后隧道断 |
| ⑤ | 配 ingress 路由 | PUT 必须带全量数组,最后一条 http_status:404 |
加子域后老域名失效 |
| ⑥ | 建 DNS 记录 | 每个子域一条 CNAME → <tid>.cfargotunnel.com,proxied:true |
记录建了但不解析 |
| ⑦ | 安全设置 | SSL/TLS 模式改 full |
525 / 526 |
| ⑧ | 三层验证 | NS + HTTP/SSL + 内容,缺一不可 | "应该好了吧"然后就坏了 |
每步的完整命令、参数、判据在 references/tunnel-fixed-domain.md,按步骤编号对应。
三、三个最容易踩死的坑
坑 1:服务"看着在跑",其实是会话临时进程
计划任务 State=Ready 不代表服务常驻。唯一可靠判据是查监听进程的父进程:
$p = (Get-NetTCPConnection -LocalPort 3000 -State Listen).OwningProcess
(Get-CimInstance Win32_Process -Filter "ProcessId=$p").ParentProcessId |
ForEach-Object { (Get-CimInstance Win32_Process -Filter "ProcessId=$_").Name }
| 父进程 | 结论 |
|---|---|
powershell.exe |
✅ 计划任务宿主,真常驻,可以上域名 |
bash.exe / 编辑器 / 终端 |
❌ 会话结束即消失,现在上域名等于白干 |
验证方法不是看状态,而是杀掉进程再触发任务,看它能不能自己起来。
坑 2:改 ingress 时漏规则 = 老域名全挂
Cloudflare 的 ingress 配置是整体覆盖,不是增量合并。PUT 时漏掉任何一条已有规则,那条路由当场消失。
规矩:
- 每次都读当前配置 → 改 → 全量 PUT;
- 顺序即优先级,具体路由在前;
- 最后一条必须是 http_status: 404 兜底,否则未匹配请求行为不可预期;
- hostname 写完整域名、不带协议(app.example.com,不是 https://app.example.com);
- PUT 成功后 version 递增,运行中的 cloudflared 自动热加载,不用重启服务。
坑 3:计划任务里一加输出重定向,进程就静默死掉
在计划任务上下文里给 PowerShell 加 -RedirectStandardOutput / -RedirectStandardError,会触发环境变量字典键冲突(Path vs PATH),进程启动即失败,且没有任何报错。
用 -WindowStyle Hidden,日志自己写文件,不要用重定向。保活模板里已经把这条写进注释了,别改。
四、快速开始
# 0) 自检:Python、浏览器驱动、cloudflared 是否就绪
python scripts/preflight.py
# 1) 固化本地服务(把两个脚本复制到应用目录,改 $Port / $EntryScript)
# 详细步骤见 references/tunnel-fixed-domain.md §1
# 2) 建隧道 → 取 token → 装服务 → 配 ingress → 建 DNS
# 见 references/tunnel-fixed-domain.md §2-§6
# 3) 验证
# 见 §五,或用 references/tunnel-fixed-domain.md §8 的脚本一次跑完
五、三层验收(缺一层都不算完成)
# 第 1 层:NS 是否真的生效(用 8.8.8.8,别用本地 DNS)
nslookup -type=ns example.com 8.8.8.8
# 第 2 层:域名 + 证书 + 响应码
curl -s -o /dev/null -w "%{http_code}|ssl=%{ssl_verify_result}|%{time_total}s\n" --max-time 20 https://app.example.com/
# 第 3 层:内容确实是你的应用(不是错误页、不是别人的站)
curl -s --max-time 20 https://app.example.com/ | grep -o -i "<title>[^<]*</title>" | head -1
| 层 | 通过判据 |
|---|---|
| NS | 返回 Cloudflare 分配的两条 NS |
| HTTP | 200 |
| SSL | ssl_verify_result=0(证书链完全有效) |
| 内容 | 标题与目标应用一致 |
| zone | Dashboard 显示 active |
| 隧道 | healthy,4 条 QUIC 连接 |
| 服务 | Cloudflared = Running / Automatic |
> 只有 4 条连接才是正常。看到 8 条 = 手动进程和服务进程在双跑,Stop-Process 掉手动那个。
六、排障速查
| 现象 | 根因 | 解法 |
|---|---|---|
| 502 | origin 不通,本地服务没起 | 先做步骤 ①;curl 127.0.0.1:<port> |
| 522 | CF 连不上隧道 | 查 Cloudflared 服务是否 Running |
| 525 / 526 | SSL/TLS 模式错 | 改成 full |
| 证书无效 | 用了多级子域 | 换一级子域 |
| 隧道 healthy 但域名打不开 | ingress 漏规则 / hostname 写错 | 重读坑 2,GET configurations 核对 |
| 加子域后老域名失效 | PUT 没带全量数组 | 重新全量 PUT |
| DNS 记录不生效 | proxied:false 或 content 写错 |
必须 <tid>.cfargotunnel.com + proxied:true |
| 改 NS 后解析中断 | 原注册商 DNSSEC 没关 | 回原注册商关掉 |
| 输入 NS 报「DNS 无效」 | 末尾多了个句点 | 去掉 |
完整对照表(含 20+ 条)见 references/tunnel-fixed-domain.md §10。
七、域名从零开始
已有域名且已接入 Cloudflare 的,直接跳到 §二。
从零开始要走四步:注册 → 实名认证 → 改 NS → 等生效。
关键实测数据:NS 传播实测约 9 小时,控制台标的 24–48 小时是保守值,不用干等一天。改 NS 前务必先关掉原注册商的 DNSSEC,否则解析会中断。
完整流程(含阿里云侧字段填法、身份证上传、审核跟踪)见 references/domain-register-dns.md。
八、页面操作基础
首次建隧道要在 Cloudflare 控制台和域名注册商后台点几下。这些控制台都是 SPA,直接点常常「返回成功但没反应」。
症状:脚本报告点击成功,页面毫无变化。
原因:页面存在全屏遮罩层,在捕获阶段拦截了合成事件。
做法(按可靠性排序):
// ① 原生点击,绕过命中测试(最省事,优先)
document.querySelector('#btn').click()
// ② 派发完整事件序列(前一种无效时用)
window.__J.fire(el)
// ③ 用原生 setter 填值(React / Vue 受控组件必须这样,直接赋 .value 无效)
window.__J.fill(css, value)
把 scripts/page_helpers.js 注入页面后即可用 window.__J:
bash scripts/inject_helpers.sh <session> <tab>
# 返回 {"ok":true,"value":"helpers ready"}
⚠️ 每次导航或刷新后 window.__J 都会丢失,必须重新注入。
驱动层支持 bsk 和 Playwright 两种,Mac / Linux 用后者:见 references/browser-driver.md。
九、文件索引
| 文件 | 用途 |
|---|---|
references/tunnel-fixed-domain.md |
⭐ 八步全流程详解:命令、参数、判据、排障、维护 |
references/domain-register-dns.md |
域名注册 → 实名 → NS 切换 → 接入 Cloudflare |
references/browser-driver.md |
浏览器驱动层:bsk / Playwright 切换与排障,含 bsk 在 SPA 表单上的 9 条实测规矩(observation 一次性、ref 失效、hidden input 传不了、点击静默失效、模态框关不掉…) |
CHANGELOG.md |
版本更新记录 |
scripts/preflight.py |
开工自检:Python / 驱动 / cloudflared / 目录权限 |
scripts/keep-alive-template.ps1 |
通用保活模板,改 2 个变量即可用,幂等、会话0安全 |
scripts/register-keepalive-task.ps1 |
一键注册计划任务(登录触发 + 每分钟保活) |
scripts/driver/ |
驱动适配层:cli.py 统一命令,bsk 与 playwright 双实现 |
scripts/page_helpers.js |
页面操作库 window.__J |
scripts/inject_helpers.sh |
注入 page_helpers.js |
scripts/upload_file.py |
文件注入(上传身份证等) |
十、边界与红线
- 只操作你自己的域名和账号。 不代他人绑定、不做域名抢注、不做绕过平台限制的用途。
- 域名实名认证的人脸识别必须本人完成,本技能不代做、也无法代做。
- 付款(买域名、买证书)由本人操作。本技能只负责告诉你要花多少、花在哪、为什么必须花。
- 隧道 token 等同凭据,不要贴进公开仓库或聊天记录;泄露了立刻在 Dashboard 重置。
- 不要把
~/.cloudflared/整个目录随意打包分发。