标准 crontab 的表达式就是 5 个用空格分开的字段:分 时 日 月 周。顺序从小到大,且日和周是并列关系而不是同时满足关系——这一条坑了很多人,后面单独说。
| 位置 | 字段 | 范围 | 备注 |
|---|---|---|---|
| 1 | 分钟 | 0–59 | |
| 2 | 小时 | 0–23 | 24 小时制 |
| 3 | 日(月内) | 1–31 | |
| 4 | 月 | 1–12 或 JAN–DEC | |
| 5 | 周(星期) | 0–7 | 0 和 7 都表示周日 |
| 字符 | 含义 | 例子 |
|---|---|---|
* |
每个单位都匹配 | * * * * * 每分钟 |
, |
枚举多个值 | 0 6,18 * * * 每天 6 点和 18 点 |
- |
连续区间 | 30 2 * * 1-5 周一到周五 02:30 |
/ |
步长 | */10 * * * * 每 10 分钟 |
步长可以写在区间上:0 9-18/2 * * 1-5 表示工作日 9 点到 18 点之间每 2 小时(9、11、13、15、17 点)。
| 表达式 | 含义 |
|---|---|
* * * * * |
每分钟 |
*/5 * * * * |
每 5 分钟 |
0 * * * * |
每小时整点 |
15 * * * * |
每小时的第 15 分钟 |
0 3 * * * |
每天 03:00 |
30 2 * * 1-5 |
周一至周五 02:30 |
0,30 9-18 * * 1-5 |
工作日 9–18 点每半小时 |
0 0 * * 0 |
每周日 00:00 |
0 0 1 * * |
每月 1 号 00:00 |
0 0 1 1 * |
每年 1 月 1 日 00:00 |
0 4 1 * * |
每月 1 号 04:00 |
还有一组简写,等价于固定表达式:
| 简写 | 等价写法 |
|---|---|
@hourly |
0 * * * * |
@daily / @midnight |
0 0 * * * |
@weekly |
0 0 * * 0 |
@monthly |
0 0 1 * * |
@yearly / @annually |
0 0 1 1 * |
@reboot |
系统启动时执行一次(没有等价表达式) |
0 0 1 * 5 在标准 Vixie cron 里的含义是"每月 1 号 或者 每周五的 00:00",不是"每月 1 号且这天是周五"。只要这两个条件任意成立就会触发。
想表达"每月 1 号,且必须是周五"这类同时满足条件,标准 cron 写不出来,只能在脚本里判断:
0 0 1 * * [ "$(date +\%u)" = "5" ] && /usr/local/bin/report.sh
注意 crontab 文件里 % 有特殊含义(表示换行,其后的内容会作为命令的标准输入),所以必须写成 \% 转义。
| 系统 | 段数 | 顺序 |
|---|---|---|
| 标准 crontab(Vixie / BusyBox) | 5 | 分 时 日 月 周 |
| Quartz(Java) | 6 或 7 | 秒 分 时 日 月 周 [年] |
Spring @Scheduled |
6 | 秒 分 时 日 月 周 |
| Kubernetes CronJob | 5 | 分 时 日 月 周 |
Quartz 多出来的第一段是秒,而且它额外支持 ?(不指定,用于和"日"互斥)、L(最后一天 / 最后一个星期几)、W(最近的工作日)、#(第几个星期几,如 6#3 表示第三个星期五)。这些字符不是标准 cron 的一部分,把 Quartz 表达式直接贴进 crontab 会报错,反之亦然。
一、时区。 cron 默认按系统本地时间解释表达式。服务器在 UTC 而你在东八区,写 0 9 * * * 会在北京时间 17 点执行。Debian/Ubuntu 的 cron 支持在 crontab 里写 CRON_TZ=Asia/Shanghai 覆盖,但不是所有实现都支持;较新版本的 Kubernetes CronJob 也提供了 spec.timeZone 字段。最稳妥的做法是统一服务器时区为 UTC,然后按 UTC 写表达式。
二、任务会重叠。 cron 不关心上一次跑完没有。备份脚本跑 10 分钟而间隔是 5 分钟,就会叠起来。用 flock 加一把非阻塞锁:
*/5 * * * * flock -n /tmp/backup.lock /usr/local/bin/backup.sh
三、环境变量几乎不存在。 cron 给任务的 PATH 通常只有 /usr/bin:/bin,你在 ~/.bashrc 里 export 的东西一律读不到。脚本里要么写绝对路径,要么在 crontab 顶部显式声明 PATH=/usr/local/bin:/usr/bin:/bin。
四、输出会被发邮件。 cron 默认把任务的 stdout 和 stderr 收集起来发邮件给 crontab 所属用户。没有配 MTA 时这些输出就丢了,日志莫名其妙查不到。显式重定向:
0 3 * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
另外提醒一句:cron 的最小粒度是分钟,写不出"每 30 秒"。需要秒级调度要用 systemd timer,或者在脚本里循环加 sleep。
crontab 只在加载文件时做语法检查,字段写错通常不报错,只是永远不触发。三步自查:
crontab -l 看内容,systemctl status cron(RHEL 系是 crond)确认守护进程在跑。grep CRON /var/log/syslog,RHEL 系看 /var/log/cron。这里只能看到"有没有触发",脚本自己的输出取决于你的重定向。* * * * *,等一两分钟确认脚本逻辑正确,再改回真实周期。"cron 不执行"的排查链,按命中率排序:脚本没有 chmod +x → 脚本内命令用了相对路径 → 依赖的环境变量不存在 → % 没转义 → 表达式字段顺序写错。前三条占了绝大多数。