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;
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 和到期检查串起来,才算真正完成。