KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
08 · 证书生命周期与总验收 — keel 龙骨
本章目标:让证书过期这件事永远不会突然发生,并给出一份整门课的最终验收清单。 验收标准:到期提醒已设置,续期动作序列已演练,且能独立完成一次不中断服务的证书替换。
本章目标:让证书过期这件事永远不会突然发生,并给出一份整门课的最终验收清单。
验收标准:到期提醒已设置,续期动作序列已演练,且能独立完成一次不中断服务的证书替换。
一、证书会过期 —— 而且是在你忘记的时候
免费证书通常 90 天(Let's Encrypt)或 1 年(云厂商)。过期后的表现不是"变成 http",而是:
- 浏览器显示
NET::ERR_CERT_DATE_INVALID,用户必须手动点"继续访问"(多数人不会点) - 爬虫直接失败,已收录页面会被降权甚至移除
- 微信内置浏览器、App 内 WebView 可能直接拒绝加载
所以这一章的内容看起来最"运维",但它决定了前七章的成果能维持多久。
二、三道防线
防线 1:日历提醒(最低要求)
在日历里设两个提醒:
证书到期前 30 天 —— 开始续期
证书到期前 7 天 —— 必须已完成部署(这是最后期限)
查到期日的命令:
openssl x509 -in /etc/nginx/ssl/xxxxxx.cn_bundle.crt -noout -enddate
# notAfter=Dec 28 23:59:59 2026 GMT
防线 2:自动续期(推荐)
certbot 装好后自带 systemd timer:
# 查看定时器
systemctl list-timers | grep certbot
# 演练一次(不会真的替换,但会走完整流程)
sudo certbot renew --dry-run
自定义脚本续期时,关键是续完要 reload nginx,否则进程内存里还是旧证书:
sudo certbot renew --quiet --deploy-hook "systemctl reload nginx"
用云厂商免费证书的话没有自动续期,只能走"重新申请 → 下载 → 上传 → 替换 → reload"的手动流程(见第四节)。这种方案下,防线 1 的日历提醒是唯一保障。
防线 3:外部监控(最稳,成本最低)
任何能监控 HTTPS 证书到期的服务都可以(云监控、UptimeRobot、Better Uptime 等),配置一个简单的 HTTPS 检查,它会在证书快到期时告警。
自己也可以用一条命令做最朴素的检查,塞进 crontab:
#!/usr/bin/env bash
# /usr/local/bin/check-cert.sh
DOMAIN=www.xxxxxx.cn
DAYS=$(echo | openssl s_client -connect "$DOMAIN":443 -servername "$DOMAIN" 2>/dev/null \
| openssl x509 -noout -checkend $((21*86400)) >/dev/null 2>&1 && echo ok || echo expiring)
[ "$DAYS" = "expiring" ] && echo "证书将在 21 天内过期: $DOMAIN" | mail -s "cert warning" you@example.com
0 9 * * * /usr/local/bin/check-cert.sh
三、不中断服务地换证书
证书替换是原子操作:新文件就位 → nginx -t → reload。整个过程不掉连接。
set -euo pipefail
CONF=/etc/nginx/sites-enabled/learning
SSL=/etc/nginx/ssl
NEW_CRT=/tmp/xxxxxx.cn_bundle.crt
NEW_KEY=/tmp/xxxxxx.cn.key
# 1. 备份(证书与配置都要)
sudo cp $SSL/xxxxxx.cn_bundle.crt $SSL/xxxxxx.cn_bundle.crt.bak.$(date +%s)
sudo cp $SSL/xxxxxx.cn.key $SSL/xxxxxx.cn.key.bak.$(date +%s)
sudo cp $CONF /tmp/learning.bak.$(date +%s)
# 2. 校验新证书是否与私钥匹配(不匹配就别往下走)
[ "$(openssl x509 -noout -pubkey -in $NEW_CRT | openssl md5)" = \
"$(openssl rsa -noout -pubkey -in $NEW_KEY | openssl md5)" ] || { echo "私钥不匹配"; exit 1; }
# 3. 就位
sudo install -o root -g root -m 644 $NEW_CRT $SSL/xxxxxx.cn_bundle.crt
sudo install -o root -g root -m 600 $NEW_KEY $SSL/xxxxxx.cn.key
# 4. 语法检查 → reload(失败自动回滚)
sudo nginx -t || { sudo cp $SSL/xxxxxx.cn_bundle.crt.bak.* $SSL/; sudo systemctl reload nginx; exit 1; }
sudo systemctl reload nginx
# 5. 验收
openssl x509 -in $SSL/xxxxxx.cn_bundle.crt -noout -enddate -subject
curl -sI https://www.xxxxxx.cn/ | head -1
第 2 步的自检很重要:证书与私钥不匹配时,nginx -t 会直接失败,但在那之前你已经把原证书覆盖了——虽然有备份,但站点会处在"配置无法加载"的状态。先比对再覆盖,能避免这几十秒的不可用。
四、总验收清单(整门课一次跑完)
把下面这段存成 verify-site.sh,每次改动后跑一遍:
#!/usr/bin/env bash
DOMAIN=www.xxxxxx.cn
IP=203.0.113.10
pass(){ echo " ✅ $1"; }
fail(){ echo " ❌ $1"; FAILED=1; }
FAILED=0
echo "1) DNS"
[ "$(dig +short $DOMAIN)" = "$IP" ] && pass "$DOMAIN -> $IP" || fail "DNS 解析不正确"
echo "2) 端口与跳转"
code=$(curl -s -o /dev/null -w "%{http_code}" https://$DOMAIN/)
[ "$code" = "200" ] && pass "HTTPS 200" || fail "HTTPS 返回 $code"
loc=$(curl -sI http://$DOMAIN/ | grep -i '^location' | tr -d '\r')
echo "$loc" | grep -q "https://$DOMAIN" && pass "HTTP 301 -> HTTPS" || fail "301 不正确: $loc"
echo "3) 证书"
enddate=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
echo " 到期: $enddate"
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null \
| grep -q "Verify return code: 0" && pass "证书链完整" || fail "证书链不完整"
echo "4) 内容无 http 残留"
n=$(curl -s https://$DOMAIN/learning/ | grep -c 'http://' || true)
[ "$n" = "0" ] && pass "无 http 残留" || fail "仍有 $n 处 http://"
echo "5) SEO 入口"
for p in /robots.txt /learning/sitemap.xml; do
c=$(curl -s -o /dev/null -w "%{http_code}" https://$DOMAIN$p)
[ "$c" = "200" ] && pass "$p 可访问" || fail "$p 返回 $c"
done
exit $FAILED
chmod +x verify-site.sh && ./verify-site.sh
全绿即代表:域名归你、解析正确、HTTPS 有效、全站无 http 残留、SEO 入口就绪。
五、故障速查表
| 现象 | 最可能的原因 | 第一步排查 |
|---|---|---|
| 打不开,超时 | 安全组/防火墙;或你的 IP 被云主机安全封禁 | 换手机热点访问;iptables -L -n |
ERR_CERT_DATE_INVALID |
证书过期 | openssl x509 -noout -enddate |
ERR_CERT_AUTHORITY_INVALID |
用了不带中间证书的 crt | 换成 bundle 文件 |
ERR_CERT_COMMON_NAME_INVALID |
证书不包含当前访问的域名 | openssl x509 -noout -ext subjectAltName |
| 能开但显示不安全 | 页面内有 http 子资源 | Console 看 Mixed Content |
| 301 循环 | 多个 server 块互相跳转,或后端也在跳 | curl -sIL 看完整跳转链 |
nginx -t 报 unknown directive "http2" |
版本 < 1.25.1 | 改成 listen 443 ssl http2; |
| 本地改了但线上没变 | 发布流程没覆盖前端源码/没重新构建 | 对比 assets/*.js 文件名 |
六、课程收尾
到这里,你已经完成了「域名 → 解析 → 服务器 → 证书 → 全站 HTTPS」的完整闭环。此时站点是可被安全访问的,但还不等于能被找到。
下一门课《SEO 收录与优化》处理剩下的部分:让搜索引擎知道这个站点存在、抓得动、愿意收,并且长期保持健康。