说到 Linux 服务器上的自动化,cron 是绕不开的基础设施。备份、日志清理、监控采样、定时同步……只要是需要「到点自动干活」的场景,都能看到它的身影。它没有 systemd timer 那么多花哨特性,但足够简单可靠,是运维工具箱里最常用的定时调度器。
这篇文章在 Ubuntu 24.04.4 LTS 上把所有命令真实执行验证了一遍(cron 3.0pl1-184ubuntu2),从五字段语法讲到实战注册、日志验证、常见坑与排障流程,附终端实测截图,可以直接照着抄。文末还会纠正一个流传很广、但在这版系统上已经过时的说法(关于 cron 的 PATH 环境变量)。
cron 是什么:系统内置的定时任务调度器
cron 是一个守护进程,默认每分钟检查一次各用户的任务表,到了执行时间的任务就交给 shell 去跑。Ubuntu/Debian 上运行的是 Debian 维护的 cron 3.0pl1,这是经典的 Paul Vixie cron 的血统,经过 Debian 团队多年持续修补,稳定性和安全性都有保障——Ubuntu 24.04 自带 3.0pl1-184ubuntu2(2024 年 3 月因 xz-utils 后门事件(CVE-2024-3094)做过一次全量重编译),截至 2026 年 Ubuntu 安全公告中该包不存在受影响漏洞。
提醒一个细节:网上很多教程教你用 cron -V 查版本,这在 RHEL/CentOS 系可以,但 Ubuntu 上会直接报 invalid option -- 'V'(实测如此),正确姿势是查软件包信息:
dpkg -l cron | tail -1五字段语法:一分钟看懂定时表达式
cron 的精髓是那条五字段表达式:
分 时 日 月 星期 命令| 字段 | 含义 | 取值范围 |
|---|---|---|
| 分 | 小时内的第几分钟 | 0-59 |
| 时 | 一天中的第几小时 | 0-23 |
| 日 | 一个月中的第几天 | 1-31 |
| 月 | 一年中的第几个月 | 1-12(也支持 jan,feb,…) |
| 星期 | 一周中的第几天 | 0-7,0 和 7 都代表周日(也支持 sun,mon,…) |
常用符号只有四个:
*任意值,* * * * *表示每分钟,列表,1,15 * * * *表示每小时的第 1 和第 15 分-范围,9-18 * * *表示 9 点到 18 点之间每小时/步长,*/5 * * * *表示每 5 分钟
几个经典例子:
*/5 * * * * 每 5 分钟
0 2 * * * 每天凌晨 2 点
0 9 * * 1-5 工作日(周一至周五)早上 9 点
0 0 1 * * 每月 1 号零点
30 4 * * 0 每周日凌晨 4:30
5-55/10 * * * * 每小时的 5 分、15 分……55 分,即每 10 分钟上面最后一条是 Ubuntu 上 sysstat 软件包的真实任务,后面讲系统级任务时还会见到它。
此外还有一组好记的别名:@reboot(开机时执行一次)、@hourly、@daily、@weekly、@monthly、@yearly。@reboot 常用于开机自动拉起脚本。
拿不准表达式时,用 crontab.guru 在线校验,输入后直接用人话告诉你执行时间。
crontab 管理命令:增删改查
每个用户都有自己的任务表,统一用 crontab 命令管理:
| 命令 | 作用 |
|---|---|
crontab -e | 编辑当前用户的任务表(首次会提示选择编辑器) |
crontab -l | 列出当前用户的任务 |
crontab -r | 删除全部任务(强烈建议 crontab -r -i,删除前会确认) |
crontab -u 用户名 -l | 查看其他用户的任务(需要 root) |
两个要点:
- 永远不要直接编辑
/var/spool/cron/crontabs/下的任务表文件。任务表必须通过crontab命令写入,因为它会做语法校验,出错的表达式会被拒绝;直接改文件的话,cron 只会静默跳过坏条目。 - 任务表在写入的瞬间生效,不需要重启 cron 服务。
cron 服务在 Ubuntu 上叫 cron:
systemctl is-active cron # 输出 active 即正常运行我在 Ubuntu 24.04.4 上实测的环境长这样(这也是本文所有演示的起点):

截图中依次是:软件包版本 3.0pl1-184ubuntu2、服务状态 active、以及一台全新服务器上 root 用户的空任务表(no crontab for root)。
实战:写一个系统负载记录脚本
理论说完,来点真的。目标:写一个脚本,每分钟把「当前时间 + 系统 1 分钟负载」追加到一个日志文件,然后验证它确实在按计划执行。
编写脚本
cat > /usr/local/bin/cron-demo.sh << 'EOF'
#!/bin/bash
# cron 演示脚本: 输出当前时间与系统 1 分钟负载
echo "$(date '+%F %T') load:$(awk '{print $1}' /proc/loadavg)"
EOF
chmod +x /usr/local/bin/cron-demo.sh注意两点:
awk的程序{print $1}用了单引号,$1才不会被 bash 提前展开。如果写成双引号"{print $1}",脚本里的$1会被展开成空,awk 收到的程序变成{print }——它会把整个 loadavg 行原样打出来(这是我实测时踩过的坑,日志里出现一长串0.00 0.00 0.00 2/170 65230就是它的症状);- 脚本要
chmod +x,路径用绝对路径。
注册任务:两种写法
写法一,echo 管道直接交给 crontab -:
echo '* * * * * /usr/local/bin/cron-demo.sh >> /var/log/cron-demo.log 2>&1' | crontab -写法二,先把任务写进文件再整体安装(推荐,便于备份与审计):
crontab /tmp/cron-demo.cron我注册了两条任务,故意留一条不带输出重定向的——下一节你就能看到它的下场:
* * * * * /usr/local/bin/cron-demo.sh >> /var/log/cron-demo.log 2>&1 # 输出重定向到日志文件
* * * * * /usr/local/bin/cron-demo.sh # 什么都不加,等着丢输出注册完立刻 crontab -l 就能看到任务:

截图里是演示环境的全部家当:脚本共 3 行(第 1 行 shebang、第 2 行注释、第 3 行才是真正的输出语句),任务表里躺着两条每分钟任务。
验证执行:日志里的真相
* * * * * 在每分钟的第 0 秒触发,不用等太久。一两分钟后再看结果:

这张截图信息量很大,拆开讲。
上半部分:带重定向的任务把输出写进了 /var/log/cron-demo.log,每分钟一行时间戳 + 负载(2026-08-05 20:15:01 load:0.04、20:16:01 load:0.01),说明任务确实在按计划执行,而且输出完整落盘。
下半部分:journalctl -u cron 能看到 cron 服务的全部日志。每行格式是 时间 主机名 CRON[进程号]: (用户名) CMD (实际执行的命令),注意三个细节:
- 每条任务的执行记录都在,精确到秒(都是
:01触发); - 截图中第 2 行那条
CMD (command -v debian-sa1 > /dev/null && debian-sa1 1 1)是 sysstat 软件包装的系统级任务(后面「系统级任务」一节会讲它)——系统任务和用户任务的执行记录在同一份日志里; - 不带重定向那条任务,日志里出现了关键提示:
(CRON) info (No MTA installed, discarding output)——它的输出被丢弃了,一条都没留下。这就是 cron 最大的坑,下面展开。
常见坑:新手翻车现场
坑 1:输出被静默丢弃(No MTA installed)
cron 的传统行为是:任务产生 stdout/stderr 输出时,通过邮件发给任务所有者(MAILTO)。但绝大多数服务器根本没装邮件服务(MTA),于是输出直接被丢弃——日志里只剩一句 No MTA installed, discarding output,任务「干没干活、干得怎样」完全无从得知。
解决办法:所有任务统一重定向输出:
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1>> 追加写入,2>&1 把标准错误并入同一个文件。这样任务的一切输出都留痕,配合后面的排障流程,一眼定位问题。
副作用:日志文件会无限增长,记得给它配 logrotate 轮转——日志轮转本身就是这套 cron 体系里的经典应用。
坑 2:PATH 环境变量(多数教程已过时)
传统说法:cron 执行环境极简,PATH 只有 /usr/bin:/bin,所以命令必须写绝对路径。这个说法在 Ubuntu 24.04 上已经过时了,我实测验证过——注册一条任务把 cron 环境里的 PATH 打出来:
echo '* * * * * echo PATH=$PATH' | crontab -下一条执行后,journal 里看到的是完整 PATH(实测输出原样):
Aug 05 20:18:01 demo CRON[65927]: (root) CMD (echo PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin)原因:Ubuntu 24.04 的 cron 服务以 -P 参数启动(见 /usr/lib/systemd/system/cron.service 中的 ExecStart=/usr/sbin/cron -f -P),cron 会继承 systemd 环境的完整 PATH。这是 2024 年 2 月 cron 3.0pl1-184ubuntu1 引入的变更,很多老教程没有跟上。
但绝对路径依然是好习惯:cron 不加载 ~/.bashrc、~/.profile,你的别名、export 出来的自定义变量它一概不认。稳妥做法是在 crontab 顶部显式声明环境:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1坑 3:% 号会被 cron 吞掉
crontab 文件里 % 是特殊字符,会被 cron 替换成换行符。所以 date +%Y%m%d 直接写进 crontab 行会当场裂开。两个对策:
- 转义:
date +\%Y\%m\%d; - 更干净的做法:把
date的格式串写进脚本内部——本文的演示脚本就是这么干的,所以 crontab 行里一个%都没有。
坑 4:脚本自身有问题
路径写错、忘了 chmod +x、脚本语法错误……任务照样「执行」,但什么都没发生。注册之前先手动跑一遍:
sudo -u 任务所属用户 /绝对/路径/脚本.sh
bash -x /绝对/路径/脚本.sh # 想看每步执行过程就加 -x系统级任务:/etc/crontab、/etc/cron.d 与 cron.{hourly,daily,weekly,monthly}
以上是用户级任务;系统级任务有另一套组织方式。
/etc/crontab:系统级 crontab,和用户 crontab 唯一的区别是多一个用户名字段(任务以指定用户身份执行)。Ubuntu 24.04 上它长这样(实测原文):
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; }
52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; }注意三件事:
/etc/cron.hourly、/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly四个目录由run-parts触发——目录里每个可执行、无扩展名的脚本都会被依次执行(logrotate、man-db、apt 清理等系统维护任务都住在这里);test -x /usr/sbin/anacron ||的意思是:如果装了 anacron(为便携机补跑错过的任务),调度权交给 anacron,否则由 cron 兜底;/etc/cron.d/:独立配置文件目录,格式和/etc/crontab一样(有用户名字段),软件包用它在不动系统 crontab 的前提下装自己的任务,修改后即时生效。
sysstat 装在 /etc/cron.d/sysstat 里的真实任务,正好是「范围 + 步长」组合的好例子:
5-55/10 * * * * root command -v debian-sa1 > /dev/null && debian-sa1 1 1
59 23 * * * root command -v debian-sa1 > /dev/null && debian-sa1 60 2每 10 分钟(5、15、25……55 分)采样一次系统活动数据,每晚 23:59 再轮转一次统计文件——这就是你在 journalctl -u cron 里看到的那条 debian-sa1。
排障流程:任务不执行怎么办
按这个顺序查,五分钟内定位 90% 的问题:
- 服务在不在:
systemctl is-active cron,不是 active 就先systemctl start cron再看 journal 里的报错; - 任务到底有没有被调度:
journalctl -u cron --since today --no-pager | grep 'CMD'。如果对应时间点根本没有 CMD 行,说明任务压根没被启动——问题在表达式语法或任务表本身(用 crontab.guru 校验,或crontab -e重写); - 有 CMD 行但没效果:多半是脚本问题——
bash -x手动执行,检查路径、权限、PATH; - 有 CMD 行,脚本也对:查输出去了哪——
grep 'MTA'找到No MTA installed,说明输出被丢弃,按坑 1 加重定向; - 临时验证环境:加一条每分钟任务打印环境(
echo PATH=$PATH),确认 cron 执行环境与预期一致。
cron vs systemd timer:怎么选
cron 不是唯一选择。systemd 的 timer 单元也能做定时任务,而且日志自动进 journald、支持 Persistent=true 补跑停机期间错过的任务、能写依赖关系、加随机延迟防抖动。简单周期任务用 cron 就够了——配置直观、老系统通用、不依赖 systemd;需要补跑、依赖编排、精细控制时,再考虑 systemd timer。
总结
- cron 每分钟检查一次任务表,五字段
分 时 日 月 星期+ 命令,*、,、-、/四个符号覆盖绝大多数需求; - 任务表用
crontab命令增删改查,别手改/var/spool/cron/crontabs/; - 任务输出必须重定向,否则
No MTA installed之后啥都留不下; - Ubuntu 24.04 的 cron 以
-P启动、继承完整 PATH,老教程的「PATH 只有 /usr/bin:/bin」已过时,但绝对路径 + 显式声明环境依然是好习惯; - crontab 里
%要转义或写进脚本内部;脚本先手动执行验证再注册; - 系统级任务分布在
/etc/crontab、/etc/cron.d/、/etc/cron.{hourly,daily,weekly,monthly},run-parts负责批量执行; - 任务不执行,先
journalctl -u cron | grep CMD判断是「没被调度」还是「执行了没生效」。
评论 (0)
暂无评论,快来抢沙发吧!