HTTPS 证书申请与自动续期

免费证书已经足够好用,真正容易出问题的是两件事:续期没有真正发生,以及配置了 HSTS 之后才发现某个子域还不支持 HTTPS。这篇按顺序把这两件事处理掉。

签发证书

用 certbot 的 Nginx 插件最省事,它会自动改写 server 块并配置 80 端口的验证路径。

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

如果不想让工具改配置,用 webroot 模式:手动在 Nginx 里放行 /.well-known/acme-challenge/,再让 certbot 把验证文件写进去。

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
sudo certbot certonly --webroot -w /var/www/letsencrypt -d example.com

证书默认落在 /etc/letsencrypt/live/example.com/。Nginx 里要用 fullchain.pem(包含中间证书),不要只用 cert.pem,否则部分客户端会报证书链不完整。

确认续期真的会发生

certbot 安装时会注册一个 systemd timer。先看它是否存在、下次触发时间是什么时候,再做一次 dry-run。

systemctl list-timers | grep certbot
sudo certbot renew --dry-run

dry-run 成功只代表验证流程没问题,不代表线上证书一定会续。续期之后 Nginx 还需要 reload 才会加载新证书,建议在续期命令上挂一个 deploy hook。

sudo certbot renew --deploy-hook "systemctl reload nginx"

TLS 参数

现在的客户端基本都支持 TLS 1.2 及以上,关掉更老的协议即可,不需要堆很多复杂配置。

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
HSTS 要先想清楚 HSTS 会让浏览器在有效期内强制使用 HTTPS。如果某个子域还没配好证书,或者以后要临时用 HTTP 调试,就会被自己锁住。建议先用较小的 max-age(比如一天)观察,确认所有子域都正常后再改成一年的值。

混合内容与跳转

页面本身是 HTTPS,但里面引用了 http:// 的图片或脚本时,浏览器会拦截。上线前用开发者工具的 Console 或 curl 检查一遍,把所有资源改成相对路径或 https。

curl -I http://example.com
curl -I https://example.com
curl -sI https://example.com | grep -i strict-transport

80 端口的 server 块只保留跳转即可,不要再提供内容。

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

监控到期时间

自动续期失败时通常没有明显症状,直到某天浏览器弹出警告。加一条最简单的检查,提前两周告警。

echo | openssl s_client -servername example.com \
  -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates

把输出里的 notAfter 解析成日期,和当前时间比较即可。放到定时任务里,每天跑一次,比任何复杂监控都直接。

常见故障

  • 验证失败:80 端口没有放行,或者 /.well-known/ 被重定向到了 HTTPS。
  • DNS 未生效:新域名刚解析时先等几分钟,用 dig +short example.com 确认。
  • 证书链不完整:Nginx 配了 cert.pem 而不是 fullchain.pem。
  • 续期成功但仍显示旧证书:没有 reload,或者 reload 的是错误的 Nginx 实例。

证书本身不复杂,复杂的是让它一直有效。把续期、reload 和到期检查串起来,才算真正完成。