如何构建 cron 表达式
Cron 表达式是在 Linux、云平台、CI/CD 流水线和任务调度器中定义循环计划的标准方式。语法紧凑但不直观,构建一个可视化的 cron 生成器能让你确切看到任务何时运行,在部署前捕获常见错误,并消除自动化中最容易出错部分的猜测。一旦你理解了五个字段、特殊字符和最常见的陷阱,就可以自信地指定任何循环计划。
Cron 简史
最初的 cron 来自 Brian Kernighan,出现在大约 1979 年的 Unix 第七版中。它每分钟重新读取配置并运行所有到期的任务。Paul Vixie 在 1987 年将其改写为现在所称的 Vixie cron,这也是大多数 Linux 发行版至今仍在使用的版本。Vixie cron 加入了按用户的 crontab、环境变量、@reboot 关键字,以及几项让非管理员也能使用该格式的便利功能。
五字段语法在四十多年里几乎没有变化。Amazon EventBridge、Google Cloud Scheduler、Kubernetes CronJob、GitHub Actions、GitLab CI、Jenkins、Airflow、n8n 以及几十个其他系统都消费同样的紧凑格式,只有少量扩展。这种稳定性正是 cron 值得学一次就再也不必重学的原因。这项技能在任何按时钟运行自动化的地方都能转移。
Cron 语法
标准 cron 表达式有 5 个字段,以空格分隔。每个字段控制一段时间,当所有字段都匹配当前时刻时任务运行。
┌───────────── 分钟 (0-59)
│ ┌───────────── 小时 (0-23)
│ │ ┌───────────── 日 (1-31)
│ │ │ ┌───────────── 月 (1-12,或 JAN-DEC)
│ │ │ │ ┌───────────── 星期 (0-6,星期日=0,或 SUN-SAT)
│ │ │ │ │
* * * * *
对小时、分钟和月份,各字段是 AND 关系,但在 Vixie cron 中日和星期是 OR 关系。这意味着 0 12 1 * 1 既在每月 1 日触发,也在每个星期一中午触发,而不仅在恰好是 1 日的星期一。这个陷阱几乎每个人第一次都会中招。
常见 cron 计划
你最常用到的模式:
| 计划 | 表达式 | 含义 |
|---|---|---|
| 每分钟 | * * * * * | 每 60 秒运行 |
| 每 5 分钟 | */5 * * * * | 在 :00、:05、:10、:15... |
| 每 15 分钟 | */15 * * * * | 在 :00、:15、:30、:45 |
| 每小时 | 0 * * * * | 每小时整点 |
| 每 2 小时 | 0 */2 * * * | 00:00、02:00、04:00... |
| 每天午夜 | 0 0 * * * | 每天 00:00 一次 |
| 每天上午 9 点 | 0 9 * * * | 每天 09:00 一次 |
| 一天两次 | 0 9,21 * * * | 09:00 和 21:00 |
| 每星期一上午 8 点 | 0 8 * * 1 | 每周一次,星期一 |
| 工作日下午 6 点 | 0 18 * * 1-5 | 星期一至星期五 |
| 每月 1 日 | 0 0 1 * * | 每月 1 日午夜 |
| 每季度 | 0 0 1 */3 * | 1 月 1 日、4 月 1 日、7 月 1 日、10 月 1 日 |
| 每个工作日早晨 | 0 7 * * 1-5 | 周一至周五 07:00 |
| 周日中午 | 0 12 * * 0 | 每周一次,星期日 |
许多系统也接受展开为等效 5 字段表达式的缩写别名:@yearly、@monthly、@weekly、@daily、@hourly 和 @reboot。它们很简洁但并非通用,所以在依赖它们之前请检查你的平台。
如何构建 cron 表达式
- 选择粒度:你需要每分钟、每小时、每天一次、每周一次还是每月一次?从满足需求的最粗设置开始。
- 使用可视化控件:从下拉菜单中选择分、时、日、月和星期值。或者从预设开始,如「每小时」或「每天午夜」,然后调整。
- 预览下次运行时间:生成器显示接下来 5 次执行时间,让你能确认计划在预期的时间触发。
- 检查时区:预览应当匹配将运行任务的服务器或调度器的时区,而不是你的本地时间。
- 复制表达式并粘贴到你的 crontab、GitHub Actions YAML、AWS EventBridge 规则或任何你使用的调度器中。
- 先用短间隔测试,再提交最终计划。一个快速的
*/5 * * * *证明任务会触发;看到两三次执行后,再换成真正的表达式。
特殊字符与运算符
Cron 在任何字段内支持一小套强大的运算符。
| 字符 | 含义 | 示例 |
|---|---|---|
* | 任意值 | * * * * * = 每分钟 |
*/n | 每 n 个 | */15 * * * * = 每 15 分钟 |
, | 多个离散值 | 0 8,12,18 * * * = 8 点、中午、18 点 |
- | 含端点范围 | 0 9-17 * * * = 9 点到 17 点每小时 |
n-m/k | 带步长的范围 | 0 9-17/2 * * * = 9、11、13、15、17 |
? | 无特定值(仅 Quartz) | 0 0 ? * MON(Java 调度器) |
L | 最后(AWS、Quartz) | DoM 中的 L = 当月最后一天 |
W | 最近的工作日(AWS、Quartz) | 15W = 离 15 日最近的工作日 |
# | 第 n 个星期某(AWS、Quartz) | MON#2 = 月度第二个星期一 |
@hourly | 简写 | 与 0 * * * * 相同 |
原版 Vixie cron 仅支持前五行。高级运算符(L、W、#、?)来自 Quartz,即 Java 的调度库,后被 AWS EventBridge 和少数其他云调度器采用。它们不可移植,所以不要在必须运行于通用 Linux 上的代码中混用。
不同平台上的 cron
Cron 是一族相关的语法,并非单一标准。知道你的调度器讲哪种方言可省下数小时调试。
| 平台 | 字段 | 备注 |
|---|---|---|
| Vixie cron (Linux) | 5 | 经典款。*/n、范围、列表,无高级运算符 |
| BSD cron | 5 | 类似 Vixie,但环境略有不同 |
| crontab.guru | 5 | 反映 Vixie 语义的网页解析器 |
| GitHub Actions | 5 | Vixie 语法,运行于 UTC,最小 5 分钟精度 |
| GitLab CI | 5 | Vixie 语法,运行于实例时区 |
| AWS EventBridge | 6 | 增加年份。星期使用 1-7(周日=1),支持 L/W/# |
| Google Cloud Scheduler | 5 | Vixie 语法加时区配置 |
| Kubernetes CronJob | 5 | Vixie 语法加 @ 简写 |
| Quartz (Java) | 6 或 7 | 在最前加秒,以及可选的年份 |
| systemd 定时器 | OnCalendar 格式 | 不是 cron,但用更清晰的语法解决同样的问题 |
如果你写的计划要在多个平台上运行,坚持使用每个系统都能理解的保守 5 字段子集。只在你确定目标支持时才用 L、W 或 #。
常见陷阱
- 日与星期是 OR 关系,
0 9 15 * 1这样的表达式在每个星期一 AND 每月 15 日触发,而不仅在 15 日恰好是星期一时触发。要做交集,通常需要外层包装或换用别的调度器。 - 时区混淆,服务器 crontab 几乎总是在 UTC 中运行。如果你想要美东 9 点,那在冬天是
0 14 * * *UTC,但在夏天因夏令时是0 13 * * *UTC。使用支持时区提示的调度器,或把一切归一为 UTC。 - 夏令时切换,在本地时间 02:30 计划的任务可能在拨回时运行两次,在向前拨时一次都不运行。把敏感任务安排在 01:00-03:00 窗口之外,或使用 UTC。
- Vixie 的
MAILTO陷阱,如果你的任务向 stdout 打印任何内容,Vixie cron 会把输出邮寄给 crontab 所属用户。在没有邮件中继的服务器上,这会很快塞满/var/spool/mail。用>>/var/log/myjob.log 2>&1将输出重定向到日志文件。 - 环境不是你的登录 shell,cron 以精简过的环境运行:没有来自你
.bashrc的PATH,没有 virtualenv,没有nvm。在 crontab 顶部设置你需要的变量,或用绝对路径调用你的脚本。 - 百分号需要转义,Vixie crontab 中未转义的
%会被解释为命令中的换行符。如果你的命令需要字面百分号(例如date +"%Y-%m-%d"调用),总是要转义为\%。 - 长时间运行的任务会重叠,cron 不会因为上一次还没结束就跳过本次执行。如果你的任务可能超过间隔,把它包在锁文件中(
flock、setlock)或使用能处理并发的执行器。 - 59 分钟时的溢出,
*/40 * * * *不是每 40 分钟触发一次。它在每小时的 0 分和 40 分触发,因为步长值会在字段边界处循环。要真正的 40 分钟间隔,你需要更丰富的调度器。 - 分钟字段忘写
0,* 9 * * *从 09:00 到 09:59 每分钟运行,而不是 09:00 仅一次。如果你想每小时只触发一次,分钟字段必须有显式值。 - Cron 在笔记本上不可靠,anacron 就是为此存在的。Vixie cron 在休眠后不会补上错过的执行,所以计划在 03:00 的每日备份,如果那个时间笔记本是合上的就不会运行。使用 anacron、带
Persistent=true的 systemd 定时器,或 macOS 上的 launchd plist。
cron 的替代品
对某些工作负载,cron 粗到分钟的精度和没有记账机制开始让人难受。最常见的升级:
| 工具 | 优势 | 何时选用 |
|---|---|---|
| systemd 定时器 | 清晰的 OnCalendar 语法、跨重启持久、与 unit 集成 | 你已经在用 systemd 并想要更丰富的日志 |
| Anacron | 休眠后补上错过的执行 | 笔记本或并非始终开机的机器 |
| Airflow / Dagster | DAG 依赖、重试、可观测性 | 多步数据流水线 |
| Temporal | 有状态工作流,正好一次的保证 | 跨服务的长时间编排 |
| AWS EventBridge | 托管,集成 Lambda、S3、SQS | AWS 上的云原生任何任务 |
| GitHub Actions | 公共仓库免费,运行于托管运行器 | 与 CI 相邻的计划任务 |
| cron 触发的无服务器函数 | 无服务器要维护 | 装得下 Lambda 的轻量任务 |
对绝大多数一次性循环任务,cron 仍是正确答案。其他工具在你需要状态、重试、依赖或跨机协调时才闪光。
隐私与 cron 生成器
Cron 表达式生成器完全在你的浏览器中运行。你构建的计划、下次运行时间的预览以及复制的表达式从不接触我们的服务器。没有关于生成了哪些表达式的日志,没有关于哪些预设流行的遥测,也没人有办法重建你正在处理的计划。Cron 表达式表面上不是个人数据,但一个任务的计划(每晚数据库导出、每周计费运行、每小时与合作伙伴同步)能透露很多关于一个企业如何运营的信息。把这些信息保留在客户端可以避免把基础设施模式不慎泄露给第三方。对于像选择计划这样的日常任务,默认的隐私级别应当与这些计划所代表内容的敏感度相匹配。
常见问题
cron 表达式的格式是什么?
标准 cron 表达式有 5 个由空格分隔的字段,分别表示分(0-59)、时(0-23)、日(1-31)、月(1-12)和星期几(0-6,其中 0 是周日)。星号(*)表示该字段的「每个」值。
cron 中的 */5 是什么意思?
*/5 语法表示「每 5 个」。在分字段中,*/5 表示每 5 分钟(0、5、10、15…)。在时字段中,*/5 表示每 5 小时。它适用于任意字段。
cron 表达式在所有平台上都相同吗?
5 字段格式在 Linux cron、AWS EventBridge、GitHub Actions 和大多数调度系统上是标准。某些平台会添加第 6 个字段用于秒或年份。请查阅您平台的文档。
如何为每月最后一天安排任务?
标准 cron 没有「最后一天」关键字。可以使用变通方法,如每天运行并在脚本中检查日期,或使用平台特有的扩展(AWS EventBridge 支持 L 表示「last」)。
Why did my cron job not run at the expected time?
The most common cause is timezone confusion. Server cron usually runs in UTC, not your local time. Other causes include the server being asleep at the scheduled minute, the user crontab not being installed, or PATH/environment differences between your shell and cron's stripped-down environment.
What is the difference between 0 in the day-of-week field and 7?
Both 0 and 7 represent Sunday in classic Vixie cron, which uses 0-6 plus an alias for 7. Some implementations (notably AWS EventBridge) use 1-7 with Sunday as 7 and Monday as 1, so always check your platform's documentation before assuming.