KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

08 · 证书生命周期与总验收 — keel 龙骨

本章目标:让证书过期这件事永远不会突然发生,并给出一份整门课的最终验收清单。 验收标准:到期提醒已设置,续期动作序列已演练,且能独立完成一次不中断服务的证书替换。

本章目标:让证书过期这件事永远不会突然发生,并给出一份整门课的最终验收清单。
验收标准:到期提醒已设置,续期动作序列已演练,且能独立完成一次不中断服务的证书替换。


一、证书会过期 —— 而且是在你忘记的时候

免费证书通常 90 天(Let's Encrypt)或 1 年(云厂商)。过期后的表现不是"变成 http",而是:

所以这一章的内容看起来最"运维",但它决定了前七章的成果能维持多久。


二、三道防线

防线 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 收录与优化》处理剩下的部分:让搜索引擎知道这个站点存在、抓得动、愿意收,并且长期保持健康。

进入 keel 阅读