僵尸进程源于 cron 启动的子进程未回收,关键在确保被调用程序自身不遗留僵尸:需重定向输出防止 sendmail 卡住、脚本中用 wait 回收后台进程、用 nohup/setsid 脱离 cron 会话、优先迁移到 systemd service 实现自动回收与健壮管理。

配置 Crontab 任务时,僵尸进程通常不是 cron 本身产生的,而是由 cron 启动的子进程(如 shell 脚本、命令链)未正确回收其子进程导致的。关键不在“避免 cron 启动僵尸”,而在“确保被 cron 调用的程序自身不遗留僵尸”。下面从实操角度给出直接有效的配置要点:
重定向所有输出,切断 sendmail 触发链
这是最常见、最易忽略的根源:cron 默认将 stdout/stderr 发送给本地 mail 系统(如 sendmail 或 postfix)。若邮件服务异常或未配置,这些输出会堆积在 /var/spool/clientmqueue/ 或撑爆 /var/mail/root,而 sendmail 进程卡住后常派生出 [sh] <defunct></defunct> 僵尸。
- 每条 crontab 条目末尾必须加上:
> /dev/null 2>&1 - 错误写法:
0 * * * * /usr/local/bin/backup.sh - 正确写法:
0 * * * * /usr/local/bin/backup.sh > /dev/null 2>&1 - 如果脚本内部还调用其他命令(如
ssh、rsync),也建议在脚本内统一重定向,或使用set -o pipefail+exec > /dev/null 2>&1开头
让脚本主动等待子进程结束
如果你的脚本中用了 & 启动后台任务(比如并行执行多个 rsync),父脚本退出前必须调用 wait,否则子进程变成孤儿后可能被 init 收养,但若父脚本没等就退出,子进程退出时状态无法被回收,就会短暂变僵尸。
- 在脚本末尾加一行:
wait - 更稳妥写法:
wait -n 2>/dev/null || true(等待任一后台进程结束,兼容无子进程情况) - 避免在脚本里写
command &后直接exit或自然结束
用 nohup + setsid 替代裸后台启动
单纯加 & 会让进程挂在 cron 的 shell 下,父进程仍是 cron —— 一旦该 shell 因异常退出而未清理子进程,就容易留僵尸。改用 nohup 或 setsid 可让进程脱离 cron 的会话控制,由 init(PID 1)直接收养,它会在子进程退出时自动回收。
- 推荐写法:
0 2 * * * nohup /path/to/script.sh > /dev/null 2>&1 - 更彻底写法:
0 2 * * * setsid /path/to/script.sh > /dev/null 2>&1 防止脚本意外读取 stdin 导致阻塞
优先迁移到 systemd service(长期推荐)
crontab 本质是“按时间触发一次”,不适合长期运行或需进程健壮性保障的任务。对需要稳定驻留、自动重启、资源隔离的场景,应改用 systemd 服务。
- systemd 自动处理 SIGCHLD,父进程无需显式 wait
- 支持
Restart=on-failure、OOMScoreAdjust、LimitNOFILE等精细控制 - 日志统一归集到 journalctl,不再依赖 mail 或文件重定向
- 避免 cron 定时拉起新实例造成进程堆积(比如脚本执行超时,下次又启动一个)











