说真的,Nginx 的进程管理信号这件事,初次接触的人很容易被 nginx -s stop、nginx -s quit、nginx -s reload、nginx -s reopen 这四个长得几乎一模一样的命令绕晕。表面上看都是「让 Nginx 干点啥」,但底层走的信号完全不同,对线上流量的影响也天差地别。
老规矩,我先把结论摆前面:生产环境里,只要不是要立刻让 Nginx 消失(比如机器要关机、配置出现严重问题需要紧急止血),首选永远是 nginx -s quit。这条命令会走 SIGQUIT 信号,Nginx 会等所有活跃连接正常处理完再退出,不会让你的用户收到莫名其妙的连接重置。
下面我从信号层面把这事儿掰开讲清楚,顺便把 reload 和 reopen 也一起讲了,免得你下次又踩坑。
一、四种控制信号的速查表
先上对比表,这张表是全文的锚点,建议收藏:
| 控制方式 | 底层信号 | 信号编号 | 等价命令 | 行为特征 |
| 快速停止(Stop) | SIGTERM | 15 | kill -TERM | 立即终止,不等待活跃连接释放 |
| 优雅停止(Quit) | SIGQUIT | 3 | kill -QUIT | 等待所有活跃连接处理完毕后再退出 |
| 配置重载(Reload) | SIGHUP | 1 | kill -HUP | 重新加载配置,新建 Worker,老 Worker 处理完存量请求后退出 |
| 日志切割(Reopen) | SIGUSR1 | 10 | kill -USR1 | 重新打开日志文件,用于 logrotate 场景 |
注意:网上不少老文章把 SIGQUIT 写成「信号 0」,这是错的。信号 0 在 POSIX 里是「空信号」,根本不会传递给进程,只用于 kill -0 检查进程是否存在。真正的 SIGQUIT 编号是 3,SIGTERM 是 15。表格里我直接列正确编号,避免你照着错的去 kill。
二、SIGTERM 的处理逻辑:为什么说它是「粗暴」的停止方式
Nginx 是典型的 Master/Worker 进程模型(这玩意儿到现在也是 Web 服务器里的经典设计,参考 Nginx、Apache、HAProxy 都是这套思路)。理解信号差异之前,必须先把这个模型刻进脑子里:
- Master 进程:只有一个,负责读取配置、管理 Worker、接收外部信号
- Worker 进程:默认有多个(一般等于 CPU 核数),实际处理 HTTP 请求
当你在终端执行 nginx -s stop 时,Nginx 客户端工具会向 Master 进程发送 SIGTERM。接下来的链路是这样的:
- Master 进程收到 SIGTERM,不再走任何优雅退出逻辑
- Master 立刻向所有 Worker 进程转发 SIGTERM
- Worker 进程不做任何清理,直接退出(不关闭 socket、不等待请求完成)
- 此时正在处理的 HTTP 请求被强制中断,客户端大概率看到「Connection reset by peer」或者响应截断
老实讲,这种行为在生产环境是相当不友好的。如果你的 Nginx 前端还有负载均衡,连接重置会被打到后端服务上,相当于一次小规模的事故。所以 SIGTERM 这个信号,更适合「机器要断电了」「Nginx 配置写错导致整个服务雪崩」这种紧急止血场景。
三、SIGQUIT 的优雅之处:Graceful Shutdown 完整流程
跟 SIGTERM 形成鲜明对比的是 SIGQUIT。nginx -s quit 走的是这条路径:
- Master 进程收到 SIGQUIT,关闭监听 socket(关键:这一步让新连接不再进来,但已建立的连接不受影响)
- Master 向所有 Worker 进程发送 SIGQUIT
- Worker 进程进入「优雅退出」状态:不再接受新连接,但会继续处理完当前正在处理的请求
- Worker 处理完所有活跃请求后,正常关闭连接,退出进程
- 所有 Worker 退出后,Master 自己也退出
这套流程在 Nginx 源码里被称为 Graceful Shutdown。说白了,Nginx 给了一个「自然消亡」的窗口期,让正在进行的业务跑完再下线。
这里有个很多人会忽略的细节:Worker 在优雅退出时,会先处理完 worker_shutdown_timeout 配置定义的超时时间内的请求(这个配置在 nginx.conf 里,默认无超时,部分高版本 Nginx 引入)。如果你的接口处理时间特别长(比如大文件上传、AI 推理接口),需要合理设置这个值,否则 Worker 会一直挂着不退出,导致 stop 脚本超时被 systemd 强杀。
四、SIGHUP 和 SIGUSR1:别再把 reopen 当成 reload
很多运维新手会把 nginx -s reopen 跟 nginx -s reload 混为一谈,毕竟名字都长得很像。但它们的底层信号完全不同,作用场景也完全不一样:
SIGHUP(Reload)— 配置热加载
这是大家最熟悉的信号。nginx -s reload 走的是 SIGHUP,Master 收到后的处理流程:
- 重新读取
nginx.conf,校验语法
- 如果配置有问题,Master 直接报错,旧的 Worker 继续服务(这点很关键,不会因为 reload 把服务搞挂)
- 配置没问题的话,根据新配置启动新的 Worker 进程
- 老 Worker 收到关闭信号,等手里的请求处理完再退出
- 新 Worker 开始接受新连接
这个流程就是传说中的「零停机热加载」,是 Nginx 在配置变更场景的核心能力。
SIGUSR1(Reopen)— 日志切割专用
nginx -s reopen 走的是 SIGUSR1,作用是让 Nginx 重新打开日志文件。典型使用场景是配合 Linux 的 logrotate 做日志切割:
# /etc/logrotate.d/nginx 大致写法
/var/log/nginx/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 nginx nginx
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
为什么要单独搞个 reopen?因为 Linux 下文件被打开后,即使你用 mv 把日志移走,进程里的文件描述符还是指向旧 inode,新请求的日志会继续写到被 mv 走的旧文件里。reopen 就是让 Nginx 关闭旧 fd、重新打开同名新文件,确保日志切割后新日志能正确写入。
五、生产环境选型建议(这部分建议收藏)
基于上面四节的原理分析,结合我自己在生产环境的踩坑经验,给出几条可直接落地的建议:
1. 重启/升级 Nginx → nginx -s quit,不是 stop
# 推荐写法
nginx -t && nginx -s quit
nginx -t 先校验配置语法,没问题再 quit。这套组合拳是滚动发布、配置变更、二进制升级(USR2 + QUIT)的标准动作。
2. systemd 环境下,注意默认 KillSignal
CentOS 7+ / Ubuntu 16+ 默认 systemd 管理 Nginx。如果你在 service 文件里没改 KillSignal,systemd 给 Nginx 的默认停止信号是 SIGTERM,等同于 nginx -s stop,不是优雅停止。
推荐在 /etc/systemd/system/nginx.service 或 /lib/systemd/system/nginx.service 里加上:
[Service]
KillSignal=SIGQUIT
TimeoutStopSec=30
TimeoutStopSec=30 给 30 秒的优雅退出窗口,配合 Nginx 自己的 worker_shutdown_timeout 一起用,防止长时间运行的请求被强杀。
3. 容器化部署(Docker/K8s)的特殊情况
容器场景下,Nginx 通常跑在前台(nginx -g "daemon off;"),K8s 终止 Pod 时默认发的是 SIGTERM,Docker stop 默认也是 SIGTERM(10 秒后转 SIGKILL)。
这意味着如果你不改配置,容器里 Nginx 退出时也是粗暴停止。两个解决方案:
- 启动命令改成
nginx -g "daemon off; signal-stop; signal-quit;"(Nginx 1.x 早期版本不支持,需根据实际版本调整)
- 自己写个 entrypoint.sh 捕获 SIGTERM 后转发为 SIGQUIT 再退出
- 在 Dockerfile 里用
STOPSIGNAL SIGQUIT 指令改默认停止信号(Dockerfile 17.05+ 支持)
K8s 侧记得把 terminationGracePeriodSeconds 调大,给 Nginx 足够的退出时间。
4. 紧急情况才用 SIGTERM
机器要关机、Nginx 因为配置错误挂了、Worker 陷入死循环无法响应——这些场景下 kill -TERM 是最快止血手段。但事后一定要复盘:是不是该上配置校验、是不是该加 worker_rlimit_core、是不是该用 stub_status 监控。
六、常见问题 FAQ
Q1:怎么查看当前 Nginx Master 进程的 PID?
cat /var/run/nginx.pid
# 或者
ps aux | grep "nginx: master process"
Q2:执行 quit 之后 Worker 一直没退出,进程还在怎么办?
大概率是某个 Worker 卡在长请求上(文件上传、WebSocket、AI 推理)。先 nginx -s quit 给 30 秒窗口,如果还没退,再 kill -TERM 强杀。如果你经常碰到这种情况,考虑给接口加超时、把 Nginx 上游超时调小,或者在前端做请求大小限制。
Q3:reload 之后老的 Worker 一直不退,正常吗?
正常。reload 走的就是「老 Worker 处理完存量请求再退」的优雅流程。如果老 Worker 长期不退,说明你的接口里有长连接(WebSocket、Server-Sent Events、文件下载)。这种情况是设计上的合理现象,不是 bug。
Q4:QUIT 和 HUP 的「优雅」有什么区别?
QUIT 是「整体下线」前的优雅,所有 Worker 最终都会退出;HUP 是「配置变更」前的优雅,新 Worker 会顶上接管流量,老 Worker 自然消亡。一个是退役,一个是换班。
Q5:能在不重启的情况下单独让某个 Worker 退出吗?
不能。Nginx 没有提供按 Worker 维度单独管理的接口,所有信号都是面向 Master 进程的。如果你需要对单个 Worker 做隔离,得考虑在前面挂负载均衡,把流量摘掉再处理。
Q6:SIGKILL(信号 9)能用吗?
能用,但不建议。SIGKILL 不能被捕获,Nginx 没机会做任何清理——不释放 socket、不关闭连接、不刷日志、不通知上游。生产环境上 SIGKILL 基本上等同于一次小型事故现场,只有在 SIGTERM 都杀不掉进程(极端僵尸状态)时才作为最后手段。
七、最后总结
四种信号,一张图带过:
- SIGTERM(stop):粗暴中断,紧急止血用
- SIGQUIT(quit):优雅退出,正常停机/升级用
- SIGHUP(reload):热加载配置,零停机变更用
- SIGUSR1(reopen):重新打开日志,logrotate 配合用
记住这张表和这套对应关系,下次再遇到 Nginx 进程管理的问题,基本都能直接定位。线上服务无小事,stop 和 quit 用错一次,可能就是一个工单。
标签:Nginx、信号机制、SIGTERM、SIGQUIT、SIGHUP、SIGUSR1、优雅停止、Graceful Shutdown、Linux 运维、Web 服务器、负载均衡、进程模型
来源华强北商行 · 数码科技资讯