计划任务排查最怕两件事:一是漏看,二是看完就动手。开始前,先界定影响范围和需要观察的业务路径:站点首页与后台是否可访问、登录与上传是否正常、PHP-FPM 与数据库连接是否正常、备份任务是否照旧产出文件、证书续期与日志轮转是否按点执行、缓存清理和临时文件回收是否仍在跑。这些路径里,任何一条依赖 cron,本篇文章的改动都可能影响它。下面的操作都假定你在已获授权的服务器上执行,范围只到“盘点、判断、小步锁定、验证回滚”。

一、先盘点:计划任务的来源不止一个 crontab -l
很多人只执行 crontab -l 就以为查完了,这只覆盖当前用户。linux 上至少还有五类来源,必须逐类过一遍。
| 来源 | 路径 | 格式要点 | 常见遗漏 |
|---|---|---|---|
| 系统 crontab | /etc/crontab | 比用户 crontab 多一个“执行用户”字段 | 只当配置文件看,没逐行核对用户列 |
| cron.d 片段 | /etc/cron.d/* | 同样是六段式(含用户字段) | 软件包安装时新增的片段没人复核 |
| run-parts 目录 | /etc/cron.hourly、daily、weekly、monthly | 放可执行脚本,文件名通常不能带点 | 脚本内容被替换,目录列表却没变 |
| 用户 crontab | /var/spool/cron/(RHEL 系)或 /var/spool/cron/crontabs/(Debian/Ubuntu) | 五段式,无用户字段 | 只有 root 能 crontab -l -u 逐个看 |
| 其他调度器 | /etc/anacrontab、systemd timer、at 队列 | 各自独立语法 | 攻击者改用 timer 或 anacron 落持久化 |
一次跑完的盘点命令(在 root 下执行,全部为只读操作):
# 系统级
ls -la /etc/crontab /etc/cron.d/ 2>/dev/null
ls -la /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly 2>/dev/null
cat /etc/anacrontab 2>/dev/null
systemctl list-timers --all --no-pager
# 用户级:逐个账号导出
for u in $(cut -d: -f1 /etc/passwd); do
out=$(crontab -l -u "$u" 2>/dev/null)
[ -n "$out" ] && printf '===== %s =====n%sn' "$u" "$out"
done
# 直接看落盘文件,留意 mtime 与属主
ls -la /var/spool/cron/ /var/spool/cron/crontabs/ 2>/dev/null预期现象:输出应当能和你手上的变更记录对上号。如果出现“文件修改时间很近,但最近没人提过变更单”的条目,就把它标出来,进入下一步判读,先别删。
盘点完立刻留一份基线快照,放到本机之外(堡垒机、日志平台或运维仓库)。之后排查时,直接 diff 基线即可定位新增内容:
mkdir -p /root/audit && cd /root/audit
tar czf cron-$(date +%F).tar.gz /etc/crontab /etc/cron.d /etc/cron.*/ /var/spool/cron 2>/dev/null
sha256sum cron-$(date +%F).tar.gz > cron-$(date +%F).sha256
lsattr -R /etc/cron.d /etc/crontab > cron-attrs-$(date +%F).txt 2>/dev/null注意 lsattr 那一步:如果发现某个 cron 相关文件已经带着 i 属性,而你的变更记录里没有这一笔,说明之前有人(或某个程序)已经锁过,需要先查清来源再谈后续操作。
二、判读:业务任务与异常任务的区分依据
凭“看着奇怪”下结论,误删业务任务是这类巡检最常见的事故。可靠的判据是三条同时成立:有明确归属人、有可追溯的变更来源、命令链落到可信路径。
先做一轮关键词筛选,把可疑条目缩到十几条以内再看细节。这只是筛查,不是结论:
grep -REn "wget|curl|base64|/dev/tcp|nc |ncat|bash -i|python -c|perl -e|chmod +x|/tmp/|/dev/shm|@reboot"
/etc/crontab /etc/cron.d /etc/cron.*/ /var/spool/cron/ 2>/dev/null| 特征 | 业务任务的常见形态 | 需要进一步核实的形态 |
|---|---|---|
| 命令来源 | 指向 /usr/local/bin、/opt/应用目录 下的自有脚本 | 一段内联的 `curl ... |
| 网络行为 | 明确的内网备份地址、对象存储域名 | 陌生公网 IP、非常用端口、/dev/tcp/ 直连 |
| 执行频率 | 每天、每小时、每 5 至 15 分钟 | 每分钟一次的高频任务,或只写 @reboot |
| 路径与权限 | 绝对路径,属主为 root 或应用账号,权限规整 | 藏在 /tmp、/dev/shm、.cache 之类目录,文件名带点或伪装成系统名 |
| 可维护性 | 有注释、有日志输出、纳入了配置管理 | 无注释、无日志,或与同名业务任务并存但内容不同 |
| 副作用 | 产出可核对的备份、报表、清理结果 | 只做“保活”、反复改写自身或其他 cron 文件 |
判定为可疑但不确定的条目,处理顺序是:先注释掉(在行首加 # 并写上日期和操作人),保留原文件备份,观察一到两个周期。直接删除会丢掉溯源线索,也可能在没弄清依赖前打断业务。
还有一种情况要单独提:cron 条目本身看起来正常,但它调用的脚本内容被换过。所以盘点不能只看 cron 文件,凡是 cron 调用的脚本,都要核对一次属主、权限和修改时间。
三、小步锁定:chattr +i 的适用边界
chattr +i 给文件加上 immutable 属性后,包括 root 在内的任何账号在不解锁的情况下都无法修改、重命名或删除它。它适合当“止血”和“防回归”手段,不适合当清除手段——已经写进去的恶意条目不会因此消失,正在运行的进程也不会因此停止。
先明确几个前提:
- 需要 root 权限,且文件系统要支持该属性(ext4、xfs 支持;部分网络文件系统行为不同),以
lsattr的实际输出为准。 - 给目录加
i会阻止在该目录内新建、重命名、删除文件,但不会阻止修改目录内已有文件的内容。要保护文件本身,得对文件单独加锁。 - 锁定会挡住合法的变更。
crontab -e在用户 crontab 被锁后会写入失败,软件包安装往/etc/cron.daily投放脚本时也会失败。这些都不是故障,而是预期行为,但必须写进变更流程。
建议的锁定范围按下表控制,从变更频率最低的对象开始,一次只锁一类:
| 对象 | 变更频率 | 建议 | 说明 |
|---|---|---|---|
/etc/crontab | 低 | 可以锁 | 系统级总表,改动少,收益直接 |
/etc/cron.d/ 下单个业务条目 | 低 | 可以锁(文件级) | 优先锁具体文件,先别锁整个目录 |
/etc/cron.d/ 目录 | 低 | 谨慎 | 目录锁会挡住新增片段,装软件前需临时解锁 |
/etc/cron.daily 等 run-parts 目录 | 中 | 不建议长期锁 | logrotate、证书续期、索引更新等包管理动作会写入 |
/var/spool/cron/root | 中 | 谨慎 | 锁后 crontab -e 失效,需配套解锁流程 |
/etc/anacrontab | 低 | 视发行版 | 确认无包管理写入后再锁 |
单文件锁定的操作与验证:
# 1. 记录锁定前状态,便于回滚比对
lsattr /etc/crontab /etc/cron.d/* 2>/dev/null | tee /root/audit/before-lock.txt
# 2. 备份
cp -a /etc/crontab /root/audit/crontab.$(date +%F).bak
# 3. 加锁
chattr +i /etc/crontab
# 4. 确认属性已生效(应看到 i 标志)
lsattr /etc/crontab预期现象:lsattr 输出的属性位中出现 i;此后任何写入该文件的尝试都会返回 Operation not permitted。锁完不要马上锁第二个对象,先走完下一节的验证。
四、变更与回滚清单
把每次锁定当成一次正式变更来对待,清单越短越容易执行到位。
变更前:
- 已导出基线快照与
lsattr记录,并存放到本机之外。 - 已确认目标文件当前的 cron 条目全部能对应到业务或系统组件。
- 已确认没有正在进行的安全处置需要临时改写该文件。
- 已通知相关业务负责人本次变更的观察窗口。
变更中:
- 每次只锁一个对象,锁完立刻
lsattr复核。 - 在变更记录里写下锁定时间、对象、执行人、解锁条件。
回滚动作(按需执行,先解锁再改内容,改完视情况重新锁定):
chattr -i /etc/crontab # 单文件解锁
chattr -i /etc/cron.d # 目录解锁
chattr -i /var/spool/cron/root # 用户 crontab 解锁
# 校验是否已恢复可写
lsattr /etc/crontab
touch /etc/crontab.test 2>/dev/null || true如果回滚后仍然写不进去,先看 lsattr 是否还有残留属性,再确认文件系统是否只读、是否被 SELinux 上下文或挂载选项限制。
五、验证:锁完业务是否照常
验证要覆盖三个层面:cron 守护进程还在正常调度、任务确实按点执行了、业务功能没有被间接影响。
先看调度日志,确认没有因为文件不可写而报错:
# Debian/Ubuntu
journalctl -u cron --since "-24h" --no-pager | grep -i -E "cron|error" | tail -50
# RHEL/CentOS
journalctl -u crond --since "-24h" --no-pager | tail -50
grep CRON /var/log/cron 2>/dev/null | tail -50正常现象是日志里能看到 (root) CMD (...) 之类的执行记录。如果出现与权限相关的错误反复刷屏,说明锁定挡住了某个仍在尝试写入的进程,需要先查清是谁在写。
再看任务的实际产出,这比日志更可信:
| 验证项 | 方法 | 期望结果 |
|---|---|---|
| 备份任务 | 查看备份目录最新文件的时间戳与体积 | 时间在本周期内,体积无明显异常 |
| 站点可用性 | 访问首页、登录后台、上传一个小文件 | 状态码正常,无 5xx,上传成功 |
| 证书续期 | certbot renew --dry-run(如使用 certbot) | 演练通过,未报 cron 相关错误 |
| 日志轮转 | logrotate -d /etc/logrotate.conf | 调试输出无致命错误 |
| 用户 crontab 可写性 | 用被测账号执行 crontab -e 后直接退出 | 若已锁定,应明确报错;这是预期,记录在案 |
观察窗口按最低频任务的一个完整周期来定:小时级任务看 2 小时以上,每日任务至少看满 24 小时,每周任务建议看满 7 天。窗口内不要叠加其他 cron 相关变更,否则出问题无法归因。

六、复盘:把一次性巡检变成常态基线
锁定只是最后一步,真正降低风险的,是让计划任务的变化始终可见。
- 把基线快照纳入固定流程,每周重新生成一次并比对差异;差异项必须有对应变更单才能关闭。
- 用 auditd 监控关键路径的写入事件,把改动留痕:
auditctl -w /etc/cron.d -p wa -k cron_change,并把/etc/crontab、/var/spool/cron一并纳入。规则要写进持久化配置,重启后仍生效。 - 把“cron 变更需先解锁、变更后重新锁定并验证”写进运维手册,避免下一个人直接对锁定的文件执行
crontab -e后误判为系统故障。 - 记住
chattr +i的定位:它提高的是篡改成本,不是不可逾越的屏障——有 root 权限的人依然可以解锁。它必须和日志监控、文件完整性校验、最小权限账号管理一起用,才构成完整的防护。
最后留一条判断准则:任何一次锁定,如果说不清“锁的是哪个文件、谁会因此改不了、出问题怎么在五分钟内解锁”,就先不要锁,回去把范围缩小到单个文件再动手。
- crontab
- it2021
- it2021.com
- test
- 日志监控
- 溯源





1F
crontab -l 真的不够,cron.d 也得过一遍