一、什么时候需要定时任务?

  • 每天凌晨备份数据库
  • 每小时清理临时文件
  • 每周一发送报表邮件
  • 服务器重启后自动启动某个服务(@reboot

读完这篇你会:写出稳定可靠的定时任务,并且知道为什么你的任务没跑。

前置条件:能登录 Linux 服务器,有普通用户权限(部分操作需要 root)。我在 Ubuntu 22.04 和 CentOS 7 上验证过。


二、crontab 基础(90% 场景够用)

2.1 编辑你的定时任务

crontab -e   # 编辑当前用户的定时任务

第一次运行会让你选编辑器,我习惯选 nanovim

输入一行任务,例如每天凌晨 2 点 30 分执行备份脚本:

30 2 * * * /home/tony/backup.sh

保存退出。查看当前任务:

crontab -l

2.2 时间格式拆解

位置

含义

取值范围

1

分钟

0-59

2

小时

0-23

3

1-31

4

1-12 或 jan-dec

5

星期

0-7(0和7都是周日)或 mon-sun

特殊符号

  • * 任意值
  • , 列举:1,15 表示第1和15分钟
  • - 范围:1-5 表示1到5
  • / 步长:*/10 表示每10分钟

举个栗子

# 每小时的第5分钟执行
5 * * * * /path/to/command

# 每周一上午9点
0 9 * * 1 /path/to/command

# 每15分钟
*/15 * * * * /path/to/command

# 每月1号和15号凌晨3点
0 3 1,15 * * /path/to/command

2.3 快捷字符串(省脑子)

字符串

等价于

含义

@hourly

0 * * * *

每小时整点

@daily@midnight

0 0 * * *

每天0点

@weekly

0 0 * * 0

每周日0点

@monthly

0 0 1 * *

每月1号0点

@reboot

每次系统重启后执行一次

我的习惯:能用快捷字符串就用,减少拼写错误。

@daily /opt/scripts/cleanup.sh
@reboot /usr/local/bin/start_myapp.sh

三、系统级定时任务(root 或服务)

3.1 /etc/crontab 文件

系统级任务多了一个“用户”字段:

# 格式:分 时 日 月 周 用户 命令
0 2 * * * root /usr/bin/backup.sh

编辑系统任务需要 root 权限:sudo vim /etc/crontab

3.2 /etc/cron.d/ 目录

把每个任务单独放一个文件,方便用包管理分发。例如 /etc/cron.d/mysql-backup

30 3 * * * mysql_user /opt/scripts/mysql_backup.sh

3.3 预置目录(适合简单脚本)

  • /etc/cron.hourly/ – 每小时执行
  • /etc/cron.daily/ – 每天执行
  • /etc/cron.weekly/ – 每周执行
  • /etc/cron.monthly/ – 每月执行

把可执行脚本放进去就行。注意:脚本不能带点号(如 backup.sh 可以,backup.sh.old 会被忽略)。


四、我踩过的三个大坑

坑 1:环境变量不一样

cron 执行时 PATH 非常精简,可能只有 /usr/bin:/bin。你的脚本里如果用了 /usr/local/bin/mysqldump,就会报 command not found

解决方法

  • 在脚本里写绝对路径
  • 或者在 crontab 开头手动设置 PATH:
PATH=/usr/local/bin:/usr/bin:/bin
30 2 * * * /home/tony/backup.sh

坑 2:% 号需要转义

cron 中 % 表示换行。如果你在命令里用了日期格式化(比如 date +%Y%m%d),会莫名失败。

错误示例

0 1 * * * echo `date +%Y%m%d` > /tmp/date.txt   # 不行

正确做法:把命令放进脚本,或者在 crontab 里转义:

0 1 * * * echo `date +\%Y\%m\%d` > /tmp/date.txt

老实说,我选择写脚本,不在 crontab 里堆复杂逻辑。

坑 3:输出不处理,磁盘被日志填满

cron 默认会把命令的输出(stdout/stderr)发邮件给用户。如果你没配邮件,这些输出会积在 /var/mail/$USER,久而久之磁盘爆了。

解决:把输出重定向。

# 标准输出和错误都扔到黑洞
30 2 * * * /home/tony/backup.sh > /dev/null 2>&1

# 或者记到日志文件
30 2 * * * /home/tony/backup.sh >> /var/log/backup.log 2>&1

五、验证你的定时任务跑没跑

5.1 查看日志

  • Ubuntu/Debiangrep CRON /var/log/syslog
  • CentOS/RHELgrep CRON /var/log/cron

你会看到类似这样的行:

Jan 30 02:30:01 hostname CRON[12345]: (tony) CMD (/home/tony/backup.sh)
Jan 30 02:30:01 hostname CRON[12344]: (tony) CMDOUT (Backup finished)

5.2 手动跑一次脚本

# 模拟 cron 的最小环境
env -i PATH=/usr/bin:/bin /home/tony/backup.sh

如果这样能成功,cron 里大概率也能成功。


六、进阶:systemd timer(cron 的现代替代)

从 2016 年起,很多发行版(比如 CentOS 7+、Ubuntu 16.04+)开始推荐用 systemd timer。它的好处:

  • 可以设置随机延迟(避免所有机器同时执行压垮服务)
  • 错过执行时间后可以补跑
  • 日志直接进 journald,不用翻邮件
  • 支持日历事件(比如“每月最后一个周日”)

6.1 一个简单的 timer 例子

假设你要每天凌晨 3 点运行脚本 /opt/backup.sh

创建 service 单元 /etc/systemd/system/backup.service

[Unit]
Description=My backup script

[Service]
Type=oneshot
ExecStart=/opt/backup.sh

创建 timer 单元 /etc/systemd/system/backup.timer

[Unit]
Description=Run backup daily at 3am

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true   # 如果错过,重启后补跑

[Install]
WantedBy=timers.target

启用并启动

sudo systemctl daemon-reload
sudo systemctl enable backup.timer
sudo systemctl start backup.timer

查看 timer 状态

systemctl list-timers --all

你会看到下一次执行时间。

6.2 彩蛋:随机延迟

如果很多机器同时回调同一个 API,可以加随机延迟:

[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=3600   # 最多随机延迟1小时

这样每台机器会在 3 点到 4 点之间随机执行,错开高峰。


七、常见问题(真有人问过我)

Q:为什么我写的 crontab 没执行?
A:检查三件事:① 确认时间格式正确(用 crontab -l 再看一遍);② 看日志 /var/log/syslog/var/log/cron 有没有执行记录;③ 手动跑脚本看会不会报错。

Q:@reboot 不生效?
A:确认你的 cron 服务支持 @reboot(大部分都支持)。另外,@reboot 在系统启动时运行,但不会等待网络就绪。如果你的脚本需要网络,加个 sleep 30

Q:如何让任务只在工作日的上午 9 点到下午 5 点每 10 分钟执行一次?
A:*/10 9-17 * * 1-5 /path/to/command。注意周末(6、7)排除。

Q:systemd timer 和 cron 能混用吗?
A:能。但别用同一个脚本同时注册两套,否则会重复执行。


八、验证你学会了

自己动手:写一个定时任务,每天下午 3 点 15 分记录系统负载到 /tmp/load.log

参考答案

crontab -e
# 添加这一行
15 15 * * * uptime >> /tmp/load.log 2>&1

等第二天下午,查看 /tmp/load.log 是否有内容。或者把时间改成几分钟后测试(比如当前 14:50,写 51 14 * * * ...)。


定时任务看着简单,但生产环境里翻车最多的就是它——因为你写的时候没有真的跑。我建议每次新增任务后,先调成几分钟后执行,确认没问题再改回目标时间。

你遇到过哪些定时任务的诡异问题?来评论区说说,我帮你分析。如果觉得有用,分享给那个总在凌晨被 oncall 叫醒的兄弟。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐