第一次把自己买的域名绑到 GitHub Pages 时,我以为这就是去 DNS 后台加一条记录的事。结果那晚我一边盯着“DNS check unsuccessful”的红色提示,一边在域名服务商、GitHub 设置页和一个始终打不开的页面之间反复横跳,直到第二天早上网站才被正常访问。后来我才意识到,问题不是一个,而是三个叠在一起:CNAME 文件没有出现在正确的发布分支、A 记录被旧缓存拖住、证书签发状态还卡在排队中。
这篇就把 GitHub 绑定域名涉及的全链路拆开讲清楚。内容不光是“在哪个表单填什么”,还会解释仓库里的 CNAME 文件、DNS 里的 CNAME 记录、HTTPS 证书这三者之间的关系,以及为什么很多人配置明明看起来没问题,网站却依然打不开。适合正准备给 GitHub Pages 绑定个人域名的人,也适合绑过但被各种 HTTP 404、证书警告、强制跳转折腾过的朋友。
1. 先看懂整条链路:绑定域名不是在“解析面板”里办完的一件事
1.1 GitHub Pages 的默认域名到底是怎么工作的
在绑定自定义域名之前,先搞清楚 GitHub Pages 默认域名的工作方式。每个 GitHub 账号可以有一个用户站点,地址是 username.github.io;每个仓库还能发布项目站点,地址是 username.github.io/projectname。当你访问 username.github.io 时,GitHub 收到请求后会通过 HTTP 请求头里的 Host 字段来判断你要访问的是哪一个 Pages 站点。
这里的核心点在于:所有 GitHub Pages 站点共享同一组服务器 IP,GitHub 并不是靠 IP 区分站点,而是靠 Host 头。自定义域名做的事情,本质上就是让全世界的 DNS 都知道“myblog.com 这个域名解析到 GitHub Pages 的 IP”,同时还要让 GitHub 知道“当请求里的 Host 是 myblog.com 时,请返回某个特定仓库的内容”。
所以完整的请求链路是这样的:
- 浏览器输入 myblog.com,向本地 DNS 发起解析请求。
- 本地 DNS 沿着 DNS 体系找到你域名服务商那里的解析记录。
- 解析记录返回 GitHub Pages 的 IP 地址。
- 浏览器向该 IP 发起 HTTP 请求,请求头里带着
Host: myblog.com。 - GitHub 的边缘节点看到 Host 是 myblog.com,会去查找哪个 Pages 站点的发布内容里带有对应的身份标识。
- 找到后,返回该仓库的静态文件。
这六个环节里,第 3 步由域名服务商的 A 记录或 CNAME 记录决定,第 5 步由仓库里一个名为 CNAME 的文件决定。两件事都做对,域名才能真正打开。这就是为什么很多新手只改了 DNS,却在 GitHub 这边始终验证不过。
1.2 三件“同名不同物”的事:CNAME 文件、CNAME 记录、TLS 证书
绑定域名过程中最劝退新手的,是“CNAME”这个词出现了好几次,每次含义还都不一样。
第一是仓库根目录里的 CNAME 文件。它是一个没有扩展名的纯文本文件,内容是你要绑定的域名。GitHub Pages 发布站点时会把整个仓库的内容作为静态站点输出,CNAME 文件也会出现在站点根目录。它的作用是告诉 GitHub:“这个站点归属的用户,认可 myblog.com 这个域名。”
第二是 DNS 服务商面板里的 CNAME 记录。这是 DNS 体系里的一种记录类型,作用是把一个子域名指向另一个域名,例如把 www.myblog.com 指向 username.github.io。它解决的是“浏览器该访问哪个 IP”的问题。
第三是 TLS 证书。当你配置好自定义域名后,GitHub 会自动向公共 CA 申请该域名的证书,以保证 HTTPS 访问正常。证书签发的前提是 GitHub 能确认你确实控制这个域名,而确认方式就是看 DNS 解析是否到达 GitHub Pages。
这三件事可以这样类比:GitHub Pages 像一栋巨大的共享办公楼,username.github.io 是房间号。你要在楼门口挂一块“myblog.com 公司”的牌子(DNS 解析),同时楼里的登记簿也要写上这家公司的名字(仓库 CNAME 文件),前台看到挂着我公司名字的访客,才能带他去对应房间(TLS 证书验证)。
为了更直观,这里列一个对应关系表:
| 配置项 | 存放位置 | 谁负责 | 配置错了的表现 |
|---|---|---|---|
| CNAME 文件 | 仓库发布源根目录 | 开发者提交 | 域名能解析到 GitHub,但页面 404 或跳回默认域名 |
| A / CNAME 记录 | 域名服务商 DNS 面板 | 域名管理员 | GitHub 页面显示 DNS check unsuccessful,网站无法访问 |
| TLS 证书 | GitHub 自动签发 | GitHub 与 CA 完成 | 浏览器提示不安全,Enforce HTTPS 无法开启 |
1.3 为什么三件事不能当成“一次设置”处理
很多人配置失败的根本原因,是把这三件事当成了同一个时间点内要全部完成的设置。实际上 DNS 修改有 TTL 缓存,GitHub 对自定义域名的验证也是周期性进行的,证书签发更是需要时间。
我习惯把这三个环节按依赖关系排序:先让域名能解析到 GitHub,再让 GitHub 能认出域名,最后等证书签发。DNS 是地基,CNAME 文件是身份牌,证书是最后的安全层。顺序搞反了,或者同时操作之后立刻就去点“Enforce HTTPS”,很容易把问题复杂化。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 仓库侧配置:CNAME 文件的两种写入方式与分支陷阱
2.1 图形界面写入:Settings → Pages → Custom domain
最简单的绑定方式是在 GitHub 仓库页面操作。进入仓库的 Settings → Pages,找到 Custom domain 输入框,填上你要绑定的域名,然后点 Save。比如你希望站点通过 myblog.com 访问,就填 myblog.com,不要带 https://,也不要带路径。
点 Save 之后,GitHub 会做几件事:校验当前 DNS 是否已经指向 GitHub Pages,然后尝试在站点的发布分支根目录创建一个 CNAME 文件并提交。如果 DNS 还没配置好,页面会显示 DNS check unsuccessful,但这并不代表保存失败,CNAME 文件通常还是会写进去。
这种方式最不容易在“文件创建”这一步出错,因为 GitHub 界面帮你完成了一切。但它有一个隐患:如果发布分支不是你在本地编辑源码的那个分支,GitHub 自动创建的 CNAME 提交可能会和你的发布流程产生冲突。
2.2 手动写入:在源码目录提前放一个 CNAME 文件
如果你的站点不是直接由某个分支发布,而是通过 GitHub Actions 构建后再发布,我强烈建议手动在源码目录里放一个 CNAME 文件,并确保构建流程会把它原样复制到输出目录。
原因很简单:GitHub 在网页上保存自定义域名时,会尝试把 CNAME 文件提交到“当前发布分支”。如果你的发布分支是 gh-pages,而源码在 main 分支,那么每次手动构建或者重新部署时,旧的 CNAME 文件可能被新的构建产物覆盖,最终发布内容里根本没有 CNAME。
处理方式取决于你用的静态站点生成器:
- 如果是 Jekyll,并且发布源是 main 分支根目录,直接把 CNAME 文件放在仓库根目录即可。
- 如果是 Hexo,通常发布的是 public 目录,而这个目录往往被 .gitignore 忽略。你需要把 CNAME 放在 source 目录,再通过插件在 generate 阶段复制到 public。
- 如果是 Hugo,可以放在 static 目录,Hugo 构建时会自动把 static 下的内容复制到发布根目录。
- 如果是纯 HTML 手工维护,直接把 CNAME 文件放在发布根目录,和 index.html 同级。
还有一个更通用的办法:在 GitHub Actions 的部署步骤里加上一行复制命令。例如很多静态站点发布流程的最后一步是把整个构建产物推送到 gh-pages 分支,那么你可以在构建完成后执行 echo myblog.com > CNAME,确保 CNAME 文件一定存在。
2.3 不要踩的坑:文件名大小写、内容格式和发布分支不一致
CNAME 这个文件名必须全大写,而且没有扩展名。写成 cname.txt 或 CNAME.txt 都不会被 GitHub 识别。
文件内容也有讲究。假设你要绑定的是 www.myblog.com,那文件内容就应该是 www.myblog.com,不要画蛇添足写 https://www.myblog.com,也不要写 www.myblog.com/。如果你要绑定的是裸域名,文件内容就只写 myblog.com。
另外,还要确认这个文件最终出现在哪个“发布源”里。GitHub Pages 的发布源有三种常见设置:从分支发布、从 GitHub Actions 发布、从 docs 目录发布。如果发布源是分支,CNAME 文件必须在该分支根目录;如果发布源是 docs 目录,CNAME 文件必须在 docs 目录里。很多人把 CNAME 文件提交到了 main 分支根目录,但发布源配置的是 gh-pages 分支,那自然永远都不会生效。
2.4 用户站点与项目站点的 CNAME 归属差异
如果你的账号下有多个仓库都开启了 Pages,就需要区分用户站点和项目站点。用户站点的仓库名必须是 username.github.io,一个账号只能有一个。项目站点则是任意普通仓库。
用户站点绑定自定义域名后,会让整个 username.github.io 都响应这个域名。项目站点如果没单独配置,它的 URL 默认是在 username.github.io/projectname。一个常见的困惑是:用户站点绑定了 myblog.com,访问 myblog.com/projectname 时能不能打开某个项目?
实际操作中,项目站点的地址规则会因为用户站点是否配置自定义域名而产生变化。如果你有好几个项目页面,建议分别到每个项目仓库的 Settings → Pages 里检查一下自定义域名是留空还是有值,不要只盯着用户站点的仓库配置。很多时候项目页面 404,排查方向却一直停留在 DNS 记录上,其实问题只是项目仓库没设置对应的自定义域名,或者由于用户站点绑定了域名后导致路径层级发生了变化。
3. DNS 解析配置:A 记录还是 CNAME,怎么写才能过 GitHub 的检查
3.1 GitHub 官方用的 A 记录 IP 和主机记录写法
当你在 DNS 服务商后台配置 GitHub Pages 时,最稳妥的做法是参照 GitHub 官方文档给出的 IP。GitHub Pages 使用以下四个 IPv4 地址:
text复制185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153
如果域名服务商支持 IPv6,也可以配置对应的 AAAA 记录,GitHub 的 IPv6 地址范围是:
text复制2606:50c0:8000::153
2606:50c0:8001::153
2606:50c0:8002::153
2606:50c0:8003::153
主机记录怎么填,取决于你的域名服务商。以常见的解析面板为例,如果要让裸域名 myblog.com 生效,主机记录一般填 @;如果要让 www.myblog.com 生效,主机记录填 www。
一个域名通常需要同时配置多条 A 记录指向这四个 IP,而不是只配一条。这样可以在某一个 IP 出现故障时由其他 IP 继续提供服务。
3.2 为什么裸域名用 A 记录,子域名用 CNAME
这里有一个 DNS 层面的技术限制:根据 DNS 协议标准,CNAME 记录不能和其他记录共存。具体来说,如果一个域名存在 CNAME 记录,就不能再为它配置 MX 记录、TXT 记录等其他类型的记录。裸域名通常还要承载邮箱服务的 MX 记录,因此直接给裸域名配置 CNAME 会引发冲突。
GitHub Pages 的解决方案就是:裸域名使用 A 记录,直接指向 GitHub 的 IP;而 www 这类子域名可以使用 CNAME 记录,指向 username.github.io。
如果实在是某个 DNS 服务商不允许在同一个主机记录下添加多条 A 记录,那就需要检查是不是服务商把记录类型限制得太死。绝大多数正规 DNS 服务商都支持同一个名字配置多条 A 记录,这是负载均衡的常规做法。
3.3 裸域名、www 子域名,到底用哪个作为主域名
很多时候需要决定把博客的主域名设为裸域名 myblog.com,还是 www.myblog.com。从用户体验来说,两个都应该能访问,否则用户记错其中一个就会打不开网站。
实践中,我会在 GitHub 的 Custom domain 里填写裸域名 myblog.com,然后在 DNS 服务商里同时配置两组记录:
| 记录类型 | 主机记录 | 记录值 | 说明 |
|---|---|---|---|
| A | @ | 185.199.108.153 | 让裸域名找到 GitHub Pages |
| A | @ | 185.199.109.153 | 多 IP 冗余 |
| A | @ | 185.199.110.153 | 多 IP 冗余 |
| A | @ | 185.199.111.153 | 多 IP 冗余 |
| CNAME | www | username.github.io | 让 www 子域名也能打开 |
按照这套配置,访问 myblog.com 和 www.myblog.com 最终都能落到同一个站点。GitHub 在签发证书时会同时覆盖这两种形式,因此两个地址都能以 HTTPS 访问。
不少教程会建议大家把 www 的解析记录也指向 GitHub 的四个 A 记录 IP,而不是 CNAME 到 username.github.io。两种写法在大多数情况下都可以工作,但用 CNAME 更符合 GitHub 对子域名的推荐方式,而且后续如果 GitHub 调整 IP,CNAME 方式不需要你手动修改。
如果要做 SEO 角度的域名统一,需要明确哪个是 canonical 域名。GitHub Pages 本身不提供 301 跳转规则配置,所以如果你希望所有访问都从裸域名跳转到 www,或者反过来,光靠 GitHub 设置并不能实现。一般是在 <head> 里加 canonical 标签告诉搜索引擎主域名,或者接入一层 CDN 做跳转规则。
3.4 TTL、解析生效时间和本地缓存
配置完 DNS 记录后,能不能立刻打开网站,不完全取决于你,还取决于全球 DNS 缓存的刷新速度。DNS 记录里有一个 TTL 参数,表示其他 DNS 服务器可以缓存这条记录多长时间。常见的默认 TTL 可能是 600 秒、3600 秒甚至 86400 秒。TTL 越大,缓存时间越长,修改生效越慢。
我建议在第一次配置域名解析时,先把 TTL 调小,比如 300 秒。这样如果配置有误,修改后也能较快生效。等网站稳定运行之后再考虑把 TTL 调回去,减少 DNS 查询压力。
要验证 DNS 是否已经生效,可以使用命令行工具,也可以借助在线的 DNS 查询工具。Linux 或 macOS 下用 dig:
bash复制dig +noall +answer myblog.com
dig +noall +answer www.myblog.com
Windows 下用 nslookup:
bash复制nslookup myblog.com
nslookup www.myblog.com
如果返回的结果中有 185.199.108.153 等 GitHub Pages 的 IP,说明域名服务商这边的解析已经正常。另外,你本地电脑也可能缓存了旧的 DNS 结果,这时候即使外网解析已经正确,本地依然打不开。可以刷新本地 DNS 缓存后再测。
macOS 刷新 DNS 缓存:
bash复制sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
Windows 刷新 DNS 缓存:
bash复制ipconfig /flushdns
3.5 不建议用“URL 转发”代替 DNS 记录
很多域名服务商为了方便用户,提供了“显性 URL 转发”或“隐性 URL 转发”功能。它的作用是:访问 myblog.com 时,浏览器 302 跳转到 username.github.io。这种方式看起来也能打开网站,但实际上存在几个问题:
- 浏览器地址栏会从自定义域名跳回默认域名,用户看到的是 username.github.io,而不是你的品牌域名。
- URL 转发通常会丢掉路径参数,或无法正确处理 HTTPS。
- GitHub 检查自定义域名时,需要 DNS 解析结果直接指向 GitHub Pages,URL 转发的解析目标往往是服务商自己的跳转服务器,GitHub 无法完成验证。
所以,URL 转发只能作为临时方案,不能作为正式配置。
4. HTTPS 证书为何一直卡住:DNS 状态、Enforce HTTPS 与发布源三者的互相牵制
4.1 证书签发的前提条件
GitHub Pages 对自定义域名提供免费的 TLS 证书。证书签发和续期都是自动完成,不需要手动上传证书文件。但这个自动流程有两个前提:
第一,GitHub 能通过 DNS 查询确认你填写的域名解析到了 GitHub Pages。如果 DNS 记录缺失、指向错误,或者你套了一层 CDN 代理,GitHub 的验证就会失败,证书会一直处于等待或失败状态。
第二,仓库的发布内容里必须存在 CNAME 文件,且文件内容和你填写的域名一致。GitHub 通过 CNAME 文件建立“Host 请求”与“具体仓库”之间的关联,如果这个关联不存在,证书签发流程就没有对象。
所以,如果你在 GitHub 的 Custom domain 界面看到 HTTPS 那一栏始终没有变成 Active,不要只盯着证书信息看。先回过去检查:DNS 解析是否已经由外部公共 DNS 验证通过?仓库发布分支里 CNAME 文件是否真的存在?
4.2 Enforce HTTPS 的勾选时机
GitHub Pages 设置页里有一个 “Enforce HTTPS” 的选项。它默认是勾选状态,但在证书没有签发成功之前,不建议手动打开,甚至应该暂时关掉。
原因在于:Enforce HTTPS 一旦打开,GitHub 会把所有 HTTP 请求自动 301 跳转到 HTTPS。如果此时证书还没签发成功,HTTPS 访问会直接报错,用户访问 myblog.com 时就会看到“无法安全连接”的提示,而不是你期望的网站页面。
正确顺序是:先确认自定义域名状态显示为 DNS check successful,再等待 HTTPS 状态从“Not asked”或“Waiting”变成 Active,最后再打开 Enforce HTTPS。如果状态一直停在 DNS check unsuccessful,那就先修 DNS,而不是反复去开关 Enforce HTTPS。
4.3 自定义域名和 CDN 代理之间的冲突
如果你在域名服务商这边使用了 Cloudflare 或其他 CDN 的代理模式,配置 GitHub Pages 自定义域名时很容易卡在证书签发环节。
以 Cloudflare 为例,DNS 记录有两种状态:灰色云朵是 DNS only,只做解析;橙色云朵是 Proxied,会经过 CDN 节点。当你开启橙色云朵
