成为全栈·基础补充·域名、DNS 与 HTTPS 证书

成为全栈·基础补充·域名、DNS 与 HTTPS 证书

本文统一用示例域名 api.example.com 来讲(实际项目换成你自己的域名),而它的配置在仓库里只有一行:

toml 复制代码
routes = [{ pattern = "api.example.com", custom_domain = true }]

一行。 而这行代码背后压着四层东西:DNS 解析、TLS 证书、Cloudflare 路由、Worker 绑定。

而这四层各自都可能出问题,症状却高度相似------都是一个打不开的页面。

这就是这一章要解决的问题:当"打不开"出现时,怎么知道该找哪一层。

三层各管什么

先把职责分清,因为大部分排查困难都源于"不知道这一层负责什么"。

text 复制代码
用户输入 api.example.com
    ↓ ① DNS:这个名字对应哪个 IP / 哪个 Cloudflare 地址
    ↓ ② TLS 证书:证明这个域名确实是它,且加密传输
    ↓ ③ 边缘节点:Cloudflare 收到请求
    ↓ ④ 路由:匹配 pattern,把请求转给 Worker
    ↓ Worker 代码
层 出问题时的典型症状 谁的锅
DNS "域名无法解析" / 换个网络才能访问 域名配置 / TTL
TLS "你的连接不是私密连接" 证书 / 配置
Cloudflare 路由 502 / 找不到站点 pattern 配错
Worker 500 / 正常错误响应 你的代码

而最容易混的是前两层 ,因为它们都由 Cloudflare 自动管理------用户看到的是"打不开",而你去 Worker 日志里查,什么都没有。

DNS:TTL 是"最长可能陈旧时间"

DNS 的作用是把名字解析成地址。而这个专栏的实际配置里,DNS 甚至不是自己管的:

toml 复制代码
# ── 自定义域名(部署后需在 CF 配 DNS + SSL)──
routes = [{ pattern = "api.example.com", custom_domain = true }]

custom_domain = true 意味着 Cloudflare 帮你创建 DNS 记录和证书。

但这个"帮你"有一个前提:你的域名本身要在 Cloudflare 的 DNS 里(而不是 registrar 那边)。

而这里有一个必须理解的性质:DNS 有缓存。

text 复制代码
浏览器缓存 → 系统缓存 → 路由器缓存 → 公共解析器缓存 → 权威 DNS

五层,各有自己的 TTL。 而 TTL 表达的不是"多久同步一次",而是:

这个记录最多可能被缓存多久。

而这个区别很重要 ------因为它意味着:改完 DNS 之后,不同的人可能在不同时间看到新配置,最长差一个 TTL。

而这个专栏在部署时的实际经历是:

bash 复制代码
# 验证解析结果
dig api.example.com
nslookup api.example.com

而 dig 的价值在于它能指定解析器,绕开本机缓存:

bash 复制代码
dig api.example.com @1.1.1.1      # 用 Cloudflare 的公共 DNS
dig api.example.com @8.8.8.8      # 用 Google 的

如果三者结果不同,说明公共解析器还没更新 ------而不是你的配置错了。

顺带说几个记录类型,而这个项目相关的有两个:

类型 作用 这个项目用在哪
A / AAAA 名字 → IP Cloudflare 的 Anycast 地址
TXT 存文本 所有权验证、SPF、DKIM
CNAME 名字别名 静态资源托管

而 TXT 记录有一处常见踩坑 :验证域名所有权时会往 _acme-challenge 下面加一条 TXT。而如果同一子域名下已有其他 TXT 记录,添加时要小心不要覆盖掉。

TLS 证书:三个容易混淆的地方

第二层。证书的作用是证明服务器是这个域名的控制者,同时提供加密。

而它的问题往往出在证书和域名的匹配关系上:

情况 结果
证书含 example.com ✅
证书含 example.com + *.example.com ✅ api.example.com 匹配
证书只含 www.example.com ❌ api.example.com 不匹配

而这个专栏用的是通配符或完整的域名列表------由 Cloudflare 自动管理。

第二个坑是中间证书。

服务端只发自己的证书,而客户端需要构建完整链条 (自己的 + 中间 CA + 根 CA)。如果中间证书没配全,只有部分客户端会报错------因为有些客户端缓存了中间证书,有些没有。

症状是"我这儿好好的,用户说打不开" ------而这类问题的根源是服务端少发了一张证书。

第三个坑是这个专栏真实遇到的:端口 80 无法访问导致续期失败。

自动续期的验证方式有两种:HTTP(需要 80 端口可达)和 DNS(往域名加一条 TXT)。

如果用了 HTTP 验证,而 80 端口被防火墙挡了,续期就会失败------而它失败的时候不会通知任何人,直到证书过期。

所以现在的做法是:

措施 防什么
用 DNS 验证 不依赖端口
加监控(检查过期时间) 续期静默失败
自动续期提前 30 天 给足失败重试的时间

而"加监控"这条是这个专栏明确记下的债 ------因为自动续期失败是一个"什么都不发生直到出事"的问题。

而 SITE_URL 那个环境变量也是相关的------M4-14 讲过分享功能:

dart 复制代码
const site = String.fromEnvironment('SITE_URL', defaultValue: '');

如果它配错了,分享出去的链接就是错的 ------而这和证书不同,它不会报错,只会让用户点到一个错地址。

所以这类"拼错的配置"需要专门检查,而不能指望运行时发现。

四层的问题,怎么定位

现在回到开头那个问题:打不开,怎么知道找哪一层。

判断顺序是固定的:

bash 复制代码
# 1. DNS 通不通
dig api.example.com
# 失败 → DNS 层,检查域名是否在 CF、记录是否存在

# 2. HTTP 通不通(跳过证书验证)
curl -sk https://api.example.com/api/v1/health
# 连不上 → 路由或 Worker 层
# 通了但证书有问题 → TLS 层

# 3. 业务对不对
curl -s https://api.example.com/api/v1/articles | jq '.code'
# 0 → 一切正常
# 其他 → 你的代码有问题

而这三步的价值在于它们各自只可能在一个层失败。

我把这个判断写进了 B-19 那篇(它讲排查原则):

一次廉价的探测,能把排查范围砍掉大半。

而这里的具体形态就是上面这三条命令------每条命令验证一个边界,而不是"试一下看看"。

顺带说 curl -k(跳过证书验证)的正确用法 :它只应该在"验证证书本身有问题"时用------比如你在排查证书配置。

而如果你用 -k 作为日常习惯,那你会失去发现证书问题的能力。 而证书问题的症状是"少数用户打不开"------你永远无法从自己的机器上复现它,除非你刻意去验证。

证书相关的三个环境变量

这一章讲证书,而项目里有三个地方和"地址"有关,它们的失败方式各不相同:

变量 配错的后果 是否有报错
API_BASE_URL(App 端) 连到错误环境 无(两边都有数据)
CORS_ORIGINS(后端) 浏览器跨域失败 服务端日志正常
SITE_URL(分享链接) 分享出去的地址是错的 无

而 CORS 那个特别值得单说,因为它在代码层面被做了限制:

toml 复制代码
# CORS:填前端实际域名(逗号分隔);留空或 '*' 将被拒跨域(审阅 B05)

"留空或 '*' 将被拒"------这不是"允许但不推荐",是代码层面直接拒绝。

而它的价值在于:你不需要每次 review 都记得检查 CORS 配置。 一个被禁止的写法,比一个被警告的写法可靠。

而另两个变量的共同点是"配错了不报错"------这正是 B-19 讲的那个原则的另一个应用:

配置错误要尽量在启动时暴露;暴露不了的,就必须有监控。

SITE_URL 那个尤其典型:分享出去 thousands 个错误链接,而你从任何日志里都看不出来。

所以它应该在分享功能上线前手工验证一次------而不是靠"配了就行"。

一条我一开始想省掉的事

部署完成的那天晚上,我确认了 DNS 解析正确、证书有效、接口能返回数据。

然后我睡着了。

第二天早上用户反馈"打不开"。查下来是证书过期了 ------而它的续期在三个月前就失败了,失败原因是 80 端口被防火墙挡着。

而三个月里没有任何人收到通知。

Cloudflare 的自动续期本来是有邮件通知的,但我当时没配置通知邮箱 ------默认情况下它只发给自己注册的那个账号,而我用的是另一个邮箱。

而这件事最难堪的地方是:它不是"我没检查",而是"我以为检查过了"。

因为我确实验证过证书------部署当天验证的,有效期一年。

而我把"验证过"当成了"会一直有效"。

修法有两层:

  1. 配置续期失败的通知 (notifications 那个功能)
  2. 加一个主动检查过期时间的监控 ------ openssl s_client 或者外部服务

第 2 条是关键 ,因为通知是"它出问题了告诉你",而监控是"它要出问题了提前告诉你"。

而这个专栏现在的做法是把它记成一条明确的运维项------B-19 那篇里写的"已知缺口"就有它。

小结

这一章的内容不多,但它给的是一种能力:看到"打不开"时知道往哪看。

三个判断:

  1. DNS 有五层缓存 ,TTL 是"最长陈旧时间"而不是"同步倒计时"。而验证要用 dig @1.1.1.1 绕开本机缓存。
  2. 证书问题有三个来源 (不匹配 / 缺中间证书 / 续期失败),而续期失败是唯一静默的------它几个月后才暴露。
  3. curl -sk 只在排查时用,日常用它是失去了一种发现问题的能力。

而这一章真正的判断标准是第 2 条背后那个:

自动化的失败是静默的,而静默的失败只有监控能发现。

证书续期是自动化的(不需要人操作),而它的失败不会打断任何人------直到某天所有用户都打不开了。

这和 B-17 讲的那笔债是同一类问题:

静默失败 后果
证书续期失败 三个月后全站不可用
幂等键没实现(B-20) 重复请求产生重复数据
requestId 不关联日志(B-19) 用户报错无法定位

这三件事都不会报错,所以只能靠主动检查。 而"主动检查"的意思不是"小心一点",是"写一个会自动喊人的东西"。

这也是这个专栏为什么反复强调"缺口要写下来"------写下来的缺口至少还存在于文档里,而没写下来的缺口只存在于代码的沉默里。

延伸阅读


如果这篇文章对你有帮助,欢迎订阅我的 CSDN 专栏 「成为全栈」:

🔗 专栏地址:https://blog.csdn.net/fungleo/category_13204651.html

📦 本系列配套代码仓库:https://github.com/fengcms/become-a-full-stack-developer

相关推荐
FungLeo2 小时前
成为全栈·基础补充·Web 安全基础:XSS、CSRF 与 SQL 注入
web安全·网络安全·xss·csrf·sql注入·成为全栈
FungLeo1 天前
成为全栈·基础补充·数据库索引:为什么加了索引还是慢
mysql·postgresql·sqlite·sql优化·数据库索引·成为全栈
Lsetea2 天前
Docker更新SSL证书后容器仍读旧文件:单文件挂载与目录挂载排查
运维·nginx·docker·https·ssl证书
FungLeo2 天前
成为全栈·基础补充·Linux 服务器入门
linux·运维·服务器·ssh·部署·成为全栈
2501_916008893 天前
iPhone 设备信息查看,用 KeyMob 看型号、序列号、电池健康和 UDID
android·ios·小程序·https·uni-app·iphone·webview
FungLeo4 天前
成为全栈·Go 后端篇·从 Node SQLite 到 PostgreSQL:把迁移做成独立交付物
postgresql·golang·sqlite·go·数据库迁移·成为全栈
FungLeo4 天前
成为全栈·Go 后端篇·Go 后端怎么测试:从成功响应到故障后置状态
golang·集成测试·差分测试·成为全栈·后端测试·数据库测试
FungLeo4 天前
成为全栈·Go 后端篇·换后端不改前端:四个客户端如何做兼容回归
flutter·golang·go·成为全栈·兼容测试·taro 小程序
李纲明5 天前
WordPress 登录后跳回登录页或提示「Cookie 被阻止」:先核站点 URL 与 HTTPS 一致,再查安全插件与 CDN
https·登录·cdn·wordpress·cookie·devdiy·站点url