crontab 定时任务不执行排查实战:12 个常见坑与修复

crontab 定时任务不执行是 Linux 运维最高频的故障之一:脚本在终端手动能跑,写进 cron 却静默失败,日志里什么都没有。本文按”现象 → 定位 → 修复”的顺序,拆解 12 个最常见、也最容易踩的坑,并给出一套可直接照做的排查清单。

一、先确认:任务到底有没有跑

排查的第一步不是改配置,而是确认 cron 服务本身在不在、任务有没有被调度。很多”不执行”其实是服务没启动,或任务被写到了错误的用户表里。

# 查看 cron 服务状态(Debian/Ubuntu)
systemctl status cron
# CentOS/RHEL 用 crond
systemctl status crond

# 列出当前用户的任务
crontab -l
# 查看某用户(需 root)
crontab -l -u www-data

# 看 cron 自己的执行日志(路径因发行版而异)
grep CRON /var/log/syslog
journalctl -u cron --since "1 hour ago"

如果日志里压根没有对应任务的记录,说明任务没被加载或 cron 没运行;如果有记录但仍失败,往下看具体坑。

二、坑 1:PATH 环境变量缺失(最高频)

cron 执行环境极其”干净”,默认 PATH 通常只有 /usr/bin:/bin。你在终端能用的 python3、node、docker 在 cron 里很可能找不到,于是脚本第一行就报 command not found 然后退出。建议把 PATH 写成一行放在 crontab 文件最顶部,这样所有任务共享同一套查找路径,避免逐个脚本补绝对路径带来的疏漏。

# 在 crontab 顶部显式声明 PATH
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /opt/scripts/backup.sh

# 或者脚本内部用绝对路径
*/5 * * * * /usr/bin/python3 /opt/scripts/backup.py

三、坑 2:相对路径与当前工作目录

cron 的工作目录默认是用户的 HOME(通常是 / 或 /root),不是脚本所在目录。脚本里写的 ./config.yaml、data/ 都会找不到。

# 在脚本开头 cd 到确定目录
*/5 * * * * cd /opt/scripts && /usr/bin/python3 backup.py

# 或在脚本里用绝对路径读取资源
CONFIG = "/opt/scripts/config.yaml"

四、坑 3:权限与用户身份

crontab -e 编辑的是当前用户的任务;root 的任务和用户 www-data 的任务互相隔离。脚本文件本身的权限、属主也要匹配,否则会报 Permission denied。

# 给脚本可执行权限
chmod +x /opt/scripts/backup.sh
# 确认属主
ls -l /opt/scripts/backup.sh
# 以正确用户身份安装任务
sudo crontab -e -u www-data

五、坑 4:Shell 差异(sh 而非 bash)

很多发行版的 cron 用 /bin/sh 执行任务,而 /bin/sh 往往指向 dash,不支持 bash 专属语法(如 source、数组、[[ ]])。脚本在 bash 里正常,进 cron 就报语法错。

# 在 crontab 顶部指定 shell
SHELL=/bin/bash
# 或脚本首行用 shebang 并显式调用
*/5 * * * * /bin/bash /opt/scripts/backup.sh

六、坑 5:输出与 MAILTO 把错误”吞掉”

cron 默认会把任务的标准输出/错误以邮件发给用户,但服务器通常没配邮件,于是错误信息凭空消失,你只看到”没执行”。这是排查时最坑的一点。

# 把输出重定向到日志,错误也一并收集
*/5 * * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# 或在 crontab 顶部设定收件人
MAILTO="admin@example.com"

七、坑 6:脚本没有解释器或不可执行

忘记写 shebang(#!/bin/bash),或文件没有可执行位,cron 就会以默认 shell 去”读”它,结果一团乱。务必保证脚本首行 shebang 正确且带 +x。

八、坑 7:% 符号的特殊含义(date 命令重灾区)

crontab 里 % 是换行/命令分隔符,必须转义。很多人写 date +%Y%m%d 直接失败,就是因为 % 被 cron 当成特殊字符。

# 错误写法:% 未转义
* * * * * /bin/echo $(date +%Y%m%d) >> /tmp/t.log
# 正确写法:反斜杠转义
* * * * * /bin/echo $(date +\%Y\%m\%d) >> /tmp/t.log

九、坑 8:时区与系统时间不对

cron 按系统本地时区调度。如果服务器时区是 UTC 而你以为是北京时间,任务就会”差 8 小时执行”,看起来像没执行。容器环境尤其容易出现时区错位。

# 查看当前时区
timedatectl
# 确认 cron 重读时区(改完时区后重启 cron)
systemctl restart cron

十、坑 9:任务未加锁,互相覆盖或重复跑

任务执行时间超过调度间隔,或上一次还没结束就被再次触发,会造成资源争用、数据错乱。用 flock 加一把文件锁最省心。

# 同一时刻只允许一个实例运行
* * * * * /usr/bin/flock -n /tmp/backup.lock /opt/scripts/backup.sh
# 最多等待 10 秒拿锁,拿不到就跳过
* * * * * /usr/bin/flock -w 10 /tmp/backup.lock /opt/scripts/backup.sh

十一、坑 10:资源限制(磁盘满、文件描述符耗尽)

当系统磁盘写满,或进程打开的文件描述符耗尽时,任何需要写日志、建连接的任务都会失败。这类问题往往和 cron 本身无关,却表现为”任务不执行”。因此排查清单里专门把磁盘与 inode 检查单列一步,能快速排除这类干扰,避免在 cron 配置上白费力气。

# 检查磁盘剩余
df -h
# 检查 inode 是否耗尽
df -i
# 查看某进程打开的文件数
ls /proc/$(pgrep backup.sh)/fd | wc -l

十二、坑 11:SELinux / 容器 / systemd 环境隔离

开启了 SELinux 的机器可能拦截 cron 访问某些文件;容器里想用宿主机 cron 也常常因环境隔离而失效。更稳妥的做法是把定时任务交给容器编排或 systemd timer,而不是依赖容器内裸 cron。

十三、坑 12:cron 服务本身没启动或被禁用

最后也是最容易忽略的一点:cron 守护进程根本没运行,或新装系统后没开机自启。先确认服务状态(见第一节),必要时 systemctl enable --now cron。

十四、标准排查清单(照着做)

步骤命令 / 动作预期
1. 服务在跑?systemctl status cronactive (running)
2. 任务已加载?crontab -l能看到你的任务
3. 有执行记录?grep CRON /var/log/syslog出现对应任务行
4. 输出去哪了?任务加 >> log 2>&1日志里有报错
5. 环境问题?任务首行打印 env对比终端 PATH
6. 资源够吗?df -h / df -i磁盘与 inode 有余量

十五、12 个坑速查表

坑一句话修复
PATH 缺失命令找不到crontab 顶部声明 PATH 或用绝对路径
相对路径资源读不到脚本里 cd 到绝对目录
权限/用户Permission deniedchmod +x、用对应用户安装
Shell 差异bash 语法报错SHELL=/bin/bash 或显式调 bash
输出被吞无日志无报错重定向到文件 + MAILTO
% 未转义date 等命令失败用 \% 转义
时区不对差几小时执行统一时区并重启 cron
未加锁重复/覆盖跑flock 文件锁
资源耗尽写不进 / 连不上查磁盘、inode、文件描述符
环境隔离容器/SELinux 拦改 systemd timer 或编排
服务没起根本没调度enable –now cron

十六、小结

crontab 定时任务不执行,九成以上都逃不出”环境、权限、输出、路径”这四类。养成两个习惯即可少走弯路:第一,任务一律把输出重定向到日志;第二,脚本里用绝对路径、显式声明 PATH 与 SHELL。把上面的清单存成模板,下次再遇到 cron 沉默,照着走一遍就能定位。对于关键业务任务,建议叠加外部监控(如心跳文件加巡检脚本),即便 cron 静默失败也能被及时发现,而不是等到业务方投诉才后知后觉。更系统的开发环境排错思路也可以一并参考。

上一篇 Spring Boot 自动配置原理与自定义 Starter 实战
下一篇 数据脱敏实战:敏感数据识别、加密与动态脱敏方案
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]