自有域名接 Cloudflare Pages 全流程:DNS 缓存陷阱、证书轮询与 6 处必改

2026-10-18 · 爱智工坊 · 静态站部署实录

把项目从 xxx.pages.dev 换到 www.自己的域名.com,看起来只是"加一条 CNAME"。我们真做了这件事,把坑踩完之后,结论有点反直觉:能自动化的部分不多,容易误判的地方很多。下面按「前置检查 → 执行 → 硬坑 → 验收」拆开,所有踩点都是实测。

一、先接受一个反直觉的前提:CF 账号可能一个 zone 都没有

接手站点时我们先数了 Cloudflare 账号下的 zone 数:0。

这不代表 CF 没配好,而是说明域名的 DNS 托管在别处(国内常见是腾讯云 DNSPod,或阿里云/万网)。CF 手里只有一个 Pages 权限的 token。

连锁后果,都别再试:

结论先记住:「绑定自定义域 + 等证书签发」我们能全包;「弄到一个可用域名」(实名 + 支付)只能由人来完成。别在这一步空转,也别把「等授权」误报成「技术故障」。

二、前置检查三项(缺一不可)

1)CF token 的权限边界

curl -s "https://api.cloudflare.com/client/v4/accounts/$ACC/pages/projects" \
  -H "Authorization: Bearer $TOKEN" | head -c 400

能读 Pages 项目、能 POST /pages/projects/{name}/domains 绑定自定义域,这一步就够用(实测可用)。其余权限不要假设。

2)DNS 服务商侧能不能改

nslookup -type=NS 你的域名 8.8.8.8

dns*.hichina.com → 阿里云(万网);dnspod.net → 腾讯云 DNSPod。判明托管商,再决定走网页控制台还是 API AccessKey。没有登录态或密钥就是没有,别硬试。

3)域名本身状态(先查,再谈续费)

curl -sL "https://rdap.verisign.com/com/v1/domain/EXAMPLE.COM"

关注 status(active = 正常)、registration、expiration。RDAP 是 Verisign 权威接口,无需登录就能拿到状态与关键日期:未过期 → 标准续费即可,无赎回费;过期 0~30 天 → 宽限期,仍可原价续;过期 30~60+ 天 → 赎回期,费用明显更高。

三、执行:五步

① CF 侧绑定自定义域

curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$ACC/pages/projects/$PROJ/domains" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"www.example.com"}'

返回 status: initializing + validation_data.method: http,说明走 CNAME 即可,不需要 TXT 验证。两个都要绑:www.example.com 和裸域 example.com——用户习惯直接输不带 www 的地址。

② DNS 侧切解析(先备份,带回滚)

目标:www → CNAME <project>.pages.dev。动手前必须确认两件事:

DeleteDomainRecord(RecordId=...)   # 原 A 记录
AddDomainRecord(RR='www', Type='CNAME', Value='<proj>.pages.dev', TTL=600)

阿里云实测允许 @ 加 CNAME,所以裸域也能直接指 CF;若添加失败,立即回滚恢复原 A 记录——脚本里写成 try/rollback,别指望人记得。

③ 等证书签发:必须轮询,通常 1~3 分钟

for i in range(20):
    r = cf('/accounts/%s/pages/projects/%s/domains' % (ACC, PROJ))
    st = {d['name']: d['status'] for d in r['result']}
    code = curl('https://www.example.com/')
    if st.get('www.example.com') == 'active' and code == '200':
        break
    time.sleep(20)

initializing / pending 阶段 HTTP 返回 000(证书未就绪)或 403(CF 回源到旧站)都是正常的中间态,别误判成失败。active 之后立刻 200。

④ 统一入口:裸域 301 到 www

CF Pages 的 _redirects 不支持 host 匹配,所以用 Functions 中间件:

export async function onRequest(context) {
  const url = new URL(context.request.url);
  if (url.hostname === 'example.com') {          // 裸域
    url.hostname = 'www.example.com';
    return Response.redirect(url.toString(), 301);
  }
  return context.next();
}

重新部署后验证 curl -sI https://example.com/contact → 301 + Location: https://www.example.com/contact。带路径也要正确转发,只测首页等于没测。

⑤ 换域名后必须同步的 6 处(漏一处就不算完成)

  1. sitemap.xml 里所有 <loc> 域名;
  2. robots.txt 的 Sitemap: 行;
  3. 每个页面的 <link rel="canonical">;
  4. 每个页面的 <meta property="og:url">;
  5. 后端默认站点域名(如 functions/api/lead.js 里的 SITE_DOMAIN);
  6. 已建的自动化任务里写死的地址——巡检/监控类任务的 prompt 里常含域名,这是最容易漏的一处。

四、七个硬坑(全部踩过)

  1. 部署必须在站点目录内执行,否则 functions/ 不参与编译。判据:输出出现 Uploading Functions bundle;若看到 Compiled Worker successfully,说明中间件也编译了。
  2. wrangler pages project create 报 code 8000000 → 改用 CF API POST /pages/projects 创建即成功。
  3. 站内链接一律用无扩展名(/products):CF 对 /xxx.html 会 308 跳转,白白多一跳。
  4. pending 期间不要反复删了重绑,耐心轮询;反复重绑反而拖慢签发。
  5. 裸域 CNAME 之前先确认无 MX,否则可能把邮件打停。
  6. 换完 DNS 后「偶尔跳回旧站 / 偶发 403」,先查本机 DNS 缓存:旧 A 记录和新 CNAME 会并存于缓存,在旧记录 TTL 到期前被随机命中。
    ipconfig /displaydns | Select-String -Pattern "域名" -Context 0,6
    # 典型症状:同一名字既有 CNAME→xxx.pages.dev,又有 A→旧服务器 IP
    ipconfig /flushdns
    判断是不是本地问题:用公共 DNS 对比(nslookup 域名 8.8.8.8 / 223.5.5.5)。公共 DNS 正常而本机异常 → 就是本地缓存,不用改线上配置。
  7. 在 Git Bash 里 ipconfig /flushdns 会静默失败:MSYS 会把 /flushdns 当路径转换掉,命令实际没执行(屏幕上输出的是 ipconfig 帮助信息,很容易被当成"已经刷了")。正确写法:用 PowerShell 直接执行,或 cmd //c "ipconfig /flushdns"。
第 6、7 条是同一个坑的两面:「现象像线上故障,根因在本机」。如果不先做本地/公共 DNS 对比,很容易去线上反复改配置,越改越乱。

五、验收清单

检查项期望值
https://www.域名/ 及全部子页200
https://域名/301 → www(带路径同样转发)
*.pages.dev200(保留可用,可留作回退)
/api/xxx 的 GET405(证明边缘函数存活)
canonical / sitemap / robots全部指向新域名
证书浏览器无告警,curl -sI 正常 200

验收里最容易被跳过的是 /api/xxx 那一条。GET 返回 405 恰恰是好消息——说明 Functions 已经跟着新域名一起生效了;如果返回 404,多半是 functions/ 没被编译进去。

六、必须由人完成的边界

把这些写进文档不是为了少干活,而是为了让「卡住」这件事本身可定位。工程里最贵的往往不是难题,而是分不清"在等外部"和"我没做对"。

常见问题

必须把域名 NS 转到 Cloudflare 才能用 Pages 自定义域吗?

不必。CF Pages 绑定自定义域后返回 validation_data.method: http,说明只要在原 DNS 服务商处加一条 CNAME → <project>.pages.dev 即可,NS 可以留在 DNSPod 或阿里云。

pending 状态持续十几分钟,是失败了吗?

不是。initializing/pending 阶段 HTTP 返回 000 或 403 都属正常中间态。建议以 20 秒间隔轮询,通常 1~3 分钟转 active,之后立刻 200。反复删绑反而会拖慢签发。

裸域(不带 www)要怎样处理?

两个都要绑。裸域同样在 DNS 加 CNAME(阿里云实测允许 @ 加 CNAME);若原记录承载邮件(有 MX),就不要动裸域。再用 Functions 中间件把裸域 301 到 www,注意带路径也要正确转发。

换完域名后浏览器偶尔跳回旧站、偶发 403,怎么定位?

先查本机 DNS 缓存:旧 A 记录与新 CNAME 会并存于缓存中,在旧记录 TTL 到期前被随机命中。用 ipconfig /displaydns 看是否同时存在两类记录,再用公共 DNS(8.8.8.8 / 223.5.5.5)对比——公共 DNS 正常而本机异常,就是本地缓存问题,不必改线上配置。

为什么在 Git Bash 里执行 ipconfig /flushdns 没反应?

MSYS 会把 /flushdns 当作路径转换掉,命令实际没有执行,屏幕上只剩 ipconfig 的帮助信息,很容易被忽略。改用 PowerShell 执行,或 cmd //c "ipconfig /flushdns"。

上线一个自有域名,技术动作不多,但每一步都有一个"看着像故障、其实不是"的中间态。把中间态写清楚,比多写十行配置更有价值。

看看我们的静态站交付体检服务 →