当前位置:首页>Linux>Linux 定时任务:别让你的备份脚本睡过头

Linux 定时任务:别让你的备份脚本睡过头

  • 2026-09-09 19:32:59
Linux 定时任务:别让你的备份脚本睡过头

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

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

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

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


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

2.1 编辑你的定时任务

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

第一次运行会让你选编辑器,我习惯选 nano 或 vim。

输入一行任务,例如每天凌晨 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:/bin30 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/Debian
    :grep CRON /var/log/syslog
  • CentOS/RHEL
    :grep 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=oneshotExecStart=/opt/backup.sh

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

[Unit]Description=Run backup daily at 3am[Timer]OnCalendar=*-*-* 03:00:00Persistent=true   # 如果错过,重启后补跑[Install]WantedBy=timers.target

启用并启动:

sudo systemctl daemon-reloadsudo systemctl enable backup.timersudo systemctl start backup.timer

查看 timer 状态:

systemctl list-timers --all

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

6.2 彩蛋:随机延迟

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

[Timer]OnCalendar=*-*-* 03:00:00RandomizedDelaySec=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 叫醒的兄弟。

最新文章

随机文章