在生产环境中,更新 TLS 证书是一个常见但敏感的操作。如果直接重启 Nginx 服务,会导致连接中断,影响用户体验。本文介绍如何在 Nginx 中实现 TLS 证书的热更新,在不中断服务的情况下完成证书更新。
什么是证书热更新?#
证书热更新(Hot Reload)是指在不停止服务的情况下,重新加载新的证书文件。Nginx 支持通过发送 reload 信号来实现配置和证书的平滑重新加载。
与完全重启(nginx -s stop 然后 nginx)相比,reload 的优势在于:
- 不会中断正在处理的请求
- 平滑过渡,新连接使用新证书,旧连接继续使用旧证书
- 服务可用性不受影响
Nginx reload 机制#
Nginx 支持以下几种信号:
nginx -s reload - 平滑重新加载配置
kill -HUP <master_pid> - 发送 HUP 信号,作用与 reload 相同
kill -USR1 <master_pid> - 重新打开日志文件
kill -USR2 <master_pid> - 优雅升级(用于升级 Nginx 二进制文件)
当 Nginx 收到 reload 信号时:
- 主进程(Master Process)解析新的配置文件
- 如果配置合法,主进程启动新的工作进程(Worker Process)
- 旧的工作进程优雅退出(处理完现有连接后关闭)
- 新的工作进程使用新的证书
证书热更新操作步骤#
1. 准备新的证书文件#
首先,将新的证书和私钥文件复制到指定目录:
1
2
3
4
5
6
7
|
# 复制新的证书文件
sudo cp server_new.crt /etc/nginx/ssl/server.crt
sudo cp server_new.key /etc/nginx/ssl/server.key
# 确保文件权限正确
sudo chmod 600 /etc/nginx/ssl/server.key
sudo chmod 644 /etc/nginx/ssl/server.crt
|
2. 验证证书文件#
在重新加载之前,验证证书和私钥是否匹配:
1
2
3
4
5
|
# 提取证书公钥
openssl x509 -in /etc/nginx/ssl/server.crt -noout -modulus | openssl md5
# 提取私钥公钥
openssl rsa -in /etc/nginx/ssl/server.key -noout -modulus | openssl md5
|
如果两者的 MD5 值一致,说明证书和私钥匹配。
同时检查证书有效期:
1
2
|
# 检查证书有效期
openssl x509 -in /etc/nginx/ssl/server.crt -noout -dates
|
3. 测试 Nginx 配置#
在重新加载之前,务必测试配置文件是否正确:
1
2
3
4
5
6
|
# 测试 Nginx 配置语法
sudo nginx -t
# 如果配置测试通过,应该看到类似输出:
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
|
4. 触发配置 reload#
配置测试通过后,触发 reload:
1
2
3
4
5
6
7
8
|
# 方法一:使用 nginx -s reload
sudo nginx -s reload
# 方法二:直接发送 HUP 信号
sudo kill -HUP $(cat /var/run/nginx.pid)
# 方法三:使用 systemctl(适用于 systemd)
sudo systemctl reload nginx
|
5. 验证新证书已生效#
reload 完成后,验证新证书是否已经生效:
1
2
3
4
5
|
# 使用 openssl s_client 查看当前使用的证书
echo | openssl s_client -servername example.com -connect localhost:443 2>/dev/null | openssl x509 -noout -subject -dates
# 或者使用 curl 访问并查看证书信息
curl -v https://localhost/ 2>&1 | grep -E "SSL certificate|subject|expires"
|
配置示例#
以下是一个完整的 Nginx HTTPS 配置示例,支持证书热更新:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
server {
listen 443 ssl http2;
server_name example.com;
# 证书配置
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 完整的 TLS 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# 会话缓存
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
root /var/www/html;
index index.html;
}
|
无损 reload 的最佳实践#
1. 使用符号链接#
为了简化证书更新流程,可以使用符号链接:
1
2
3
|
# 配置中使用符号链接
ssl_certificate /etc/nginx/ssl/current/server.crt;
ssl_certificate_key /etc/nginx/ssl/current/server.key;
|
更新证书时,只需更换符号链接指向:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 创建新证书目录
sudo mkdir -p /etc/nginx/ssl/update_$(date +%Y%m%d)
# 复制新证书到新目录
sudo cp server_new.crt /etc/nginx/ssl/update_$(date +%Y%m%d)/
sudo cp server_new.key /etc/nginx/ssl/update_$(date +%Y%m%d)/
# 切换符号链接
sudo ln -sfn /etc/nginx/ssl/update_$(date +%Y%m%d)/server.crt /etc/nginx/ssl/current/server.crt
sudo ln -sfn /etc/nginx/ssl/update_$(date +%Y%m%d)/server.key /etc/nginx/ssl/current/server.key
# 触发 reload
sudo nginx -s reload
|
这种方法的好处是:
- 原子操作,不会出现半更新的状态
- 轻松回滚:如果新证书有问题,只需切换回旧的符号链接
- 便于审计:保留历史证书版本
2. 监控 reload 状态#
可以通过以下命令监控 Nginx 的工作进程:
1
2
3
4
5
|
# 查看 Nginx 进程状态
ps aux | grep nginx
# 实时查看连接状态
watch -n 1 'ss -tn | grep :443 | wc -l'
|
3. 设置合理的 worker_processes#
确保 Nginx 配置中使用了合适的 worker 进程数:
1
2
3
4
5
|
# 自动检测 CPU 核心数
worker_processes auto;
# 每个 worker 允许处理的连接数
worker_connections 1024;
|
常见问题排查#
问题一:reload 后证书没有更新#
可能的原因:
- 证书文件路径错误
- 证书和私钥不匹配
- 配置文件没有正确指定证书路径
排查方法:
1
2
3
4
5
|
# 检查 Nginx 错误日志
sudo tail -f /var/log/nginx/error.log
# 查看当前配置加载的证书
nginx -T 2>&1 | grep -A 5 "ssl_certificate"
|
问题二:部分连接仍在使用旧证书#
这是正常现象。旧的工作进程在处理完现有连接后会退出,新连接会使用新证书。可以通过以下命令强制关闭旧的 worker 进程:
1
2
3
4
5
|
# 查看当前 worker 进程
ps aux | grep nginx
# 如果确认所有请求都已处理完毕,可以优雅关闭旧进程
kill -QUIT $(cat /var/run/nginx.pid.oldbin)
|
问题三:reload 失败#
通常是由于配置文件语法错误:
1
2
3
4
|
# 查看详细错误信息
nginx -t 2>&1
# 如果有语法错误,会显示具体行号和错误原因
|
自动证书更新脚本#
结合 ACME 客户端(如 Certbot),可以编写自动证书更新脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#!/bin/bash
# auto_renew_ssl.sh
# 更新证书
certbot renew --quiet --post-hook "nginx -s reload"
# 验证新证书
if [ $? -eq 0 ]; then
echo "$(date): Certificate renewed successfully"
# 验证证书已更新
echo | openssl s_client -servername example.com -connect localhost:443 2>/dev/null | openssl x509 -noout -enddate
else
echo "$(date): Certificate renewal failed"
exit 1
fi
|
将脚本添加到 crontab:
1
2
|
# 每天凌晨 3 点检查并更新证书
0 3 * * * /path/to/auto_renew_ssl.sh >> /var/log/cert_renewal.log 2>&1
|
通过 Nginx 的 reload 机制,我们可以在不中断服务的情况下更新 TLS 证书。关键步骤包括:
- 准备新的证书文件并验证
- 使用
nginx -t 测试配置
- 执行
nginx -s reload 或发送 HUP 信号
- 验证新证书已生效
遵循本文的最佳实践,可以确保证书更新过程平稳、安全,不会影响用户体验。
参考来源#