-今日
-本周
-本月
-累计
项目工作台运维 个人项目与实践记录

故障复盘 #001:nginx 崩溃后服务无法自动恢复

服务中断

日期2026-09-13
环境Alibaba Cloud Linux 3.2104 / ECS 2 vCPU 2 GB / 华北1(青岛)
组件nginx 1.24.0(alinux3-updates 仓库)
影响进程被杀后站点持续不可用,需人工介入才能恢复
发现方式主动故障注入测试(kill -TERM 主进程)
严重级别中(无自动恢复能力,2 GB 内存机器 OOM 风险高,实际会发生)
处理耗时约 15 分钟
项目内容
日期2026-09-13
环境Alibaba Cloud Linux 3.2104 / ECS 2 vCPU 2 GB / 华北1(青岛)
组件nginx 1.24.0(alinux3-updates 仓库)
影响进程被杀后站点持续不可用,需人工介入才能恢复
发现方式主动故障注入测试(kill -TERM 主进程)
严重级别中(无自动恢复能力,2 GB 内存机器 OOM 风险高,实际会发生)
处理耗时约 15 分钟

1. 现象

在部署完 nginx 并确认站点正常(HTTP 200)后,做了一次"服务自愈能力"验证: 主动 kill 掉 nginx master 进程,观察 systemd 是否会自动拉起。

结果:

当前 master PID: 15009
kill 之后 master PID: (空)
HTTP 000          ← 连接失败

服务没有恢复。 手动执行 systemctl start nginx 后立即恢复正常,说明 nginx 本身和配置都没有问题,问题出在服务管理器没有做恢复动作

2. 排查过程

2.1 确认 systemd 对 nginx 的生效策略

$ systemctl show nginx -p Restart -p RestartSec
Restart=no
RestartSec=100ms        # 该值此时无意义,因为 Restart=no

关键点:Restart=no 表示 systemd 不会因进程意外退出而重启服务

2.2 确认这是发行版默认行为,不是配置写错

$ systemctl cat nginx | grep -E '^Restart='
(无输出)

查看原始 unit 文件 /usr/lib/systemd/system/nginx.service 的完整关键字段:

Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload

该 unit 完全没有 Restart= 指令。 在 systemd 中,Restart 未设置时默认值为 no

注意:ExecStartPre=/usr/sbin/nginx -t 只保证启动前配置语法正确, 它和"进程崩了要不要重启"没有任何关系。这两个概念极易混淆。

3. 根因

nginx 的 systemd unit 由发行版(阿里云 Alibaba Cloud Linux)提供,未声明任何重启策略, 因此 systemd 采用默认值 Restart=no

后果:任何导致 nginx 主进程退出的情况都不会被自动恢复,包括:

服务会一直处于 inactive (dead),直到有人发现并手动 systemctl start

4. 修复方案

采用 systemd drop-in 覆盖,而不是直接修改 /usr/lib/systemd/system/nginx.service

选择 drop-in 的原因:

方案问题
直接改 /usr/lib/systemd/system/nginx.servicenginx 包升级时会被覆盖,修复静默丢失
/etc/systemd/system/nginx.service 放完整副本同样会被版本更新"冻结",且与厂商版本脱节
/etc/systemd/system/nginx.service.d/override.conf只覆盖差异字段,可随包升级继承其余改进 ✅

创建文件 /etc/systemd/system/nginx.service.d/override.conf

[Service]
Restart=always
RestartSec=2s
TimeoutStartSec=30s
TimeoutStopSec=10s

各字段含义:

对 nginx 这类"应当长期存活"的服务,也可以用 on-failure;但 always 能覆盖 "有人手动 kill 后希望它自己回来"的场景,对无状态 Web 服务更合适。

秒级疯狂重启,把 journal 刷爆;2 秒给了一个缓和窗口。

生效步骤:

systemctl daemon-reload      # 必须!否则 systemd 不会读取新 unit 配置
systemctl show nginx -p Restart      # 验证:Restart=always
systemctl reload nginx               # 不中断服务地应用

5. 验证

不是"改完就算",而是再次真实注入故障来证明修复有效。

$ OLD=$(cat /run/nginx.pid); echo $OLD
15154

$ kill -KILL "$OLD"          # SIGKILL,模拟 OOM Killer 的不可捕获终止
$ sleep 5
$ cat /run/nginx.pid
15259                        # ← PID 已变化,说明被重新拉起

$ systemctl status nginx | head -6
     Active: active (running) since Sun 2026-09-13 15:00:01 CST; 2s ago
    Drop-In: /etc/systemd/system/nginx.service.d
             └─override.conf

连续可用性探测:

第 1 次探测: HTTP 200
第 2 次探测: HTTP 200
第 3 次探测: HTTP 200

修复确认有效。 使用 SIGKILL 而非 SIGTERM 是刻意的: SIGTERM 可以被进程捕获并优雅处理,而 SIGKILL 不可捕获, 更接近 OOM Killer 和 segfault 的真实场景。

6. 遗留风险与后续改进

nginx 若因配置错误起不来,systemd 会每 2 秒重试一次。需要配套告警 才能发现"服务在无限重启"。→ 后续引入 StartLimitBurst 限制 + 巡检告警。

→ 后续写脚本批量检查所有 unit 的 Restart 策略。

如果是生产环境,这将是一次静默故障。 → 后续建立可用性探测 + 告警(这正是 MTTD 指标的意义)。

7. 本次沉淀的通用经验

  1. enabled 不等于"会自愈"。

systemctl is-enabled nginx 返回 enabled 只表示开机时会启动, 完全不代表运行中崩溃后会恢复。这是两个独立机制,极易混淆。

  1. ExecStartPre 的语法检查 ≠ 运行时保护。

它只在启动那一刻执行一次。

  1. 改 systemd 配置后必须 daemon-reload

少这一步,systemctl show 看到的仍是旧值——很容易误判"改了没生效"。

  1. 验证修复要用故障注入,而不是读配置。

systemctl show nginx -p Restart 输出 always 只能说明"配置写对了", 不能说明"行为符合预期"。本次用 SIGKILL + PID 变化 + HTTP 探测三重确认。

  1. 优先用 drop-in 而非改原厂文件。

这是"改动可叠加、可继承、可回滚"的运维原则。


附:可复用的检查命令

# 一次性检查所有服务的重启策略(找出没有自愈能力的服务)
for u in $(systemctl list-units --type=service --state=running --no-legend | awk '{print $1}'); do
  printf '%-40s %s\n' "$u" "$(systemctl show "$u" -p Restart --value)"
done | sort -k2

这条命令的思路:不假设,去枚举。 本次故障的价值不在于修好 nginx, 而在于发现了"我们从未检查过服务的自愈能力"这个系统性盲区。