# CRON定时任务表达式

> 用结构化方式讲清 CRON 表达式：先识别 5/6/7 位格式，再理解字段、符号、模板、平台差异和排错方法。

- 发布于：2020-09-20
- 更新于：2026-05-14
- 分类：学习
- 标签：后端、基础知识
- 原文：https://chanx.tech/blog/cron

CRON 表达式用来描述“一个任务什么时候执行”。

它常见于 Linux 定时任务、后端任务调度、Spring、Quartz、Node.js 定时任务库、云函数、CI/CD 定时流水线、Kubernetes CronJob 等场景。

但学习 CRON 时，最容易卡住的不是符号，而是不同平台的表达式格式不完全一样。

所以不要一上来就背 `*`、`,`、`/`。更好的学习顺序是：

```text
先确认平台格式
      ↓
再拆字段含义
      ↓
理解特殊符号
      ↓
套常见时间模板
      ↓
最后检查时区和运行环境
```

## 一、先识别 CRON 方言

同样是“每天 9 点执行”，不同平台可能写成不同形式。

| 平台类型 | 表达式 | 字段结构 |
| :--- | :--- | :--- |
| Linux crontab | `0 9 * * *` | 5 位，没有秒 |
| 带秒的任务库 | `0 0 9 * * *` | 6 位，第一位是秒 |
| Quartz / Spring 常见写法 | `0 0 9 * * ?` | 6 位，支持 `?` |
| Quartz 带年份 | `0 0 9 * * ? 2026` | 7 位，最后一位是年 |

写 CRON 前，先问一个问题：当前平台到底要几位？

这是最重要的一步。字段数量错了，后面每一位都会错位。

## 二、三种常见格式

### 1. Linux crontab：5 位

Linux `crontab` 的经典格式是：

```text
分钟 小时 日 月 星期
```

示例：

```text
30 2 * * *
```

含义：

```text
每天 2:30 执行
```

Linux crontab 没有秒字段。如果想做秒级任务，通常要用其他调度系统，或者在脚本内部循环处理。

### 2. 带秒格式：6 位

一些后端框架、Node.js 定时任务库、云函数平台会使用 6 位格式：

```text
秒 分钟 小时 日 月 星期
```

示例：

```text
0 30 2 * * *
```

含义：

```text
每天 2:30:00 执行
```

这个格式最容易和 Linux crontab 混淆，因为它只是多了最前面的秒字段。

### 3. Quartz 风格：6 位或 7 位

Quartz 常见格式是：

```text
秒 分钟 小时 日 月 星期 [年]
```

年字段通常可选。

示例：

```text
0 30 2 * * ? 2026
```

含义：

```text
2026 年每天 2:30:00 执行
```

Quartz 的特点是支持更多特殊符号，比如 `?`、`L`、`W`、`#`。

:::blue
不要把 Linux crontab、Quartz、Spring、云函数、GitHub Actions、Kubernetes CronJob 的表达式直接互相复制。先确认字段数量、星期编号、特殊符号和时区规则。
:::

## 三、字段地图

以 Quartz 7 位格式为例：

```text
秒 分钟 小时 日 月 星期 年
```

| 位置 | 字段 | 常见取值 | 例子 |
| :--- | :--- | :--- | :--- |
| 1 | 秒 | `0-59` | `0` 表示第 0 秒 |
| 2 | 分钟 | `0-59` | `30` 表示第 30 分钟 |
| 3 | 小时 | `0-23` | `9` 表示上午 9 点 |
| 4 | 日 | `1-31` | `1` 表示每月 1 号 |
| 5 | 月 | `1-12` 或 `JAN-DEC` | `1` 或 `JAN` |
| 6 | 星期 | 平台差异较大，也可用 `SUN-SAT` | `MON-FRI` |
| 7 | 年 | 常见为可选 | `2026` |

如果是 Linux crontab，就只保留：

```text
分钟 小时 日 月 星期
```

## 四、符号不是孤立背的

CRON 的特殊符号可以按“表达意图”来记。

| 想表达什么 | 符号 | 示例 | 含义 |
| :--- | :--- | :--- | :--- |
| 任意值 | `*` | `*` | 该字段的所有值 |
| 固定几个值 | `,` | `9,12,18` | 9、12、18 |
| 一段范围 | `-` | `9-18` | 9 到 18 |
| 每隔一段时间 | `/` | `*/5` | 每 5 个单位 |
| 不指定日或星期 | `?` | `?` | Quartz 中常用 |
| 最后一个 | `L` | `L` | 最后一天或最后一个星期几 |
| 最近工作日 | `W` | `15W` | 离 15 号最近的工作日 |
| 第几个星期几 | `#` | `MON#2` | 每月第二个周一 |

注意：Linux crontab 通常不支持 `?`、`L`、`W`、`#`。看到这些符号，大概率是在 Quartz 或类似平台里。

## 五、用模板理解常见时间

比起背语法，更实用的方法是记模板。

### 模板 1：每隔 N 分钟

Linux crontab：

```text
*/5 * * * *
```

含义：

```text
每 5 分钟执行一次
```

Quartz：

```text
0 0/5 * * * ?
```

含义：

```text
每 5 分钟的第 0 秒执行一次
```

### 模板 2：每天固定时间

每天凌晨 2 点：

```text
0 2 * * *
```

Quartz：

```text
0 0 2 * * ?
```

每天 9 点、12 点、18 点：

```text
0 9,12,18 * * *
```

Quartz：

```text
0 0 9,12,18 * * ?
```

### 模板 3：工作日固定时间

Linux crontab：

```text
0 9 * * 1-5
```

含义：

```text
周一到周五每天 9:00 执行
```

Quartz：

```text
0 0 9 ? * MON-FRI
```

这里 Quartz 用 `?` 放在“日”字段，表示不指定每月第几天，只按星期判断。

### 模板 4：每月固定日期

每月 1 号 10 点：

```text
0 10 1 * *
```

Quartz：

```text
0 0 10 1 * ?
```

每月 1 号和 15 号 10 点：

```text
0 10 1,15 * *
```

Quartz：

```text
0 0 10 1,15 * ?
```

### 模板 5：每年固定日期

每年 1 月 1 日 0 点：

```text
0 0 1 1 *
```

Quartz：

```text
0 0 0 1 1 ?
```

### 模板 6：秒级任务

Linux crontab 本身不支持秒级调度。

Quartz 每 10 秒执行一次：

```text
0/10 * * * * ?
```

一些 6 位 Node.js 任务库可能写成：

```text
*/10 * * * * *
```

这类表达式一定要看库文档，因为不同库对秒字段和特殊符号的支持不同。

## 六、“日”和“星期”要单独讲

CRON 中最容易产生误解的是“日”和“星期”两个字段。

看这个 Linux crontab：

```text
0 9 1 * 1
```

它很容易被理解成：

```text
每月 1 号，并且这一天是周一时执行
```

但很多传统 crontab 实现会按“或”的关系处理：

```text
每月 1 号执行，或者每周一执行
```

Quartz 为了避免这个歧义，引入了 `?`。

| 需求 | Quartz 写法 |
| :--- | :--- |
| 每月 1 号 9 点 | `0 0 9 1 * ?` |
| 每周一 9 点 | `0 0 9 ? * MON` |

记法很简单：

```text
指定“日”时，“星期”用 ?
指定“星期”时，“日”用 ?
```

## 七、速查表

### Linux crontab

| 需求 | 表达式 |
| :--- | :--- |
| 每分钟 | `* * * * *` |
| 每 5 分钟 | `*/5 * * * *` |
| 每小时整点 | `0 * * * *` |
| 每天 0 点 | `0 0 * * *` |
| 每天 2:30 | `30 2 * * *` |
| 工作日 9 点 | `0 9 * * 1-5` |
| 每天 9 点和 18 点 | `0 9,18 * * *` |
| 每月 1 号 0 点 | `0 0 1 * *` |
| 每年 1 月 1 日 0 点 | `0 0 1 1 *` |

### Quartz

| 需求 | 表达式 |
| :--- | :--- |
| 每 10 秒 | `0/10 * * * * ?` |
| 每 5 分钟 | `0 0/5 * * * ?` |
| 每小时整点 | `0 0 * * * ?` |
| 每天 0 点 | `0 0 0 * * ?` |
| 每天 2:30 | `0 30 2 * * ?` |
| 工作日 9 点 | `0 0 9 ? * MON-FRI` |
| 每月 1 号 9 点 | `0 0 9 1 * ?` |
| 每月第一个周一 9 点 | `0 0 9 ? * MON#1` |
| 每月最后一天 18 点 | `0 0 18 L * ?` |

## 八、在 Linux crontab 中落地

Linux crontab 不只是一个表达式，它还要跟命令一起写。

编辑当前用户的定时任务：

```bash
crontab -e
```

查看当前用户的定时任务：

```bash
crontab -l
```

删除当前用户的全部定时任务：

```bash
crontab -r
```

一个完整任务：

```bash
0 2 * * * /usr/bin/node /app/scripts/backup.js >> /var/log/backup.log 2>&1
```

结构拆开看：

| 片段 | 含义 |
| :--- | :--- |
| `0 2 * * *` | 每天 2 点 |
| `/usr/bin/node` | 使用绝对路径调用 Node |
| `/app/scripts/backup.js` | 要执行的脚本 |
| `>> /var/log/backup.log` | 追加标准输出到日志 |
| `2>&1` | 把错误输出也写进同一个日志 |

如果脚本依赖项目目录，可以显式 `cd`：

```bash
0 2 * * * cd /app && /usr/bin/pnpm run backup >> /var/log/backup.log 2>&1
```

## 九、时区是隐藏坑

CRON 写对了，任务仍然可能在“错误时间”执行。

常见原因是时区：

| 环境 | 常见情况 |
| :--- | :--- |
| Linux 服务器 | 取决于系统时区 |
| Docker 容器 | 默认可能是 UTC |
| 云函数 | 可能固定使用 UTC |
| CI/CD | 常见为 UTC |
| Kubernetes CronJob | 新版本可配置 `timeZone` |

先看当前机器时间：

```bash
date
```

Kubernetes CronJob 如果支持 `timeZone`，可以显式配置：

```yaml
spec:
  timeZone: Asia/Shanghai
  schedule: "0 2 * * *"
```

如果平台不支持时区配置，就要把本地时间换算成平台使用的时区。

## 十、排错清单

### 1. 字段数量是否正确

这是第一优先级。

| 你写的 | 放错平台后的风险 |
| :--- | :--- |
| `0 9 * * *` | 在 6 位平台中可能字段不足或含义错位 |
| `*/5 * * * * *` | 在 6 位平台表示每 5 秒，不是每 5 分钟 |

如果平台要求 6 位，每天 9 点通常应写成：

```text
0 0 9 * * *
```

Quartz 通常写成：

```text
0 0 9 * * ?
```

### 2. 星期编号是否符合平台规则

星期字段差异很大：

| 可能规则 | 含义 |
| :--- | :--- |
| `0` | 周日 |
| `1` | 周一 |
| `1` | 周日 |
| `7` | 周日 |
| `MON-FRI` | 周一到周五 |

能写英文缩写时，`MON-FRI` 通常比 `1-5` 更清楚。

### 3. “日”和“星期”是否同时指定

Quartz 中建议用 `?` 避免歧义：

```text
0 0 9 1 * ?
```

表示每月 1 号 9 点。

```text
0 0 9 ? * MON
```

表示每周一 9 点。

### 4. 命令是否能在非交互环境执行

手动执行成功，crontab 失败，常见原因不是表达式，而是环境不同。

重点检查：

- 命令是否使用绝对路径；
- 工作目录是否正确；
- 环境变量是否存在；
- 当前用户是否有权限；
- 是否把日志输出到文件；
- 脚本是否依赖交互式 shell。

### 5. 是否真的到了执行时间

调试时不要只盯着表达式，可以手动推演接下来三次执行时间。

例如：

```text
0 0 9 ? * MON-FRI
```

翻译成自然语言：

```text
周一到周五每天 9:00:00 执行
```

如果现在是周五 10 点，下一次执行应该是下周一 9 点。

## 十一、学习 CRON 的检查方法

每写一个表达式，都按下面的问题过一遍：

1. 这个平台要 5 位、6 位还是 7 位？
2. 第一位是不是秒？
3. 是否支持 `?`、`L`、`W`、`#`？
4. 星期字段从哪一天开始编号？
5. “日”和“星期”有没有同时指定？
6. 平台使用哪个时区？
7. 命令本身有没有日志、权限和环境变量问题？

这比单纯背表达式更可靠。

## 十二、总结

CRON 可以分成四层理解：

1. **格式层**：先确认 5 位、6 位还是 7 位。
2. **字段层**：明确每一位代表分钟、小时、日、月、星期，还是秒和年。
3. **符号层**：用 `*`、`,`、`-`、`/`、`?` 表达任意、枚举、范围、间隔和空置。
4. **运行层**：检查平台差异、时区、命令路径、环境变量和日志。

只要先判断方言，再套模板，最后检查运行环境，CRON 表达式就不会只是几串难背的星号，而会变成一套可以稳定落地的时间规则。
