说真的,配SSL证书这事儿,平时不痛不痒,一旦网站跳出"您的连接不是私密连接"或者CI流水线突然飙红,心态直接破防。这篇文章是我把过去几年在GitHub Pages、自托管Runner、Git操作上踩过的坑全总结了一遍,命令、报错截图、排查路径都给到。篇幅有点长,建议收藏后按目录跳着看。
一、SSL/TLS到底是干嘛的?先搞清楚再去配
SSL(Secure Sockets Layer)已经是"上一代"协议了,现在主流的是它的继任者TLS(Transport Layer Security)。它们干的事儿其实就两件:加密传输数据 + 验证服务器身份。没了这层保护,你登录GitHub时输入的token、push的代码、调的API,理论上都能被中间人截获。
在GitHub平台上,SSL/TLS配置涉及到的场景远比想象的多:
- GitHub Pages自定义域名:要绑定自己的域名就得有证书,没有就会红
- API调用:你不走HTTPS直接被301重定向
- Git操作(clone/push/fetch):底层用的就是TLS,证书不对就报
SSL certificate problem
- 自托管Runner:这是最容易出问题的点,公司内网CA证书不导入,流水线跑到一半就挂
GitHub对安全的要求一向严格,默认所有服务(web界面、API、Git操作)都强制HTTPS。用户自定义域名的SSL配置虽然可选,但说白了,不配就是给浏览器找骂,用户一打开就看到"不安全"三个字,信任度直接归零。
下面这套内容,就是围绕这几个场景,把配置方法、排查思路、最佳实践全撸一遍。
二、GitHub Pages自定义域名SSL配置
2.1 自动HTTPS(最省心的方案)
GitHub Pages默认提供免费的Let's Encrypt证书。给自定义域名勾选HTTPS后,平台会自动从Let's Encrypt获取并续订证书,全程不需要你手动操作。
配置步骤:
1. 进入仓库 Settings → Pages
2. 在 "Custom domain" 中输入你的域名
3. 等待DNS校验通过(通常几分钟)
4. 勾选 "Enforce HTTPS"
5. 等待证书签发(通常几分钟内完成,最长不超过24小时)
有几个细节很多人不知道:
- Let's Encrypt证书有效期为90天,GitHub会在到期前自动续订,你完全不用操心
- 首次签发如果失败,最常见的原因是DNS解析没生效(尤其是国内DNS服务商TTL设太长),等半小时再试
- 签发成功后,GitHub会同时下发证书到所有边缘节点,全球访问都能拿到正确证书
2.2 自定义证书配置(企业用户刚需)
对于企业用户或者有特殊合规要求的场景,GitHub Pages支持上传自定义SSL证书。这个能力开放至今,流程一直没变,目前支持RSA 2048位和ECDSA P-256两种密钥类型。
配置步骤:
1. 进入仓库 Settings → Pages
2. 在 "Certificate" 部分点击 "Upload"
3. 上传证书文件(.crt 或 .pem 格式)
4. 上传私钥文件(.key 格式)
5.(可选)上传中间证书链
证书格式要求与验证:
# 验证证书链完整性
openssl verify -CAfile chain.pem server.crt
# 合并证书链(GitHub要求服务器证书在前,中间证书在后)
cat server.crt intermediate.crt > fullchain.pem
> ⚠️ 注意:GitHub Pages对证书有几个硬性要求——密钥类型必须是RSA 2048位或ECDSA P-256,签名算法必须是SHA-256或更高,且证书必须包含你的自定义域名(SAN字段)。生产环境建议先用RSA 2048/ECDSA P-256保底,这俩是官方文档明确写明的支持项;其他规格上线前最好先在测试仓库试一遍。
2.3 证书格式转换(这一段是真香)
很多运维同事拿到证书时一脸懵——CA给的PFX、Java用的JKS、OpenSSL认的PEM、Windows导出的DER……格式换来换去头皮发麻。下面这些openssl转换命令,建议直接复制走,覆盖了PFX/PEM/DER之间所有互转场景:
# PEM → PFX(合并证书和私钥,常用于Windows导入)
openssl pkcs12 -export -out certificate.pfx -in server.crt -inkey private.key
# PFX → PEM(从PFX导出证书和私钥)
openssl pkcs12 -in certificate.pfx -out server.pem -nodes
# PEM → DER(Windows或某些硬件设备需要DER格式)
openssl x509 -in server.crt -out server.der -outform DER
# DER → PEM(从Windows导出的DER证书转回PEM)
openssl x509 -in server.der -inform DER -out server.pem -outform PEM
跑命令的时候如果提示Enter Export Password,最好设一个密码——PFX文件里包含私钥,不加密等于裸奔。
三、自定义域名配置详解
3.1 DNS解析配置(GitHub Pages的IP段要记牢)
GitHub Pages目前使用四个A记录地址,这个IP列表目前依然有效,直接抄走:
@ IN ALIAS username.github.io. # 如果DNS服务商支持ALIAS/ANAME
@ IN A 185.199.108.153
@ IN A 185.199.109.153
@ IN A 185.199.110.153
@ IN A 185.199.111.153
www IN CNAME username.github.io.
配置完成后,用dig命令验证DNS解析是否生效:
dig yourdomain.com +short
dig www.yourdomain.com +short
> 看到返回185.199.108.153等四个IP之一,说明A记录生效。看到返回username.github.io.的CNAME记录,说明www解析生效。如果返回的是旧IP或者NXDOMAIN,等DNS TTL过期(通常10分钟到24小时)。
3.2 子域名隔离(踩过坑的都知道)
GitHub Pages对子域名有一种"安全隔离"机制:如果你的根域名已经被另一个GitHub Pages站点占用,那你的子域名(包括www)也不能再绑到不同的GitHub Pages仓库。报错信息通常是There is already a page for this repository and its custom domain is in use by another repository。
典型场景及解决方案:
# 场景1:根域名被占用,子域名也需要绑到不同仓库
# 解决方案:在仓库的 CNAME 文件中只声明一个域名
# CNAME文件内容示例(只写一行):
docs.example.com
# 场景2:apex domain(根域名)指向了其他服务(比如博客用了Hexo)
# 解决方案:根域名用A记录,子域名用CNAME,互不冲突
@ IN A 185.199.108.153
blog IN CNAME username.github.io.
如果你用的是Cloudflare做DNS,记得关闭橙色云朵(DNS only模式),否则CNAME会被代理,GitHub拿不到正确的Host header,证书直接签不下来。
3.3 强制HTTPS与HSTS
启用"Enforce HTTPS"后,所有HTTP请求都会301重定向到HTTPS。GitHub Pages的301重定向是平台层硬编码的,用户层面无需配置Nginx/.htaccess。
HSTS配置参数详解:
max-age=31536000 # 1年(单位秒),浏览器记住"必须HTTPS"的时长
includeSubDomains # 强制所有子域名也走HTTPS
preload # 允许加入HSTS预加载列表(一旦提交,浏览器内嵌,撤掉需要几个月)
> ⚠️ 真香警告:HSTS preload不是儿戏。一旦你把域名提交到hstspreload.org,所有主流浏览器都会强制这个域名走HTTPS,无法撤销。只有当你的TLS配置能稳定支撑5年以上时,才考虑preload。一般公司用max-age=31536000; includeSubDomains就够了。
GitHub Pages在"Enforce HTTPS"勾选后,会自动下发HSTS响应头,具体策略以实际响应头为准:
Strict-Transport-Security: max-age=31536000; includeSubDomains
3.4 混合内容警告(页面加载一半被拦,真烦人)
配好HTTPS后还有一个容易踩的坑:页面里如果引用了http://开头的图片、脚本或样式表,浏览器会直接拦截,控制台报Mixed Content错误。我见过不少站点配好了证书,结果页面上一堆图裂了,就是因为这个。
排查和修复方法:
# 用curl检查页面里是否有http://资源
curl -s https://yourdomain.com | grep -o 'http://[^"'"'"']*' | sort -u
# 批量替换数据库或代码中的http://为https://
sed -i 's|http://yourdomain.com|https://yourdomain.com|g' config.php
最省事的做法是:代码里所有资源引用都用相对路径(/images/logo.png)或者协议相对路径(//cdn.example.com/lib.js),这样不管页面是HTTP还是HTTPS访问都不会出问题。如果是第三方资源(比如Google Fonts),直接换成HTTPS链接就行。
四、GitHub API的SSL配置
4.1 API访问的证书验证
GitHub API强制HTTPS,所有HTTP请求都会被301重定向到HTTPS。直接验证:
curl https://api.github.com
curl -L http://api.github.com # -L 跟随重定向
如果你在公司内网用了自签名HTTPS代理,或者中间加了自建CA,访问GitHub API时需要带上CA证书:
# 指定CA证书路径
curl --cacert /path/to/ca-bundle.crt https://api.github.com
# 跳过证书验证(不推荐,仅排查用)
curl -k https://api.github.com
4.2 Git操作的SSL配置
Git的clone和push操作底层用的也是TLS,但Git用的是自家的证书验证逻辑,跟curl不太一样。
HTTPS和SSH两种克隆方式:
# HTTPS方式(每次推送可能需要输token)
git clone https://github.com/username/repo.git
# SSH方式(推荐,配置好key后就免输密码)
git clone git@github.com:username/repo.git
SSL证书问题排查(这几个命令对运维真有用):
# 打开Git的详细TLS调试日志(能看到完整握手过程)
GIT_CURL_VERBOSE=1 git clone https://github.com/username/repo.git
# 全局配置Git信任的CA证书
git config --global http.sslCAInfo /path/to/ca-bundle.crt
# 关闭SSL验证(仅用于排查,生产环境绝对不要开)
git config --global http.sslVerify false
> 老实讲,http.sslVerify false是把双刃剑——临时关掉能快速判断是不是证书问题,但留在生产配置里等于裸奔。排查完一定要立刻恢复。
五、自托管Runner证书配置(这部分最容易翻车)
5.1 运行器通信加密
GitHub托管的运行器(GitHub-hosted runners)跟GitHub服务器之间的通信全程加密,你不用管。但自托管运行器(self-hosted runner)是另一个故事,它需要你手动配置SSL证书才能跟GitHub服务器安全通信。
GitHub Actions服务地址配置:
# GitHub官方服务(默认)
export GITHUB_ACTIONS_URL=https://github.com
# GitHub Enterprise Server(自建企业版)
export GITHUB_ACTIONS_URL=https://github.company.com
5.2 自签名证书的CA证书部署(多平台命令)
自托管Runner接入了公司内网的私有CA后,必须把CA证书部署到运行器所在的操作系统/运行时,否则Node.js、Python、Go这些工具会报unable to get local issuer certificate。
下面这些命令覆盖了主流平台,运维同事建议直接收藏:
# ===== Node.js 应用 =====
export NODE_EXTRA_CA_CERTS=/path/to/ca-bundle.crt
# 或启动Node应用时指定:
node app.js --sslCAFile /path/to/ca-bundle.crt
# ===== Debian/Ubuntu 系统CA库 =====
sudo cp ca-bundle.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates # 成功后会自动更新 /etc/ssl/certs/ca-certificates.crt
# ===== RHEL/CentOS/Fedora 系统CA库 =====
sudo cp ca-bundle.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
> ⚠️ 注意:命令里的ca-bundle.crt文件名可以随便起,但必须是PEM格式(即-----BEGIN CERTIFICATE-----开头)。如果是DER格式,先用openssl x509 -inform DER -in ca.der -out ca-bundle.crt转一下。
5.3 证书链验证(排查自托管Runner通信问题的终极武器)
如果Runner一直连不上GitHub服务器,先用这三个命令验证证书链是否完整:
# 查看完整TLS握手过程
curl -v https://github.com
# 如果是GitHub Enterprise Server
curl -v https://github.company.com
# 用openssl查看服务器返回的完整证书链
echo | openssl s_client -showcerts -connect github.com:443
如果openssl s_client输出最后一行是Verify return code: 0 (ok),说明证书链完整。再用-CAfile指定你自己的CA验证:
echo | openssl s_client -connect github.company.com:443 -CAfile /etc/pki/ca-trust/source/anchors/company-ca.crt
六、证书管理与监控
6.1 证书有效期监控
Let's Encrypt证书有效期为90天,GitHub自动续期你不用操心,但如果是自定义证书,过期了GitHub不会帮你续——页面会直接报错。
定时检查证书有效期:
# 如果用certbot管理证书
certbot certificates
# 检查GitHub Pages的证书有效期
openssl s_client -connect username.github.io:443 -servername yourdomain.com | openssl x509 -noout -dates
输出类似:
notBefore=Aug 1 00:00:00 2026 GMT
notAfter=Oct 30 00:00:00 2026 GMT
掐指一算差不多90天,提前30天做续期计划。
6.2 自动续订配置(自定义证书场景)
GitHub的Let's Encrypt自动续期,但自定义证书需要你自己管理。下面是用certbot配合cron/systemd timer实现的自动续期方案(假设你的DNS服务商支持API):
# ===== 方案1:certbot + DNS-01挑战 + cron(适用于Debian/Ubuntu)=====
# 续期脚本 /opt/certbot-renew.sh
#!/bin/bash
certbot renew --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini
# 复制新证书到指定目录
cp /etc/letsencrypt/live/example.com/fullchain.pem /opt/ssl/server.crt
cp /etc/letsencrypt/live/example.com/privkey.pem /opt/ssl/server.key
# 通过GitHub API上传新证书(需提前创建具有 repo 权限的 PAT)
gh secret set GH_PAGES_CERT --body "$(cat /opt/ssl/server.crt)"
gh secret set GH_PAGES_KEY --body "$(cat /opt/ssl/server.key)"
# ===== 方案2:systemd timer(更规范,推荐)=====
# /etc/systemd/system/certbot-renew.service
[Unit]
Description=Renew Let's Encrypt certificates
[Service]
Type=oneshot
ExecStart=/opt/certbot-renew.sh
# /etc/systemd/system/certbot-renew.timer
[Timer]
OnCalendar=Mon *-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
启用定时器:
sudo systemctl daemon-reload
sudo systemctl enable certbot-renew.timer
sudo systemctl start certbot-renew.timer
6.3 证书吊销处理(遇到私钥泄露别慌)
万一私钥泄露或者证书被误发,得赶紧吊销。GitHub Pages自定义证书的吊销流程:
# 用certbot吊销证书
certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem
# 吊销后删除证书
certbot delete --cert-name example.com
# 如果证书是CA直接签发的,去CA后台提交吊销请求(比如DigiCert、GlobalSign都有在线吊销入口)
吊销之后,GitHub Pages会继续用旧证书直到过期,所以你需要尽快上传新证书替换。另外,如果怀疑私钥泄露,吊销后还要检查一下GitHub仓库的secrets和deploy keys,该轮换的轮换,别只盯着证书。
6.4 定期审计与应急响应(最佳实践)
证书管理不是配完就完事了,我建议把下面这几条纳入日常运维:
定期审计清单(建议每月一次):
# 1. 检查所有自定义域名的证书状态
for domain in example.com www.example.com; do
echo "=== $domain ==="
echo | openssl s_client -connect $domain:443 -servername $domain 2>/dev/null | openssl x509 -noout -dates
done
# 2. 检查证书链是否完整
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep "Verify return code"
# 3. 检查HSTS响应头
curl -sI https://example.com | grep -i strict-transport-security
应急响应流程(证书过期/泄露时按顺序执行):
1. 立即吊销泄露的证书(参考6.3节)
2. 生成新密钥对并签发新证书(用certbot或CA后台)
3. 上传新证书到GitHub Pages(参考2.2节)
4. 验证新证书生效:echo | openssl s_client -connect example.com:443 -servername example.com | grep "Verify return code"
5. 如果涉及自托管Runner,同步更新Runner所在机器的CA证书库
6. 更新监控告警阈值,确保下次提前发现
七、总结
GitHub的SSL配置,说复杂也复杂,说简单也简单。核心就三件事:证书从哪来、证书怎么配、证书怎么管。
- GitHub Pages自定义域名:优先用自动HTTPS,企业合规需求再上自定义证书
- API和Git操作:记住CA证书路径配置和排查命令就行
- 自托管Runner:把CA证书部署到系统CA库,基本能解决90%的问题
- 证书管理:定期检查有效期,该续的续,该吊销的吊销,别等报错了才想起来
最后再啰嗦一句:证书这东西,配好只是开始,管好才是本事。希望这篇实测版攻略能帮你少踩几个坑。有问题评论区见,我看到都会回。