cron 不运行的原因检查清单:手动能运行却在 cron 中失败的原因与日志查看方法
明明写进了 crontab 的批处理却没有运行。在终端手动执行能成功,交给 cron 却什么都没发生。cron 的故障往往在哪里都不显示错误、悄无声息地失败,排查原因很费时间。本文把 cron 不运行时的检查顺序分为“表达式”“cron 本身”“日志”“运行环境”“文件与权限”“时区”几部分进行整理,并汇总手动能运行而 cron 中失败的典型原因及修复方法。
先区分:是“cron 没启动”还是“命令失败了”
同样是“cron 不运行”,原因大致分为两种。一种是 cron 根本没有在那个时间启动命令;另一种是 cron 按计划启动了,但命令在执行中失败了。两者要查的地方完全不同,所以先加以区分。
- 1
查看日志中是否有启动记录
如果后文介绍的 cron 日志中在那个时间出现了“CMD (命令)”的行,就说明 cron 已经启动,原因在命令一侧。没有这一行的话,就要怀疑表达式、cron 本身或文件的写法。
- 2
加一行每分钟运行的测试
用 crontab -e 临时加上“* * * * * date >> /tmp/cron-test.log 2>&1”,等 1 到 2 分钟,看 /tmp/cron-test.log 中的时间是否不断增加。如果在增加,说明 cron 本身运行正常。确认完毕后务必删除。
- 3
把正式任务的时间改到几分钟后试试
每日或每周的计划,等到下次运行可能要好几天。只在测试期间把时间改到几分钟后,看完结果再改回来,就能马上确认。
因表达式写错而看起来“没运行”的情况
只要表达式本身合法,即使与意图不同,cron 也会毫无怨言地在那个时间运行。以为“没运行”,其实是在意料之外的时间运行了,这种情况并不少见。首先要怀疑表达式本身。
| 写的表达式 | 意图 | 实际行为 |
|---|---|---|
| 0 9 31 * * | 每月最后一天 | 没有 31 日的 2、4、6、9、11 月不运行 |
| 0 9 30 2 * | 2 月最后一天 | 2 月 30 日不存在,所以一次都不运行 |
| 0 9 1-7 * 1 | 第 1 个周一 | 每周一和每月 1 到 7 日都运行(OR) |
| 30 9 * * *(UTC 的服务器) | 日本时间 9:30 | 在日本时间 18:30 运行 |
| 9 30 * * * | 9:30 | 时字段不能写 30,报错而无法读取 |
| * 3 * * * | 每天 3 点运行一次 | 3:00 到 3:59 每分钟运行,共 60 次 |
日期与星期的组合以及月末的写法特别容易出错,详见cron 日期与星期的 OR 规则及第 n 个星期几、月末的写法。把 crontab 的那一行粘贴到 cron 表达式检查器中,它会翻译出含义并列出接下来 10 次的执行时间,可以马上确认“计划在什么时候运行”。
cron 本身的状态与日志的查看方法
表达式正确却没有启动记录时,要确认 cron 服务是否停止、日志中是否有错误。不同发行版的服务名和日志位置不同。
| 要确认的内容 | Ubuntu、Debian | RHEL、CentOS、Rocky、Amazon Linux |
|---|---|---|
| 服务状态 | systemctl status cron | systemctl status crond |
| 启动服务 | sudo systemctl enable --now cron | sudo systemctl enable --now crond |
| 日志(文件) | grep CRON /var/log/syslog | sudo cat /var/log/cron |
| 日志(journal) | journalctl -u cron --since today | journalctl -u crond --since today |
- “(用户名) CMD (命令)”的行:表示在那个时间启动了命令。有这一行就说明 cron 正常
- “(CRON) info (No MTA installed, discarding output)”:命令输出了内容,但没有发送邮件的机制,所以被丢弃了。错误信息很可能就消失在这里
- “(*system*) … WRONG FILE OWNER”“INSECURE MODE”:/etc/cron.d 等处文件的所有者或权限有问题,没有被读取
- “bad minute”“bad hour”“errors in crontab file”:表达式格式错误,该行没有被读取
手动能运行、cron 中却失败的 5 个原因
日志中有启动记录却没有结果,就说明命令在 cron 的环境中失败了。cron 会在与登录时的 shell 不同的、极其精简的环境中执行命令。手动试能成功、cron 却失败,几乎都是这种差异造成的。
| 原因 | 现象 | 修复方法 |
|---|---|---|
| PATH 很短 | command not found(/usr/local/bin 或 ~/.local/bin 中的命令) | 命令写绝对路径,或在 crontab 开头写 PATH= |
| shell 是 /bin/sh | bash 的写法(source、[[ ]]、数组)报错 | 在脚本开头写 #!/bin/bash,作为脚本调用 |
| 不读取 .bashrc 等 | 环境变量(API 密钥或语言版本管理)为空 | 在脚本中设置所需变量,或写在 crontab 开头 |
| 工作目录是主目录 | 找不到相对路径的文件 | 在脚本开头 cd /path/to/dir,或改为绝对路径 |
| % 变成换行 | 命令在 date +%Y%m%d 中途被截断 | 在 crontab 中转义为 \% |
想在与 cron 相同的环境中测试,捷径是像“env -i HOME=$HOME PATH=/usr/bin:/bin SHELL=/bin/sh /bin/sh -c '命令'”这样,在几乎清空环境变量的状态下执行命令。如果这里失败,就可以确定原因是环境差异。
% 的问题不容易察觉,要特别注意。在 crontab 的命令部分,% 的意思是“在此换行,其余部分传给标准输入”。“tar czf /backup/$(date +%Y%m%d).tar.gz /data”这样的行会在 % 之前被截断而失败。可以转义为“date +\%Y\%m\%d”,或者把处理写进 shell 脚本,crontab 只负责调用脚本,这样最可靠。
文件、权限、换行符的陷阱
即使 crontab 的写法本身正确,也可能因文件的放置方式或权限而没有被读取、无法执行。请依次确认以下各项。
- 脚本是否有执行权限:用 ls -l 确认是否带 x,没有的话执行 chmod +x script.sh,或者像 sh script.sh 这样通过 shell 调用
- 第 1 行的 #! 是否正确:#!/bin/bash 的路径是否存在。在 Windows 上编辑的文件行尾是 CRLF,会以“/bin/bash^M: bad interpreter”失败,要用 dos2unix 等转换为 LF
- crontab 最后一行之后是否有换行:有的实现中,最后一行没有换行就会被忽略
- /etc/cron.d 的文件是否有用户名字段:/etc/crontab 和 /etc/cron.d/ 中的文件需要“分 时 日 月 星期 用户名 命令”至少 7 个字段
- /etc/cron.d 的文件名和权限:在 Debian、Ubuntu 中,文件名含 . 的文件(如 backup.cron)不会被读取。所有者要是 root,权限要设为其他用户不能写入(644)
- cron.allow、cron.deny:如果存在 /etc/cron.allow,只有其中列出的用户才能使用 crontab
时区偏差:是不是差了 9 小时在运行
“不运行”的真相其实是“差了 9 小时在运行”,这种情况很常见。cron 按它所在服务器(容器)的时区读取时间。云服务器和容器镜像的默认设置多为 UTC,按日本时间写的时间会晚 9 小时运行。
- 1
确认服务器的时区
执行 date 或 timedatectl,看显示的是 JST 还是 UTC。如果是 UTC,要么用日本时间减 9 小时后的时间来写,要么修改时区。
- 2
修改时区后重启 cron
可以用 sudo timedatectl set-timezone Asia/Tokyo 修改,但已经在运行的 cron 可能仍沿用旧时区。用 sudo systemctl restart cron(RHEL 系为 crond)重启。
- 3
只想让某一行用别的时区时
RHEL 系使用的 cronie,在 crontab 中写 CRON_TZ=Asia/Tokyo 后,其后的行会按指定时区读取。但在 Debian、Ubuntu 的标准 cron 中无效,所以把时间换算成 UTC 来写更可靠。
GitHub Actions、AWS EventBridge、Kubernetes 等在服务器之外读取 cron 表达式的系统也会出现同样的偏差。GitHub Actions 始终是 UTC,请参阅让 GitHub Actions 的 cron 按日本时间运行的方法;EventBridge 请参阅EventBridge cron 表达式的写法。
让下次失败时能马上发现
找到原因并修好后,做好准备让下次再出问题时能马上察觉,可以大大减少排查故障的时间。
- 一定把输出留在日志中:在行末加上 >> /var/log/myjob.log 2>&1,连同错误一起追加记录。为免日志过大,也要设置 logrotate
- 设置 MAILTO:在 crontab 开头写 MAILTO=you@example.com,有输出时就会通过邮件发送(服务器需要有发送邮件的机制)
- 防止重复启动:上次处理还没结束就开始下一次,容易引发问题。像 flock -n /tmp/myjob.lock 命令 这样加锁
- 从外部监控是否成功:在处理的最后访问监控服务的 URL,做“心跳监控”,连没运行的情况也能察觉
- 备份 crontab:定期执行 crontab -l > ~/crontab.bak,保留设置的变更记录
想重新整理 crontab 的写法和符号含义时,crontab 的写法与速查表会有帮助。每次修改设置时都用 cron 表达式检查器确认下次执行时间,就能事先防止“在与预想不同的时间运行”这类问题。
常见问题
- 写进 crontab 却不执行时,首先应该确认什么?
- 首先查看 cron 的日志(Ubuntu 用 grep CRON /var/log/syslog,RHEL 系看 /var/log/cron),确认那个时间是否有 CMD 的行。有的话是命令一侧失败,没有的话要怀疑表达式写错或 cron 服务停止。临时加上“* * * * * date >> /tmp/cron-test.log”,就能马上确认 cron 本身是否在运行。
- 为什么手动能运行,cron 中却不运行?
- 因为 cron 在 PATH 只有 /usr/bin:/bin 左右的精简环境中,使用 /bin/sh,也不读取 .bashrc 等文件来执行命令。命令写绝对路径、在脚本中设置所需的环境变量、cd 到工作目录、把 % 转义为 \%,这些做法几乎都能解决问题。
- cron 的日志在哪里查看?
- Ubuntu、Debian 记录在 /var/log/syslog 中,用 grep CRON /var/log/syslog 筛选。RHEL、CentOS、Rocky Linux 等则在 /var/log/cron。systemd 环境中也可以用 journalctl -u cron(RHEL 系为 -u crond)查看。
- cron 的执行时间为什么差了 9 小时?
- 因为 cron 按服务器的时区读取时间,而那台服务器设置为 UTC。日本时间比 UTC 快 9 小时,所以按日本时间写的时间会晚 9 小时运行。可以把时区改为 Asia/Tokyo 并重启 cron,或者换算成 UTC 的时间来写。
- 放在 /etc/cron.d 的文件不运行。
- /etc/cron.d 的文件在 5 个时间字段和命令之间需要执行用户名的字段。此外,在 Debian、Ubuntu 中文件名含 . 就不会被读取,所有者不是 root 或其他用户可写入的权限也会被忽略。也请确认日志中是否有 WRONG FILE OWNER 或 INSECURE MODE 的提示。
本文介绍的工具
相关文章
让 GitHub Actions 的 cron 按日本时间运行:UTC 换算速查表与星期偏移、延迟的对策
在 GitHub Actions 的 schedule 里写了“0 9 * * 1-5”,工作流却在日本时间傍晚 6 点才运行。这并不是写错了,而是因为 GitHub Actions 的 cron 一律按 UTC(协调世界时)解读。本文整理了把日本时间(JST)换算成 UTC cron 表达式的方法和速查表、指定早上 0 点到 8 点时出现的“星期和日期退回前一天”问题、一行写不下时如何拆成两行,以及 schedule 延迟或不运行的原因与对策。
阅读文章 →crontab 的写法与速查表:5 个字段与符号的含义、常用示例和注册命令
“每天 3 点备份”“每 5 分钟运行一次监控脚本”这类定时任务,在 Linux 上只要在 crontab 里写一行就能设置。不过“分 时 日 月 星期”这 5 个字段与 * , - / 这些符号的组合,刚开始很容易看错,常会出现 1 小时运行 60 次、本想每 7 分钟一次却出现 4 分钟间隔之类的事故。本文以示例为主,讲解 crontab 写法的基础和符号含义、可直接套用的速查表、crontab -e、-l、-r 的用法,以及编写命令部分时的注意事项。
阅读文章 →cron 的日期与星期是 OR 关系:第 2 个周一、月末、最后一个周五、隔周的正确写法
想表示“每月第 1 个周一 9 点”而写成“0 9 1-7 * 1”,结果除了每周一之外,每月 1 到 7 日每天都运行了。cron 中这个非常常见的失误,源于 cron 特有的规则:同时指定日期和星期时,只要符合“其中之一”就会运行。本文结合实际执行日期的示例,讲解日期与星期构成 OR 的机制及其例外,在 crontab 中正确运行第 n 个星期几、月末、最后一个周五、隔周的写法,以及 AWS EventBridge 和 Spring 中可用的 L、#、W 的写法。
阅读文章 →