
Cron 是一个调度守护进程,会在指定的时间间隔执行任务。这些任务称为 cron 作业(cron job),最常用于自动化系统维护、运行备份以及触发周期性脚本。
例如,你可以创建一个 cron 作业来自动化重复性任务,例如备份数据库、使用最新安全补丁更新系统、检查磁盘空间使用情况或发送电子邮件。
Cron 作业可以按分钟、小时、每月的某天、月份、每周的某天,或这些条件的任意组合来调度运行。
正在规划调度?我们的交互式 crontab 生成器能用通俗英语解释任何 cron 表达式,并展示下次运行的时间。
快速参考
如需可打印的快速参考,请参阅 crontab 速查表。
| 调度场景 | Cron 表达式 |
|---|---|
| 每分钟 | * * * * * |
| 每 5 分钟 | */5 * * * * |
| 每 15 分钟 | */15 * * * * |
| 每小时 | 0 * * * * |
| 每天午夜 | 0 0 * * * |
| 每天上午 9 点 | 0 9 * * * |
| 工作日每天上午 9 点 | 0 9 * * 1-5 |
| 每周日午夜 | 0 0 * * 0 |
| 每月 1 日 | 0 0 1 * * |
| 每年 1 月 1 日 | 0 0 1 1 * |
| cron 守护进程启动时 | @reboot |
什么是 Crontab 文件
Crontab(cron table)是一个文本文件,用于指定 cron 作业的调度计划。Crontab 文件有两种类型:系统级 crontab 文件和用户级 crontab 文件。
用户级 crontab 文件以用户名命名,其存放位置因发行版而异。在 Red Hat、Fedora 及其衍生版中,crontab 文件保存在 /var/spool/cron 目录;在 Debian、Ubuntu 及其衍生版中,保存在 /var/spool/cron/crontabs。
虽然你可以手动编辑用户级 crontab 文件,但建议使用 crontab 命令来进行管理。
/etc/crontab 文件以及 /etc/cron.d 目录下的脚本是系统级 crontab 文件,只能由系统管理员编辑。
在大多数 Linux 发行版中,你还可以把脚本放入 /etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly 这几个目录,它们会按目录名称所暗示的时间间隔运行。这些目录有自己的一些规则,下文会进一步说明。
Crontab 语法与运算符
用户级 crontab 文件中的每一行都包含五个由空格分隔的日期和时间字段,后面紧跟要运行的命令:
* * * * * command(s) - - - - - | | | | | | | | | ----- Day of week (0 - 7) (Sunday=0 or 7) | | | ------- Month (1 - 12) | | --------- Day of month (1 - 31) | ----------- Hour (0 - 23) ------------- Minute (0 - 59)
前五个字段可以包含一个或多个用逗号分隔的值,或用一个连字符分隔的值范围。
*—— 星号表示任意值。Hour 字段中的星号表示任务每小时运行。,—— 逗号允许你指定一个值列表。例如在 Hour 字段中1,3,5表示任务在凌晨 1 点、3 点和 5 点运行。-—— 连字符指定一个值范围。例如在 Day of week 字段中1-5表示每个工作日(周一到周五)运行任务。/—— 斜杠指定步长值。例如在 Hour 字段中*/4表示任务每四小时运行一次,相当于0,4,8,12,16,20。你也可以使用范围:1-30/10等同于1,11,21。
步长只在其所应用的字段内求值。在 Hour 字段中写 */23 并不意味着“每 23 小时”,它表示同一天的第 0 小时和第 23 小时,然后计数在午夜重新开始。
每个字段接受固定范围的值:
| 字段 | 允许值 |
|---|---|
| 分钟 | 0-59 |
| 小时 | 0-23 |
| 每月的某天 | 1-31 |
| 月份 | 1-12 或 JAN-DEC |
| 每周的某天 | 0-7 或 SUN-SAT(0 和 7 都表示周日) |
名称不区分大小写,因此 Mon、mon 和 MON 都有效。不同实现对名称和范围的列表支持有所不同。在使用 cronie 的 Fedora、RHEL 及其衍生版上,mon-fri 和 jan,apr,jul 是有效的;在 Debian、Ubuntu 及其衍生版上,不允许使用名称的范围和列表,因此请使用数字形式,例如 1-5。
每月的某天与每周的某天
每月的某天和每周的某天这两个字段的行为与其他三个不同。当两个字段都被限制(即都不是星号)时,cron 会在任意一个字段匹配时运行作业,而不是在两个字段都匹配时运行。
这一点几乎每个人都踩过坑。看下面这一行:
30 4 1,15 * 5 /path/to/script.sh
它读起来像是“在 1 日和 15 日的凌晨 4:30,但仅限于周五”。Cron 将其理解为“每月 1 日和 15 日的凌晨 4:30,加上每周五的凌晨 4:30”。在普通月份中,这大约会运行六次,而不是零次或一次。
由于这条规则,你无法仅凭时间字段表达“每月第一个周一”。将每月的某天限制在前七天,把每周的某天设为星号,并在命令内部判断星期几:
0 7 1-7 * * test $(date +\%u) -eq 1 && /path/to/script.sh
1-7 的范围将作业限制在每月的第一周,而 date +%u 返回星期几(1 表示周一,7 表示周日)。两者结合使脚本只在第一个周一运行。百分号前的反斜杠是必需的,下一节将解释原因。
转义百分号
在 crontab 中,百分号并不是普通字符。Cron 会读取第一个未转义的 % 之前的命令并执行该部分,把其后的所有内容作为标准输入发送给命令,每个额外的 % 会被转换为换行符。
这会悄然破坏任何使用了 date 格式说明符的命令。下面这个备份作业并不会像看起来那样工作:
0 2 * * * /usr/bin/tar -czf /backup/site-$(date +%Y-%m-%d).tar.gz /var/www
Cron 在第一个 % 处将行拆分,因此 shell 收到的是不完整的命令 tar -czf /backup/site-$(date +,并在 tar 启动前因语法错误退出。Cron 把行的其余部分准备为标准输入,但没有正在运行的命令来接收它。要对每个百分号使用反斜杠转义,以便将其传递给 shell:
0 2 * * * /usr/bin/tar -czf /backup/site-$(date +\%Y-\%m-\%d).tar.gz /var/www
当一条命令需要不止几个百分号时,把它移到一个 shell 脚本中,再从 cron 调用该脚本。Cron 不会解析脚本的内容,因此脚本内部无需转义。
系统级 Crontab 文件
系统级 crontab 文件的语法与用户级 crontab 略有不同。它包含一个额外的必填用户字段,用于指定由哪个用户运行该 cron 作业:
* * * * * <username> command(s)
Cron 目录与 anacron
除了 crontab 文件,大多数发行版还提供了一组目录,它们按固定时间表运行脚本,而无需你自己添加任何 crontab 条目。把一个可执行脚本放入 /etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly 或 /etc/cron.monthly,它就会按该间隔运行。
有两个细节决定脚本是否真正运行:它需要可执行权限位,并且文件名只能包含字母、数字、下划线和连字符。执行这些目录的助手 run-parts 会跳过任何带点的文件名,因此保存为 backup.sh 的脚本会被忽略,而 backup 会运行。
在 Debian、Ubuntu 及其衍生版上,这些目录由 /etc/crontab 中的 run-parts 行驱动:
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
test -x /usr/sbin/anacron 这个守卫值得仔细看。如果安装了 anacron,该 cron 行什么都不做,改由 anacron 接管每日、每周和每月的作业。Fedora、RHEL 及其衍生版通过 /etc/cron.d/0hourly 达到相同效果,它每小时运行 /etc/cron.hourly,其中的 0anacron 脚本把更长的间隔交给 anacron。
Anacron 的存在是因为 cron 假设机器在调度时刻处于运行状态。它会记录每个作业上次完成的时间,并在开机后不久补跑被错过的运行。这解释了常见的一个现象:笔记本电脑或台式机上的每日作业从不运行。如果没有 anacron,run-parts 行在 06:25 触发,而此时关机的机器会直接跳过那一天。
预定义宏
有几个特殊的 cron 调度宏可用于指定常见的时间间隔。你可以用这些快捷方式替代五列日期规格:
@yearly(或@annually)—— 每年 1 月 1 日午夜运行一次。等同于0 0 1 1 *。@monthly—— 每月 1 日午夜运行一次。等同于0 0 1 * *。@weekly—— 每周日午夜运行一次。等同于0 0 * * 0。@daily(或@midnight)—— 每天午夜运行一次。等同于0 0 * * *。@hourly—— 每小时开始时运行一次。等同于0 * * * *。@reboot—— cron 守护进程启动时运行,通常发生在系统引导期间。重启守护进程可能会再次运行该作业。
crontab 命令
crontab 命令允许你安装、查看或打开 crontab 文件进行编辑:
警告:
crontab -r 会在无确认的情况下删除整个 crontab。请先用 crontab -l > crontab.backup 备份,或使用 crontab -r -i 在要求确认后再删除。crontab -e—— 编辑 crontab 文件,如果不存在则创建。crontab -l—— 显示 crontab 文件内容。crontab -r—— 删除你当前的 crontab 文件。crontab -r -i—— 在删除前请求确认。-i选项仅修改-r,单独使用时无效。sudo crontab -u <username> -e—— 编辑其他用户的 crontab 文件。
crontab 命令使用 VISUAL 或 EDITOR 环境变量指定的编辑器打开文件。
Crontab 环境变量
cron 守护进程会自动设置几个环境变量:
- 默认 PATH 比你交互式 shell 中的短得多,具体值取决于 cron 的实现。在 Debian、Ubuntu 及其衍生版中为
/usr/bin:/bin;在 Fedora、RHEL 及其衍生版(使用 cronie)中为/usr/bin:/bin:/usr/sbin:/sbin。如果你运行的命令不在该路径中,请使用二进制文件的绝对路径,或在 crontab 顶部设置自定义 PATH。你不能像在普通脚本中那样隐式追加到$PATH,因为 cron 不会展开这些赋值中的变量。 - 默认 shell 是
/bin/sh。要使用其他 shell,请在 crontab 顶部设置SHELL变量。 - Cron 从用户的主目录运行命令。可用
HOME变量覆盖。 - 默认情况下,输出会通过邮件发送给 crontab 所有者。设置
MAILTO=email@example.com重定向通知,或设置MAILTO=""完全禁用邮件。
Crontab 访问限制
/etc/cron.deny 和 /etc/cron.allow 文件让你可以控制哪些用户能够访问 crontab 命令。每个文件包含一个用户名列表,每行一个。
如果 /etc/cron.allow 存在,只有其中列出的用户可以使用 crontab 命令。当该文件不存在但 /etc/cron.deny 存在时,除 /etc/cron.deny 中列出的用户外,所有人都可以使用该命令。
如果两个文件都不存在,行为取决于 cron 的实现。标准的 Debian 系统允许所有用户使用 crontab,而其他一些实现则限制为仅 root 用户可访问。
Cron 作业示例
如果你正在创建第一个 cron 作业,请使用这个安全的工作流程:
- 使用
crontab -e打开你的 crontab。 - 添加一个简单的测试作业,例如
*/5 * * * * date >> /tmp/cron-test.log。 - 保存文件并用
crontab -l确认已安装。 - 等待几分钟并验证
/tmp/cron-test.log已更新。
一旦上述流程成功,下面的示例涵盖了你最可能需要用到的调度。
- 每个工作日(周一至周五)15:00 运行命令:
0 15 * * 1-5 command - 每 5 分钟运行脚本并将标准输出重定向到
/dev/null,只通过邮件发送错误:MAILTO=email@example.com */5 * * * * /path/to/script.sh > /dev/null - 每周一 15:00 运行两个命令:
0 15 * * Mon command1 && command2 - 每 2 分钟运行一个 PHP 脚本并将输出追加到日志文件:
*/2 * * * * /usr/bin/php /path/to/script.php >> /var/log/script.log - 每天 8:00 至 16:00 的每个整点运行脚本:
0 08-16 * * * /path/to/script.sh - 每个季度第一天 6:00 运行脚本:
0 6 1 1,4,7,10 * /path/to/script.sh - 每月 1 日和 15 日 21:15 运行脚本:
15 21 1,15 * * /path/to/script.sh - 设置自定义环境变量并每分钟运行命令:
HOME=/opt PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin SHELL=/bin/bash MAILTO=email@example.com * * * * * command
故障排查
Cron 作业不运行:先用 crontab -l 检查语法,并先在 shell 中手动测试命令。同时用 systemctl status cron(Debian/Ubuntu)或 systemctl status crond(Red Hat/Fedora)确认 cron 守护进程正在运行。
命令在 shell 中能用但在 cron 中不行:Cron 使用精简的 PATH(/usr/bin:/bin)。请使用二进制文件的绝对路径(例如用 /usr/bin/python3 而不是 python3),或在 crontab 顶部设置完整的 PATH。
脚本运行但没有输出或邮件:Cron 通过邮件发送输出,这需要一个可用的邮件传输代理。要可靠地捕获输出,请将其重定向到文件:command >> /var/log/myjob.log 2>&1。要同时重定向 stdout 和 stderr,请参阅 bash 重定向 stderr/stdout 指南。
没有任何作业启动的迹象:Cron 会记录它启动的每个作业,这能将调度问题与命令失败区分开。在 Debian 和 Ubuntu 上,运行 journalctl -u cron --since today 或 grep CRON /var/log/syslog;在 Fedora、RHEL 及其衍生版上,运行 journalctl -u crond --since today。如果你的命令出现在日志中,说明 cron 已尽责,失败在命令内部。
作业在不正确的时间运行:Cron 使用系统时区,你可以用 timedatectl 检查。Cronie 支持在 crontab 顶部使用 CRON_TZ=Region/City 以其他时区调度该表。Debian 的 cron 不支持按 crontab 设置调度时区:设置 TZ 只会改变命令环境,而不会改变作业的运行时间。在 Debian 上,请使用系统时区,或在包装脚本中检查目标时区。
运行脚本时权限被拒绝:用 chmod +x /path/to/script.sh 使脚本可执行,并确保 cron 用户对该文件具有读和执行权限。
常见问题
如何编辑我的 crontab?运行 crontab -e。这会在默认编辑器中打开你的用户 crontab。保存并退出即可安装新调度,更改立即生效。
用户 crontab 和 /etc/crontab 有什么区别?用户 crontab(用 crontab -e 管理)以该用户身份运行作业,包含五个时间字段加命令。/etc/crontab 和 /etc/cron.d/ 中的文件是系统级的,包含一个额外的用户名字段,且只能由 root 编辑。
如何将 cron 作业输出重定向到文件?在你的 cron 命令后追加 >> /path/to/logfile.log 2>&1。2>&1 部分将 stderr 重定向到 stdout,以便两者都捕获在同一个文件中。
如何在系统启动时运行 cron 作业?使用 @reboot 宏而不是时间表达式:@reboot /path/to/script.sh。该作业在 cron 守护进程启动时运行,通常发生在系统引导期间。由于重启守护进程可能会再次运行它,请确保该命令可以安全地重复执行。
结语
Cron 是 Linux 上自动化周期性任务的标准工具。理解五字段时间语法、运算符和环境变量,就能完全掌控作业的运行时间和方式。要列出和管理现有作业,请参阅 crontab 列表指南。