什么是 cron 表达式?语法、字段与 15 个示例
cron 表达式是一段简短的字符串,用来告诉调度程序何时运行某个任务。标准格式由五个以空格分隔的字段组成:分钟、小时、日期(月中的第几天)、月份和星期。例如,*/15 * * * * 表示“每 15 分钟一次”:分钟字段中的 */15 匹配第 0、15、30 和 45 分钟,其余四个星号表示任意小时、任意日期、任意月份和任意星期。
免费试用:Crontab 生成器 免费使用,无需注册账号。
这个名字来自 Unix 的任务调度程序 cron,但如今同样的语法也用于 CI 流水线、云端调度服务和应用框架,有时还会增加额外的字段。本文介绍 cron 语法、15 个经过核对的示例、常见变体,以及会让任务在错误时间运行的常见错误。阅读时如果想检查某个表达式,可以把它粘贴到免费的 Crontab 生成器中,它会用通俗的语言描述执行计划。
cron 表达式的五个字段
字段从左到右依次为:
| 位置 | 字段 | 允许的值 | 名称 |
|---|---|---|---|
| 1 | 分钟 | 0–59 | – |
| 2 | 小时 | 0–23 | – |
| 3 | 日期 | 1–31 | – |
| 4 | 月份 | 1–12 | jan–dec |
| 5 | 星期 | 0–7(0 和 7 都表示星期日) | sun–sat |
小时采用 24 小时制,下午 2 点写作 14。名称取英文月份或星期的前三个字母,不区分大小写。有些实现(包括 macOS 自带的 cron)不允许在范围或列表中使用名称,因此 1-5 比 mon-fri 的兼容性更好。
在 crontab 文件中,命令写在五个字段之后:0 2 * * * /home/me/backup.sh。Linux 上系统级的 /etc/crontab 还会在命令前多一个用户名字段。
特殊字符:星号、逗号、连字符和斜杠
几乎所有写法都靠这四个字符完成:
| 字符 | 含义 | 示例 | 匹配 |
|---|---|---|---|
* |
任意值 | 小时字段中的 * |
第 0、1、2 … 23 小时 |
, |
列表 | 小时字段中的 8,20 |
第 8 和第 20 小时 |
- |
范围(含两端) | 星期字段中的 1-5 |
星期一到星期五 |
/ |
步长 | 分钟字段中的 */15 |
第 0、15、30、45 分钟 |
它们可以组合使用。小时字段中的 9-17/2 匹配 9、11、13、15 和 17 点,0-4,8-12 则是由两个范围组成的列表。
步长在每个周期都会重新开始。分钟字段中的 */7 匹配 0、7、14 … 56,到下一个小时开始时又从 0 算起,所以 :56 到 :00 之间只隔 4 分钟,而不是 7 分钟。如果步长不能整除 60 分钟或 24 小时,间隔就永远不会完全均匀。
15 个 cron 表达式示例
每个示例都逐个字段核对过。时间以调度程序所在的时区为准。
| cron 表达式 | 运行时间 |
|---|---|
* * * * * |
每分钟 |
*/5 * * * * |
每 5 分钟(:00、:05、:10 …) |
30 * * * * |
每小时的第 30 分钟(00:30、01:30 …) |
0 */2 * * * |
每 2 小时整点(00:00、02:00 … 22:00) |
0 2 * * * |
每天 02:00 |
0 8,20 * * * |
每天两次,08:00 和 20:00 |
0 9 * * 1-5 |
周一至周五 09:00 |
*/15 9-17 * * 1-5 |
周一至周五 09:00 到 17:45 之间每 15 分钟 |
0 6 * * 0,6 |
周六和周日 06:00 |
0 0 * * 0 |
每周日午夜 |
0 22 * * 5 |
每周五 22:00 |
0 0 1 * * |
每月 1 日午夜 |
0 12 15 * * |
每月 15 日 12:00 |
0 0 1 1,4,7,10 * |
每季度:1 月、4 月、7 月和 10 月的 1 日午夜 |
0 0 1 1 * |
每年一次,1 月 1 日午夜 |
有两个细节很容易被忽略。第一,范围包含两端,所以 9-17 包括 17 点:*/15 9-17 * * 1-5 每天运行 36 次,最后一次在 17:45,而不是 17:00。第二,0 0 1 */3 * 得到的是同样的季度计划,因为月份字段中的 */3 从 1 开始,匹配 1、4、7 和 10 月。
非标准宏:@daily、@weekly 和 @reboot
很多 cron 实现都接受用一个关键字代替五个字段:
| 宏 | 等同于 | 运行时间 |
|---|---|---|
@yearly 或 @annually |
0 0 1 1 * |
1 月 1 日午夜 |
@monthly |
0 0 1 * * |
每月 1 日午夜 |
@weekly |
0 0 * * 0 |
每周日午夜 |
@daily 或 @midnight |
0 0 * * * |
每天午夜 |
@hourly |
0 * * * * |
每小时的第 0 分钟 |
@reboot |
– | cron 守护进程启动时运行一次 |
Vixie cron、cronie 和 macOS 上的 cron 支持以上全部宏;Kubernetes 的 CronJob 支持基于时间的宏,但不支持 @reboot。这些宏不属于 POSIX 标准,在其他环境中使用前请先查看文档。
cron 的变体:秒、年份和额外字符
并不是所有调度程序都只读取五个字段,所以请先确认你要写的是哪种“方言”。
- Quartz(Java) 使用六个或七个字段:秒、分钟、小时、日期、月份、星期,以及可选的年份。两个日期相关字段中必须有一个是
?(“不指定值”)。Quartz 还支持L(最后)、W(最近的工作日)和#(当月第几个星期几):0 15 10 L * ?在每月最后一天的 10:15 运行,0 15 10 ? * 6#3在每月第三个星期五的 10:15 运行,因为 Quartz 将星期编号为 1–7,从星期日开始。 - Spring 的
@Scheduled(cron = "…")表达式有六个字段:第一个是秒,后面是常见的五个字段,没有年份。 - AWS EventBridge 规则使用
cron(minutes hours day-of-month month day-of-week year):共六个字段,没有秒,最后是年份,并且两个日期相关字段之一必须是?。cron(0 12 * * ? *)表示每天 UTC 12:00 运行。事件总线上的规则按 UTC 运行,而 EventBridge Scheduler 可以选择时区。与 Quartz 一样,AWS 将星期编号为 1–7,其中 1 表示星期日。 - Kubernetes CronJob 在
spec.schedule中使用标准的五个字段。如果没有设置spec.timeZone,计划将按 kube-controller-manager 所在的时区执行。可以把它设为Europe/Berlin这样的 IANA 时区名来固定时区。 - GitHub Actions 的
schedule触发器使用五个字段,默认按 UTC 时间运行,也可以选择指定一个 IANA 时区。支持的最短间隔是 5 分钟,不支持@daily这类宏,而且在 GitHub 负载较高时(尤其是每个整点),计划运行可能会延迟开始。
日期与星期字段的陷阱
在标准(Vixie)cron 中,如果日期和星期两个字段都有限定值,二者是“或”的关系,而不是“且”。只要其中一个是 *,就只看另一个。因此,0 9 1-7 * 1 并不表示“每月第一个星期一”。它会在每月 1 日到 7 日的每一天 09:00 运行,另外每个星期一也会运行,合计每月 10 或 11 次。
如果只想在第一个星期一运行,可以在表达式里只保留一个日期字段,另一个条件放到命令中判断:
0 9 1-7 * * [ "$(date +\%u)" = 1 ] && /path/to/report.sh
date +%u 会把星期输出为 1 到 7 的数字,星期一为 1。百分号必须转义为 \%,因为 cron 会把命令中未转义的 % 当作换行符。Quartz 和 AWS 要求在两个日期相关字段之一使用 ?,从而避免了这种歧义。
时区与夏令时
cron 按照负责解析表达式的机器或服务所在的时区运行,而服务器和云平台上通常是 UTC,并不是你的本地时间。在 UTC 服务器上,0 9 * * * 会在 UTC 09:00 运行,也就是北京时间 17:00(UTC+8)。时区转换器 可以帮你把本地时间换算成调度程序所在的时区。
在实行夏令时的时区,还会出现两种特殊情况:时钟拨快时,有一个本地小时并不存在;时钟拨慢时,有一个小时会出现两次。Debian 的 cron 和 cronie 会对固定时间的任务处理这类小幅度的时钟变化:落在被跳过的那一小时内的任务,会在切换后不久补运行;落在重复那一小时内的任务不会再运行一次。其他调度程序的处理方式可能不同,因此最稳妥的做法是让服务器使用 UTC,或者避免把重要任务安排在调整时钟的时段,通常是当地时间 01:00 到 03:00 之间。
如何测试 cron 表达式
写错的 cron 表达式很少会报错,任务只会在错误的时间运行,或者根本不运行。下面这个简短流程可以发现大多数问题:
- 用文字复述一遍。 把表达式粘贴到能描述计划的工具里,检查描述是否符合你的本意,尤其是两个日期相关字段。
- 用短间隔测试命令。 临时使用
* * * * *,让任务在一分钟内运行,确认无误后再改回真正的计划。 - 保存输出。 在命令后加上
>> /tmp/myjob.log 2>&1,让错误写入文件,而不是直接丢失。 - 查看 cron 日志。 在 Debian 和 Ubuntu 上使用
grep CRON /var/log/syslog或journalctl -u cron,在 Fedora 和 RHEL 上使用journalctl -u crond。 - 使用绝对路径。 cron 启动任务时只有最基本的环境变量和很短的
PATH,所以在终端里能运行的命令,放到 cron 中可能会失败。
用 Crontab 生成器创建表达式
免费的 Crontab 生成器 完全在浏览器中运行。输入一个表达式,或点击“每 15 分钟”“每天凌晨 2 点”“每周一”等快速预设,工具就会:
- 校验语法,并在表达式无效时给出提示;
- 用通俗的语言描述执行计划(英文);
- 支持标准的五个字段、开头可选的秒字段、月份和星期名称,以及用 7 表示星期日;
- 把
@weekly等宏展开为对应的五字段表达式,并说明@reboot的作用; - 显示一张速查表,列出每个符号及示例。
通过开关可以选择更详细的描述、24 小时制时间,以及星期编号是否从 0(星期日)开始。它不支持 Quartz 专有的字符(L、W、#),也不支持年份字段。该工具只负责生成和检查表达式;要真正安排任务,请把表达式复制到 crontab -e 或你所用平台的配置中。
常见问题
cron 和 crontab 有什么区别?
cron 是执行计划任务的后台服务(守护进程)。crontab 是“cron table”的缩写,是列出这些任务的文件,每行一个任务:先是 cron 表达式,后面跟着命令。你可以用 crontab -e 编辑自己的 crontab,用 crontab -l 查看它。
cron 任务可以每 30 秒运行一次吗?
标准 cron 不行,它的最小单位是一分钟。带秒字段的调度程序可以做到:Spring 接受 */30 * * * * *,Quartz 则需要在一个日期相关字段中使用 ?,例如 */30 * * * * ?。在传统 cron 中,常见的变通办法是写两行都每分钟运行的任务,其中一行在命令前先执行 sleep 30;。
如何让 cron 任务在每月最后一天运行?
标准 cron 没有表示“最后一天”的符号,但 Quartz 和 AWS 支持在日期字段中使用 L。在传统 cron 中,可以把任务安排在每月 28 日到 31 日,再由命令判断明天是否为 1 日:
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/job.sh
date -d tomorrow 是 GNU date 的语法,适用于 Linux。在 macOS 上请改用 date -v+1d。
免费试用:Crontab 生成器 免费使用,无需注册账号。