Crontab 速查表

Crontab 速查表
近未來儘未來丶cron 表达式由五个字段加一条命令组成:分、时、日、月、星期。这套语法从 1979 年起驱动 Unix 任务调度,现在也驱动 Kubernetes CronJob、GitHub Actions、AWS EventBridge 和 Vercel cron 触发器。学一次就够用了。
这篇文章面向当下就要写表达式的开发者:一个 Linux 定时任务,一个 Kubernetes CronJob,一个 GitHub Actions 触发器,或者排查为什么本该每五分钟跑一次的任务只在整点触发。直接看下面的快速参考表复制表达式;想看字段规则就跳到「语法详解」;或者打开 Crontab 生成器 — Cron 表达式构建与解析 —— 一个在浏览器中运行的隐私优先的 crontab guru 替代工具 —— 实时验证。
#Cron 表达式快速参考表
下面三十个表达式覆盖了约 90% 的真实调度需求。每一条都是合法的 POSIX 五字段 cron,可直接粘贴到 crontab -e、Kubernetes 的 schedule: 或 GitHub Actions 的 cron: 中。
| 调度场景 | Cron 表达式 | 中文解释 |
|---|---|---|
| 每分钟 | * * * * * |
全天每一分钟 |
| 每 5 分钟 | */5 * * * * |
第 0、5、10、…、55 分钟 |
| 每 15 分钟 | */15 * * * * |
第 0、15、30、45 分钟 |
| 每 30 分钟 | */30 * * * * |
第 0 和第 30 分钟 |
| 每小时 | 0 * * * * |
每个整点 |
| 每 2 小时 | 0 */2 * * * |
第 0、2、4、…、22 时 |
| 每 6 小时 | 0 */6 * * * |
第 0、6、12、18 时 |
| 每天两次(早 9 + 晚 9) | 0 9,21 * * * |
9 点和 21 点整 |
| 工作日每天上午 9 点 | 0 9 * * 1-5 |
周一至周五 09:00 |
| 周末每天上午 9 点 | 0 9 * * 0,6 |
周六周日 09:00 |
| 每天午夜 | 0 0 * * * |
每日 00:00 |
| 每天凌晨 2:30 | 30 2 * * * |
低峰批处理窗口 |
| 每周一上午 9 点 | 0 9 * * 1 |
周一 09:00 |
| 每周五下午 5 点 | 0 17 * * 5 |
周五 17:00 |
| 每周日午夜 | 0 0 * * 0 |
等价于 @weekly |
| 每月 1 号午夜 | 0 0 1 * * |
每月 1 日 00:00,等价于 @monthly |
| 每月 15 号中午 | 0 12 15 * * |
月中发薪窗口 |
| 每月最后一天(需 wrapper) | 0 0 28-31 * * + 脚本 |
需要日期判断 |
| 每季度(1/4/7/10 月 1 日) | 0 0 1 JAN,APR,JUL,OCT * |
每季度第一天 |
| 每年(1 月 1 日) | 0 0 1 1 * 或 @yearly |
元旦零点 |
| 工作日 9-17 点每 5 分钟 | */5 9-17 * * 1-5 |
工作时间轮询 |
| 周末每 30 分钟 | */30 * * * 0,6 |
周六周日监控 |
| 每小时两次,15 和 45 分 | 15,45 * * * * |
错开整点高峰 |
| 每月第一个周一(需 wrapper) | 0 9 1-7 * 1 + AND 判断 |
需 wrapper(见下文) |
| 宏 | @hourly @daily @weekly @monthly @yearly |
非标准,但被广泛支持 |
| 仅在重启时 | @reboot |
非标准,仅 vixie cron 支持 |
把上面任意一条粘贴到 Crontab 生成器 — Cron 表达式构建与解析,可以预览接下来五次触发时间,是上线前最快的烟雾测试。
#Cron 语法详解:五个字段
cron 表达式是由空格分隔的五个字段,再加一条命令。每个字段控制调度的一个维度。这套语法是本文涉及到的所有调度器的共同基础。
1 | ┌──────────── minute (0 - 59) |
记忆口诀:「分时日月周」,从左到右,由小到大。
#各字段允许的取值
| 字段 | 范围 | 别名 | 备注 |
|---|---|---|---|
| 分 | 0-59 | 无 | 0 表示「整点」 |
| 时 | 0-23 | 无 | 24 小时制;0 是午夜,12 是正午 |
| 日 | 1-31 | 无 | 当月不存在的日期会静默地永不触发(例如 2 月 31 日) |
| 月 | 1-12 | JAN、FEB、MAR、…、DEC | 大小写不敏感 |
| 周 | 0-7 | SUN、MON、TUE、…、SAT | 0 和 7 都表示周日 |
#运算符详解
五个运算符就能覆盖所有标准 cron 表达式:
| 运算符 | 含义 | 示例 | 展开结果 |
|---|---|---|---|
* |
任意值 | * * * * * |
每分钟 |
, |
列表 | 0 9,12,17 * * * |
09:00、12:00、17:00 |
- |
闭区间范围 | 0 9-17 * * * |
09:00 到 17:00 每个整点 |
/ |
步长 | */15 * * * * |
第 0、15、30、45 分钟 |
| 混合 | 组合 | 0 9-12,14-17 * * * |
上午+下午,跳过午饭时间 |
步长运算符要小心。*/N 是相对于字段的最小值对齐的,不是相对于当前时间。*/15 的含义是「每个小时的第 0、15、30、45 分钟」,不是「从现在起每 15 分钟一次」。12:03 保存表达式,下一次触发就是 12:15。如果基数不是通配符,5/15 读作「从 5 开始,每 15 分钟一次」:第 5、20、35、50 分钟。
#月份与星期使用名称
月份和星期可以写成名称,大小写不敏感:
1 | 0 0 1 JAN,APR,JUL,OCT * # 每季度的第一天 |
代码评审时名称更易读,数字形式可移植性稍好一些。一个项目内统一风格即可。
#非标准宏:@reboot、@daily 及其家族
大多数 cron 实现都接受六个快捷宏:
| 宏 | 展开为 | 含义 |
|---|---|---|
@yearly / @annually |
0 0 1 1 * |
每年一次,1 月 1 日午夜 |
@monthly |
0 0 1 * * |
每月 1 日午夜 |
@weekly |
0 0 * * 0 |
每周日午夜 |
@daily / @midnight |
0 0 * * * |
每天午夜 |
@hourly |
0 * * * * |
每个整点 |
@reboot |
(特殊) | cron 守护进程启动时执行一次 |
这些宏是非标准的:vixie cron 和 cronie 支持,但 Kubernetes CronJob、GitHub Actions、AWS EventBridge 都不认。要写可移植的表达式,就写五字段形式。@reboot 在容器里几乎用不上,因为 cron 守护进程通常不是 init 进程。
#50+ 可复制 Cron 表达式(按用途分组)
这一节按六个用途分桶,给出更密集的 cron 例子。
#每 N 分钟
1 | * * * * * # 每分钟 |
*/45 是一个常见的陷阱:分钟范围是 0-59,所以会落在第 0 和第 45 分钟,下一个小时再次回绕到第 0 分钟。要真正实现 45 分钟的等间隔节奏,需要外部 worker。
#整点变体
1 | 0 * * * * # 每小时 :00 |
#每天的固定时间
1 | 0 0 * * * # 午夜(= @daily / @midnight) |
#每周调度
1 | 0 9 * * 1-5 # 工作日上午 9 点 |
#每月与每季度
1 | 0 0 1 * * # 每月 1 日午夜(= @monthly) |
「每月最后一天」在 POSIX cron 中没有原生表达式。可以写一个 wrapper 判断 date -d tomorrow +%d = 01,或者改用原生支持该语法的调度器(Quartz 有 L,Kubernetes 没有)。
#每年与宏快捷写法
1 | 0 0 1 1 * # 1 月 1 日午夜(= @yearly / @annually) |
把上面任何一条粘贴到 Crontab 生成器 — Cron 表达式构建与解析都可以查看接下来五次触发时间,是上线前最便宜的烟雾测试。
#Cron vs systemd timer vs 云调度器:决策矩阵
cron 是默认选项,但不一定是最优解。下面对七种最常见的调度器做了对比。无论你在判断 cron 还是 systemd timer,对比 Kubernetes CronJob 与 Vercel cron,还是从 crontab 迁移到托管云服务,都用得上。
| 特性 | vixie cron | systemd timer | K8s CronJob | GHA schedule | AWS EventBridge | Vercel Cron | Cloudflare Workers |
|---|---|---|---|---|---|---|---|
| 字段语法 | 5 字段 POSIX | OnCalendar 规范 | 5 字段 POSIX + timeZone | 5 字段 POSIX | 6 字段 Quartz,含 ? |
5 字段 POSIX | 5 字段 POSIX |
| 最小间隔 | 1 分钟 | 1 秒 | 1 分钟 | 尽力而为,建议 ≥15 分钟 | 1 分钟 | 1 分钟(Pro 计划) | 1 分钟 |
| 显式时区 | CRON_TZ= |
Persistent=true |
spec.timeZone(1.27+) |
仅 UTC | ScheduleExpressionTimezone |
仅 UTC | 仅 UTC |
| 漏跑补偿 | 否(用 anacron) | 是(Persistent=true) |
是(startingDeadlineSeconds) |
否 | 是 | 否 | 否 |
| 重试 / 退避 | 否 | 部分 | 是(backoffLimit) |
失败时重试 | 是 | 否 | 是 |
| 并发控制 | 否(用 flock) |
部分 | 是(concurrencyPolicy) |
否 | 否 | 否 | 否 |
@reboot 支持 |
是 | 是(通过 OnBootSec=) |
否 | 否 | 否 | 否 | 否 |
#systemd timer:什么时候比 cron 更合适
在基于 systemd 的 Linux 上,timer 是一个实用的替代方案:日历式语法可读性更好,原生接入 journal,还能补漏跑。一个 timer 和对应的 service:
1 | # daily-report.timer |
1 | # daily-report.service |
用 systemctl enable --now daily-report.timer 启用。最大的差异在 Persistent=true:如果 9 点机器是关机的,开机后 timer 会立即补跑。vixie cron 没有等价能力,除非额外用 anacron。关于服务侧的安全加固,参考安全最佳实践。
#Kubernetes CronJob
Kubernetes 在 POSIX 调度的基础上加了一层原语,用于控制并发、保留历史和指定时区:
1 | apiVersion: batch/v1 |
concurrencyPolicy: Forbid 就是你在 Kubernetes 里的 flock 等价物。不加这一项,一个跑得久的任务会和下一次触发叠在一起。所有可配项见下文「Kubernetes CronJob 字段参考」。
#GitHub Actions schedule 的注意事项
GitHub Actions 接受标准的五字段 POSIX cron:
1 | on: |
但这是「尽力而为」:在 GitHub runner 负载高时,任务可能延迟几分钟,甚至整次跳过。不要使用短于 15 分钟的间隔。GHA 没有时区设置,永远是 UTC。
#AWS EventBridge:Quartz 风格的六字段
AWS EventBridge 使用 Quartz 变体的 cron,有六个字段,并且要求两个日字段之一必须是 ?:
1 | cron(0 9 * * ? *) |
字段顺序是:Minutes Hours Day-of-month Month Day-of-week Year。当其中一个日字段有约束时,另一个必须是 ?(Quartz 用这个语法来消解 POSIX 的 OR 歧义)。从 Linux crontab 直接复制过来肯定通不过校验。
#Vercel Cron、Cloudflare Workers、Render Cron Jobs
新兴的 serverless 平台统一选用了五字段 POSIX。Vercel cron job 写在 vercel.json 里:{ "crons": [{ "path": "/api/cron/nightly", "schedule": "0 2 * * *" }] }。Cloudflare Workers 的 Cron Triggers 写在 wrangler.toml 里:
1 | [triggers] |
Render 用的是 render.yaml。这三家都按 UTC 跑,并且不能为单条调度单独覆盖时区,从一开始就按 UTC 设计。
#7 个 Cron 调试陷阱(以及怎么抓到它们)
绝大多数「我的 cron 没跑」的报告,根因都在下面这七个之一。怀疑调度器之前,先把这张清单走一遍。
#陷阱 1:PATH 极度精简
cron 启动任务时给的是最精简的 $PATH,通常只有 /usr/bin:/bin。你交互式 shell 里有 /usr/local/bin、~/.cargo/bin,还有十几条来自 .bashrc 的补充。这些 cron 一概不知道。环境变量类问题里,这一条出现得最频繁。
症状:node: command not found。修法:在 crontab 顶部显式设置 PATH,或使用绝对路径。
1 | SHELL=/bin/bash |
#陷阱 2:stdout 和 stderr 被静默丢弃
默认情况下,cron 的输出送到一个没人看的邮件 spool 里。任务就这么静默失败了。把两路流都重定向:
1 | */15 * * * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1 |
JSON 输出可以管道送 jq处理;提取日志行参考正则表达式速查表。systemd timer 用 journalctl -u your-timer.service 就能看到所有输出。
#陷阱 3:开发与生产时区错位
你在纽约的笔记本上写 0 9 * * *,以为是美东时间早 9 点。生产服务器跑在 UTC。cron 按 UTC 早 9 点触发,也就是美东凌晨 4 点,没人发现。修法:服务器统一用 UTC、调度按 UTC 写,或者显式锁定时区。
1 | CRON_TZ=America/New_York |
CRON_TZ 从 vixie cron 3.0 起支持;Kubernetes 1.27+ 提供 spec.timeZone;AWS EventBridge 用 ScheduleExpressionTimezone;GitHub Actions 永远是 UTC。UTC、夏令时和 epoch 换算细节参考 Unix 时间戳完全指南。
#陷阱 4:命令中未转义的 %
cron 会把未转义的 % 当作换行符,后面的内容会被当成标准输入喂给命令。所以 date +"%Y-%m-%d" 直接挂掉。要把每个 % 转义成 \%,或者干脆把逻辑搬进脚本:
1 | 0 0 * * * echo "Run at $(date +"\%Y-\%m-\%d")" >> /tmp/log |
#陷阱 5:执行时间重叠
一个 */5 * * * * 的任务如果偶尔跑七分钟,下一次实例就会在上一次还没结束时被启动。两个进程会在同一行数据、同一个锁文件、同一个 API 配额上互殴。用 flock 串行化:
1 | */5 * * * * flock -n /tmp/job.lock /usr/local/bin/job.sh |
-n 表示锁被占用时立刻退出。在 Kubernetes 里则设置 concurrencyPolicy: Forbid。锁文件的权限也得注意,参见安全最佳实践。
#陷阱 6:容器里的 @reboot
@reboot 在 cron 守护进程启动时跑一次。在 VM 里它对应开机时刻;在容器里 cron 守护进程通常不是 PID 1,甚至根本没在跑。容器里不要用 @reboot,把启动时跑一次的逻辑放在 entrypoint 或 init container 里。
#陷阱 7:POSIX 中日与星期的 OR 语义
这是代价最高的 cron 陷阱。POSIX 规则:当日字段和星期字段都被约束(两者都不是 *)时,两者中任意一个匹配就会触发。
0 0 1 * 5 看起来像「每月 1 号的午夜,且必须是周五」,但实际上它在每月 1 号以及每个周五都会触发,每月多出六到十次。
1 | # 错:看起来像「月初 1 号,且必须是周五才跑」 |
把可疑表达式粘进 Crontab 生成器 — Cron 表达式构建与解析,看接下来几次触发时间,OR 陷阱一目了然。
#现代调度器:什么时候不该用 cron
cron 适合「在大致这个时间、按固定节奏跑一条命令」。下面这些相邻问题它就不擅长了:
- 有依赖关系的工作流(先 A,A 成功后跑 B):用 Airflow、Prefect、Dagster。
- 重试、指数退避、死信队列:用 Temporal、AWS Step Functions、Sidekiq。
- 亚分钟级间隔:用长生命周期 worker,在迭代间 sleep。
- 秒级精度:用专用守护进程;托管调度器都会声明不保证精确触发。
- 事件驱动型任务:用 webhook、消息队列、CDC 流。
cron 并没有被淘汰:Airflow、Step Functions、Sidekiq 在工作流的入口都接受 cron 表达式。这套五字段语法是可复用的。
#Kubernetes CronJob 字段参考
下面是 Kubernetes CronJob 语法的完整字段参考:
| 字段 | 默认值 | 作用 |
|---|---|---|
schedule |
必填 | POSIX 5 字段 cron 表达式 |
timeZone |
控制器所在时区 | 显式时区(1.27+);用 IANA 名称 |
concurrencyPolicy |
Allow |
Forbid 在上次还在跑时跳过;Replace 取消上次 |
startingDeadlineSeconds |
无限制 | 延迟超过该秒数则跳过 |
successfulJobsHistoryLimit |
3 |
保留多少个成功的 Job |
failedJobsHistoryLimit |
1 |
保留多少个失败的 Job |
suspend |
false |
暂停但不删除 |
backoffLimit |
6 |
标记 Job 失败前的 Pod 重试次数 |
activeDeadlineSeconds |
未设置 | Pod 运行时长的硬上限 |
ttlSecondsAfterFinished |
未设置 | Job 结束后多少秒自动删除 |
两个常见坑:忘掉 timeZone 会让调度跟着 kube-controller-manager 所在主机的时区跑(在托管 Kubernetes 上完全不可预测);如果是每分钟一次的调度,默认的 successfulJobsHistoryLimit: 3 会让 Job 对象按每分钟三个的速度堆积,除非配合 ttlSecondsAfterFinished 一起设置。
#跨平台的 Cron 等价物
macOS launchd。Apple 推荐用 launchd 替代 cron。一个 launchd 任务是放在 ~/Library/LaunchAgents/ 下的 .plist:
1 | <plist version="1.0"><dict> |
用 launchctl load ~/Library/LaunchAgents/com.example.daily.plist 加载。和 cron 不同,launchd 会在睡眠/唤醒后补上漏掉的执行。
Windows 任务计划程序 用 schtasks:
1 | schtasks /create /tn "DailyReport" /tr "C:\scripts\report.bat" /sc DAILY /st 09:00 |
在 WSL 上原生 Linux cron 也能用,但会话退出就停了。要让 WSL 任务始终在线,用任务计划程序拉起。
Docker 容器里的 cron。大多数精简镜像(alpine、debian-slim、distroless)出厂不带 cron 守护进程。装一个 cronie 或 busybox-cron,再用 tini 或 s6-overlay 把它跑成 PID 1。不过通常更好的选择是直接用 Kubernetes CronJob。
#进阶技巧与模式
#每月最后一天
cron 没有原生的「最后一天」运算符。在 28-31 这几天都跑一次,再判断明天是不是 1 号:
1 | 0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/eom.sh |
#当月第 N 个星期几
「第一个周一」用同样的 wrapper 模式,把日字段限制在 1-7,再判断星期:
1 | 0 9 1-7 * * [ "$(date +\%u)" = "1" ] && /usr/local/bin/first-monday.sh |
「最后一个周五」就把日改成 25-31 再加星期判断。
#用随机偏移分散负载
当大量机器跑同一个 cron 时,0 0 * * * 会在 UTC 午夜造成 thundering herd。撒点随机延迟:
1 | RANDOM_DELAY=10 # cronie / anacron,单位分钟 |
#心跳监控
cron 是静默失败的。dead-man’s-switch 模式可以解决:任务每次成功后向监控服务发一次 ping;如果预期的 ping 没到,监控服务报警。Healthchecks.io、Cronitor、Dead Man’s Snitch 都有免费层。
1 | */15 * * * * /usr/local/bin/job.sh && curl -fsS --retry 3 https://hc-ping.com/your-uuid |
如果监控逻辑要根据响应码分支(200 健康、429 限流、503 降级),参考 HTTP 状态码速查表。
#幂等性是任务自身的属性,不是调度器的
cron 没有重试、没有漏跑补偿、没有并发控制。最可靠的修法是让任务本身可以安全地重复执行。把「上午 9 点发当日报表」重新设计为「如果今天还没发过,就发」,漏跑、重复、人工补跑最终都会收敛到同一个状态。
#常见问题
#*/5 * * * * 真的是每 5 分钟一次吗?
几乎是 —— */5 * * * * 对齐到第 0 分钟,而不是「从现在起每 5 分钟」。每小时在第 0、5、10、…、55 分钟触发。步长 */N 是相对于字段的最小值,而不是当前时间。12:03 保存表达式,下一次是 12:05,不是 12:08。
#0 0 * * * 在 cron 里是什么意思?
0 0 * * * 表示每天午夜(00:00)按服务器本地时区执行。字段含义:分 0、时 0、任意日、任意月、任意星期。等价于宏 @daily 或 @midnight。要锁定时区,在 crontab 顶部加 CRON_TZ=America/New_York。
#怎么让 cron 任务每 30 秒跑一次?
标准 POSIX cron 做不到,最小粒度是 1 分钟。三个 workaround:两个 * * * * * 任务错开排,其中一个加 sleep 30 &&;或者用 systemd timer 配 OnCalendar=*:*:0/30;或者写一个长驻 worker 在迭代间 sleep。最后这种通常是正解。
#cron 默认用什么时区?
服务器的系统本地时区(/etc/timezone 或 TZ 环境变量)。一个写在 UTC 服务器上的 9 点 cron 会按美东凌晨 4 点跑。修法:在 crontab 顶部设置 CRON_TZ=,或者服务器统一 UTC、调度按 UTC 设计。GitHub Actions 永远是 UTC;Kubernetes 1.27+ 支持 spec.timeZone。
#为什么我的 cron 任务没运行?
如果 cron 任务没有运行,按顺序检查:cron 守护进程是否在跑(systemctl status cron);crontab 里是否设了 $PATH;stderr 有没有捕获(>> log 2>&1);用户的 crontab 是否真的加载了(crontab -l);命令里的 % 有没有转义;时区是否符合预期。绝大多数「没运行」的报告都卡在第二或第三条。
#Kubernetes CronJob 的语法和 Linux cron 一样吗?
调度字段是一样的,两者都用 POSIX 五字段 cron。Kubernetes 额外加了 spec.timeZone(1.27+)、用于并发控制的 concurrencyPolicy、用于漏跑补偿的 startingDeadlineSeconds,以及用于暂停的 suspend: true。Linux cron 没有这些,要靠 flock 和 anacron 凑合。
#@reboot 和 @daily 有什么区别?
@daily 是 0 0 * * * 的宏,每天午夜按固定节奏跑。@reboot 是在 cron 守护进程启动时跑一次,没有任何周期性。@reboot 在 vixie cron 和 cronie 里支持,但在 Kubernetes CronJob、GitHub Actions 和 AWS EventBridge 里都不支持。在容器里,@reboot 也几乎不会触发。
#cron 和 crontab 有什么区别?
cron 是运行计划任务的后台守护进程,crontab 是列出这些任务的文件(也是编辑该文件的 crontab 命令)。守护进程按计划读取每个用户的 crontab,运行执行时间与 cron 表达式匹配的命令。所以 cron 是引擎,crontab 是配方。
➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖
原文链接:https://go-tools.org/zh/blog/crontab-cheat-sheet-cron-expression-guide












