如何阻止PHP shell_exec派生的后台进程依赖Apache/PHP-FPM服务
高版本PHP-FPM、Apache2(尤其是Event MPM模式)新增了进程组生命周期管控逻辑:服务重启、工作进程退出时,会向当前工作进程派生的整个子进程组发送终止信号,回收所有衍生进程,避免孤儿进程泄露资源。
旧版本仅终止直接子进程,后台进程会因父进程退出被1号进程(init/systemd)领养,因此可以常驻;你之前尝试的末尾加&、重定向输出,仅把进程放到当前Shell的后台,没有脱离原有会话和进程组,自然会被连带终止。网上流传的nohup方案也不生效——nohup仅忽略SIGHUP信号,无法抵御进程组级别的SIGTERM。
方案1:setsid 脱离进程组(无额外依赖,全环境通用)
通过setsid命令让待启动的进程创建全新会话、成为独立进程组组长,彻底脱离PHP/Apache的进程管控链,同时完成三个标准流的全重定向,避免句柄牵连。
对应你复现问题的screen场景,正确调用写法如下:
shell_exec("setsid screen -dmS testing bash -c 'while true; do echo test && sleep 1; done' < /dev/null > /dev/null 2>&1 &");
验证标准:执行接口触发启动后,执行ps -ef | grep testing查看进程,若进程的PPID(父进程ID)为1,说明已经成功脱离管控,此时重启PHP-FPM/Apache进程不会受影响。
方案2:systemd托管(Systemd发行版推荐,稳定性最高)
如果服务器是使用Systemd的主流Linux发行版(CentOS7+、Ubuntu16.04+、Debian9+等),可以直接通过systemd-run把进程交给Systemd托管,进程生命周期完全和PHP/Apache服务解耦,不会被连带终止:
// 跑用户级服务,不需要root权限 shell_exec("systemd-run --user --remain-after-exit --unit=testing_task bash -c 'while true; do echo test && sleep 1; done'");
如果需要跑系统级服务,只需要给PHP运行用户配置对应sudo权限,允许其无密码执行指定的systemd-run命令即可,不要给全量sudo权限避免安全风险。
- 所有启动命令必须显式重定向
stdin/stdout/stderr三个流到/dev/null或指定日志文件,哪怕用了setsid,只要有流句柄挂载在PHP进程的连接上,仍可能出现进程被牵连终止的情况。 - 不要依赖
screen/tmux自带的后台参数实现脱离,这类工具默认不会主动脱离父进程的进程组,必须搭配setsid或systemd托管使用。 - 若使用Apache mod_php模式,可额外检查Apache配置中是否开启
KillAllChildrenOnRestart参数,若开启建议关闭,进一步降低进程被误杀的概率。
内容的提问来源于stack exchange,提问作者Mooseh

