nohup无法防止SIGKILL、SIGTERM或OOM killer终止进程;根本原因是其仅屏蔽SIGHUP,缺乏重启、监控和资源隔离能力,需配合Supervisor等进程管理工具实现可靠运行。

为什么nohup php script.php & 还是会被杀?
根本原因不是命令写得不对,而是 nohup 只能屏蔽 SIGHUP,对 SIGKILL、SIGTERM、OOM killer 完全无效。一旦系统内存不足、管理员执行 kill -9、或内核触发 OOM killer,进程照样被干掉——nohup 不提供重启、状态监控、资源隔离能力。
常见错误现象包括:
- 脚本运行几小时后突然消失,
ps aux | grep php查不到 -
dmesg | grep -i "killed process"显示类似Killed process 12345 (php) score 892 - 日志里没有 PHP 错误,
register_shutdown_function也完全没触发
Supervisor 启动 PHP 进程的关键配置项
Supervisor 不是“让进程不死”,而是“死了一秒内拉起来 + 记录退出原因”。必须显式设置以下几项,否则等于白装:
-
autostart=true:开机/重载配置后自动启动 -
autorestart=unexpected:只在非预期退出(如崩溃、被 kill)时重启;设为true会导致正常 exit 也被反复拉起 -
startretries=3:启动失败最多重试 3 次,避免无限循环卡住 -
redirect_stderr=true和stdout_logfile=/var/log/supervisor/myphp.log:必须配,否则看不到 PHP 的echo或 fatal error -
user=www-data:指定运行用户,避免以 root 启动带来的权限和安全风险
示例最小可用配置(存为 /etc/supervisor/conf.d/myworker.conf):
立即学习“PHP免费学习笔记(深入)”;
[program:myworker] command=/usr/bin/php /var/www/myapp/worker.php directory=/var/www/myapp user=www-data autostart=true autorestart=unexpected startretries=3 redirect_stderr=true stdout_logfile=/var/log/supervisor/myworker.log
PHP 脚本自身要配合 Supervisor 做什么?
Supervisor 管的是进程生命周期,但 PHP 脚本若不配合,可能造成重复消费、状态错乱、文件句柄泄漏等问题:
- 避免在脚本开头用
pcntl_fork()或pcntl_signal()干预信号处理——Supervisor 依赖标准信号(如 SIGTERM)优雅终止进程 - 必须在主循环中定期检查
pcntl_signal_dispatch(),否则SIGTERM会被忽略,Supervisor 等超时后只能发SIGKILL - 不要用
sleep(0)或空while(1)循环,应加明确的退出判断(比如检查某个 flag 文件是否存在) - 所有外部资源(数据库连接、Redis、文件锁)需支持重连或自动释放,不能假设进程永生
OOM killer 杀进程时 Supervisor 也救不了?
是的。OOM killer 是内核级强制终结,不走用户态信号机制,Supervisor 根本收不到通知,只能等下次检测到进程不存在再拉起——这中间存在时间窗口。
真正有效的应对方式是组合使用:
- 调低该 PHP 进程的 OOM 分数:写入
/proc/<pid>/oom_score_adj</pid>,例如echo -500 > /proc/$(pgrep -f "worker.php")/oom_score_adj - 限制内存上限:用 cgroup v2 或 systemd scope 包裹 Supervisor 子进程(如果 Supervisor 本身由 systemd 托管,可直接在
supervisord.service中加MemoryMax=512M) - 在 PHP 脚本中主动监控内存:
memory_get_usage(true)+gc_collect_cycles(),接近阈值时exit(0)主动交还控制权,让 Supervisor 平滑重启
最易被忽略的一点:Supervisor 自身的 supervisord 进程若被 OOM killer 干掉,整个管理就失效了——务必给 supervisord 单独设更低的 oom_score_adj,并用 systemd 的 Restart=always 双重兜底。



















