Nginx · 部署
Nginx 部署与自动重载
平台只做一件事:把三个文件准确放进你指定的目录。剩下的配置、权限、重载都在你这边, 这一页给出可以直接复制的一套。
目录怎么放
一个域名一个目录,比较省心:
/etc/nginx/ssl/
└── example.com/
├── example.com_cert.pem
├── example.com_full_chain.pem
└── example.com_key.pem
多个域名同时用,就并列放 /etc/nginx/ssl/api.example.com/、/etc/nginx/ssl/shop.example.com/。
证书文件名由平台按域名生成,共用一个目录也不会互相覆盖,但分目录更利于排查。
server 块
# /etc/nginx/conf.d/example.com.conf
server {
listen 80;
server_name example.com *.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on; # nginx < 1.25.1 用 listen 443 ssl http2;
server_name example.com *.example.com;
ssl_certificate /etc/nginx/ssl/example.com/example.com_full_chain.pem;
ssl_certificate_key /etc/nginx/ssl/example.com/example.com_key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000" always;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
server_name 写 *.example.com 只匹配一级子域名,a.b.example.com 需要另外添加或用正则。
泛域名证书本身覆盖的也是一级子域名。
私钥权限
证书由受限的部署用户上传,落盘属主是那个用户。Nginx 的 worker 进程通常以 www-data(Debian/Ubuntu)或 nginx(RHEL 系)运行,需要能读私钥。稳妥的做法:
chown root:www-data /etc/nginx/ssl/example.com/example.com_key.pem
chmod 640 /etc/nginx/ssl/example.com/example.com_key.pem
chmod 644 /etc/nginx/ssl/example.com/example.com_full_chain.pem
这些命令放在下面的重载脚本里一起执行,就不用每次手动改。
证书更新后自动重载
如果节点填了重启命令,这一步平台已经替你做完了。部署脚本会在服务器上装一个 root 所有的
sethttps-reload 包装脚本和对应的 systemd path 单元,平台上传证书后写入
reload.request 触发它。下面这些方案适用于没有配置重启命令的节点。
重载不是可选项:文件被覆盖了但 Nginx 没有 reload,线上仍然是旧证书。
方案一:监听目录(推荐)
装好 inotify-tools 后,把下面这个脚本放到 /usr/local/bin/ssl-reload.sh:
#!/usr/bin/env bash
# 监听证书目录,文件被写入后校验配置并重载 nginx
set -euo pipefail
DIR="${1:-/etc/nginx/ssl/example.com}"
GROUP="${2:-www-data}"
inotifywait -m -e close_write -e moved_to --format '%w%f' "$DIR" |
while read -r f; do
case "$f" in
*_key.pem)
chown root:"$GROUP" "$f"
chmod 640 "$f"
;;
*.pem)
chmod 644 "$f"
;;
esac
if nginx -t 2>/dev/null; then
nginx -s reload
logger -t ssl-reload "reloaded after $f"
else
logger -t ssl-reload "nginx -t failed after $f, skip reload"
fi
done
chmod +x /usr/local/bin/ssl-reload.sh
apt install -y inotify-tools # RHEL 系:dnf install -y inotify-tools
/usr/local/bin/ssl-reload.sh /etc/nginx/ssl/example.com www-data &
长期运行建议包成 systemd 服务:
# /etc/systemd/system/ssl-reload.service
[Unit]
Description=Reload nginx when certificates change
After=network.target
[Service]
ExecStart=/usr/local/bin/ssl-reload.sh /etc/nginx/ssl/example.com www-data
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now ssl-reload
方案二:定时重载
最省事,代价是每天多一次 reload(对线上几乎没有影响):
# /etc/cron.d/ssl-reload
17 4 * * * root nginx -t && nginx -s reload
方案三:手动
证书少、更新频率低,看到平台通知后自己执行 nginx -s reload 也可以,只是别忘。
验证真的换了证书
# 看线上实际返回的证书有效期
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# 看证书链是否完整(返回 0 才算完整)
echo | openssl s_client -connect example.com:443 -servername example.com -verify_return_error 2>&1 \
| grep -E "Verify return code|depth="
# 看证书覆盖了哪些域名
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 浏览器正常,安卓 / curl 报证书错误 | 只配了 _cert.pem,缺中间证书 | 改用 _full_chain.pem |
nginx -t 报 key 不匹配 | 证书与私钥不是同一次签发 | 三个文件一起更新,不要混用旧文件 |
Permission denied 读私钥 | worker 用户无权限 | 按「私钥权限」一节改属主和 640 |
| 文件已更新但证书没变 | 没有 reload,或 reload 的是另一个 nginx 进程 | 确认 nginx -s reload 作用于同一个 master;容器内要进容器 reload |
| reload 后短暂 502 | reload 期间旧 worker 优雅退出 | 正常现象,通常几十毫秒 |