Cron 表达式写错就是生产事故:8 个能抄的例子 + 5 个必踩的坑

定时任务这东西,写对了没人记得,写错了一定上事故复盘会。

下面这些坑我基本都在生产环境见过:有的是任务没跑,有的是跑了三遍,有的是半年后磁盘告警。

一、先把格式记牢

标准 Linux crontab 是 5 段,Spring / Quartz 是 6 段(多了秒):

# Linux crontab:5 段
*  *  *  *  *  command
│  │  │  │  │
│  │  │  │  └── 星期 (0-7,0 和 7 都是周日)
│  │  │  └───── 月   (1-12)
│  │  └──────── 日   (1-31)
│  └─────────── 小时 (0-23)
└────────────── 分钟 (0-59)

# Spring @Scheduled:6 段(首位是秒)
0 0 2 * * *

最容易犯的错:从 Spring 那边抄一个 0 */5 * * * * 到 crontab,直接报「bad minute」——多了一段

常用特殊字符:

字符 含义 示例
* 任意值 * * * * * 每分钟
, 列表 1,15 1 号和 15 号
- 范围 9-18 9 点到 18 点
/ 步长 */10 每 10 分钟

二、8 个能直接抄的例子

0 2 * * *           # 每天凌晨 2 点
30 3 * * 1          # 每周一 3:30
*/10 * * * *        # 每 10 分钟
0 */2 * * *         # 每 2 小时整点
0 9-18 * * 1-5      # 工作日 9-18 点,每小时整点
0 0 1 * *           # 每月 1 号 0 点
0 0 1,15 * *        # 每月 1 号和 15 号
0 4 1 * *           # 每月 1 号 4 点(月度报表)

看着简单,坑在下面。

三、坑 1:5 * * * * 不是「每 5 分钟」

这是我见过最多的错误:

写法 实际含义
*/5 * * * * 每 5 分钟(0,5,10…55)✅
5 * * * * 每小时的第 5 分钟(1:05、2:05…)❌
0/5 * * * * 从 0 开始每 5 分钟,等价于 */5

想「每 5 分钟」却写成 5 * * * *,任务一小时才跑一次,数据延迟一整天才会被发现。

四、坑 2:日和星期同时指定,是「或」不是「且」

0 0 13 * 5

新手以为这是「13 号且是周五」。错了——crontab 的规则是:日 和 星期 两个字段只要有一个匹配就执行。所以这个表达式的意思是「每月 13 号 每个周五」都会跑。

要精确限定,就只留一个字段,另一个写 *;或者在执行脚本里自己加日期判断。

五、坑 3:cron 的环境里几乎没有变量

cron 执行时 PATH 通常只有 /usr/bin:/bin。所以:

# 错:node / java / python3 常常找不到,任务静默失败
* * * * * node /opt/app/task.js

# 对:写绝对路径
* * * * * /usr/local/bin/node /opt/app/task.js

# 或者:在 crontab 文件顶部声明 PATH
PATH=/usr/local/bin:/usr/bin:/bin
* * * * * node /opt/app/task.js

调试技巧:在 shell 里用 env -i /bin/bash -c '你的命令' 复现 cron 的空环境,能立刻看出缺什么。

同理,JAVA_HOME、LANG(中文文件名乱码)、TZ(时区,cron 默认用系统时区,容器里常是 UTC)都要显式设置。

六、坑 4:不重定向输出,半年后磁盘告警

cron 的标准输出会发邮件给任务所属用户。邮件服务没配,就堆在 /var/spool/mail/ 里,日积月累能把根分区撑满。

# 正确写法:显式重定向 + 日志切割
*/5 * * * * /opt/app/sync.sh >> /var/log/sync.log 2>&1

配合 logrotate:

/var/log/sync.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
}

调试期可以临时保留输出,正式任务一定要落文件或丢弃(>/dev/null 2>&1)。

七、坑 5:任务重叠执行

任务要跑 10 分钟,但每 5 分钟触发一次 —— 就会有 2 个实例并发跑。数据同步类任务重叠跑,轻则重复写,重则死锁。

flock 加文件锁,一行解决:

*/5 * * * * flock -n /tmp/sync.lock -c '/opt/app/sync.sh >> /var/log/sync.log 2>&1'

-n 表示拿不到锁就直接退出(不等待),不会堆积进程。想等待就去掉 -n,改成 -w 60(最多等 60 秒)。

八、上线前跑一遍检查单

  1. crontab -l 确认写进去了,注意用户(root 和 www 的任务完全隔离,部署脚本里别用错 -u
  2. 命令手动跑一次,确认退出码是 0
  3. 看日志确认真被触发:CentOS//var/log/cron,Ubuntu/journalctl -u cron
  4. 涉及写文件的,确认运行用户有权限(这个坑经常在备份任务上出现)
  5. 改完先设成一个最近的时间点验证(比如下一分钟),别等到凌晨 2 点才发现没跑
  6. 容器里跑 cron 的,确认 cron 进程是 PID 1 或由 supervisor 托管,否则容器起来后 cron 根本没在跑

九、写完先验证一遍

改完 crontab 别直接上线,把表达式丢进解析器看人类可读的描述和接下来几次执行时间,确认和自己想的一致:

👉 Cron 表达式在线解析 / 生成(输入表达式直接出下次执行时间,也支持反向生成)

十、速查表

crontab -l              # 查看当前用户的任务
crontab -e              # 编辑
crontab -r              # 删除(慎用,无确认)
crontab -u www -l       # 查看指定用户的任务
cat /etc/crontab        # 系统级任务(多一个「用户」字段)
ls /etc/cron.d/         # 系统级任务目录

系统级 /etc/crontab 是 6 段(分钟 小时 日 月 星期 用户 命令),和 crontab -e 的 5 段不一样,别混着抄。


相关工具:Cron 表达式解析Linux 命令速查Docker 命令速查

全部纯前端实现,输入的内容不上传服务器。

还有 53 个免费在线工具

纯前端实现,不用注册,数据不上传服务器。

浏览全部工具