重复性的运维工作,交给定时任务
数据库备份、日志清理、磁盘空间监控、SSL 证书续期……这些事如果你一直手动做,总会有忘记的一天。定时任务就是把这些重复劳动交给系统自动执行:到点就跑、跑了就完、日志留痕。Linux 下有两套主流方案:传统的 crontab 和现代的 systemd timer。本文从 crontab 基础讲起,再带你用 systemd timer 搭一个可靠的自动备份任务,最后给出三个站长必配的实战场景。
一、crontab 五分钟入门
crontab 的配置格式是五个时间字段加一条命令:
分 时 日 月 周 命令常用命令:crontab -e 编辑当前用户的定时任务,crontab -l 查看,crontab -r 删除。下面这几个是出现频率最高的写法:
0 2 * * * /usr/local/bin/backup.sh # 每天凌晨 2 点
*/5 * * * * /usr/local/bin/check.sh # 每 5 分钟一次
0 3 * * 0 /usr/local/bin/weekly.sh # 每周日凌晨 3 点
0 0 1 * * /usr/local/bin/monthly.sh # 每月 1 号零点
30 4 1,15 * * /usr/local/bin/twice.sh # 每月 1 号和 15 号 4:30注意:星期字段里 0 和 7 都代表周日;*/n 表示每隔 n 个单位执行一次;多个值用逗号分隔。编辑保存后 crontab 立即生效,不需要重启任何服务。如果记不住这些写法,收藏下面这张速查表就够了:
| 写法 | 含义 |
|---|---|
| */10 * * * * | 每 10 分钟执行一次 |
| 0 * * * * | 每小时整点执行 |
| 30 1 * * * | 每天凌晨 1 点 30 分 |
| 0 4 * * 6 | 每周六凌晨 4 点 |
| 0 0 1 * * | 每月 1 号零点 |
| @reboot | 开机时执行一次 |
另外还有 @daily、@weekly、@monthly 这类简写,分别代表每天、每周、每月执行一次,适合不想记五段格式的场景,可读性也更好。
二、crontab 的三大坑
坑一:环境变量。 cron 执行脚本时的 PATH 非常精简,很多命令(比如 mysqldump)不在默认 PATH 里,脚本里裸写命令名会直接失败。解决办法:脚本里写命令全路径,或者在脚本开头 source /etc/profile 把环境加载进来。
坑二:输出丢失。 cron 执行的任务默认把标准输出通过邮件发走,很多服务器没配邮件服务,输出就悄悄丢了,出问题都不知道。规范做法是重定向到日志文件:
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1坑三:百分号转义。 命令里出现 % 需要写成 \%(例如 date +\%F),否则 cron 会把它当成换行处理,命令被截断,执行结果完全不是你想的那样。
除了上面三个坑,还要养成一个习惯:新建定时任务后,先手动执行一遍脚本确认无误,再把它写进 crontab。手动执行正常、cron 里却失败的情况,几乎都是环境变量或路径问题,按上面的方法排查即可。
三、为什么推荐 systemd timer
systemd timer 是 systemd 原生的定时任务方案,相比 crontab 有几个实打实的优势:
- 依赖管理:可以声明 After=network.target,网络就绪后才执行,不用担心脚本里 wget 因为网络没起来而失败;
- 失败重试:service 单元可以配置 Restart=on-failure,任务失败自动重试;
- 精确调度:OnCalendar 支持秒级精度,还支持夏令时等复杂规则;
- 日志统一:任务输出全部进 journald,一条 journalctl 命令就能查,不用自己维护日志文件;
- 错过补跑:Persistent=true 时,如果停机期间错过了执行点,开机后会自动补跑一次,这个特性对备份类任务特别重要。
另外,systemd timer 的配置本身就带校验:执行 systemd-analyze verify /etc/systemd/system/backup.timer 可以检查单元文件语法,写错的字段会直接报错,比 crontab「写错也不吭声」的作风友好得多。
四、实战:用 systemd timer 做自动备份
第一步,写服务单元 /etc/systemd/system/backup.service:
[Unit]
Description=Daily backup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal第二步,写定时器单元 /etc/systemd/system/backup.timer:
[Unit]
Description=Run backup daily at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target第三步,加载并启用:
systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers backup.timer # 查看下次执行时间
journalctl -u backup -n 30 # 查看执行日志Type=oneshot 表示这个服务只跑一次就结束,适合备份、清理这类一次性任务;OnCalendar 的格式和 crontab 不同,上面的写法表示每天 2 点 30 分,也支持星期,例如 Mon..Fri 03:00:00 表示工作日凌晨 3 点。
如果只想手动触发一次服务验证脚本是否正确,不需要等定时器:执行 systemctl start backup.service 会立即运行一次并留下日志。这个习惯特别重要——新写的定时任务先手动触发验证,确认没问题再交给定时器,能省掉大量排查时间。
五、三个站长必备的定时任务场景
场景一:MySQL 自动备份并保留最近 7 天。 脚本核心就两行:
mysqldump --single-transaction -u备份用户 -p密码 数据库名 | gzip > /backup/db_$(date +\%F).sql.gz
find /backup -name "*.sql.gz" -mtime +7 -delete--single-transaction 用于 InnoDB 表,备份过程中不锁表,不会影响线上访问;find 加 -mtime +7 -delete 自动清理 7 天前的旧备份,防止磁盘被备份文件占满。备份完顺手用 gzip -t 校验一下压缩包完整性,比什么都不检查强得多。
场景二:日志自动轮转。 直接用 logrotate,在 /etc/logrotate.d/ 下新建 nginx 配置:
/var/log/nginx/*.log {
weekly
rotate 4
compress
missingok
notifempty
}每周轮转一次、保留 4 份、旧日志压缩,nginx 日志文件不会无限膨胀。logrotate 本身由系统的 cron 驱动,你不需要自己写清理脚本。
场景三:SSL 证书自动续期。 certbot 自带续期命令,每天跑一次即可:
0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"renew 只在证书临近过期时才会真正续期,平时执行几乎不耗资源;续期成功后通过 --deploy-hook 重载 nginx,让新证书立即生效。这是全站 HTTPS 最省心的维护方式。
场景四:磁盘空间监控与告警。 网站跑着跑着磁盘满了,是个人站长最常遇到的高频事故,写个脚本每天检查一次:
#!/bin/bash
USE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USE" -gt 90 ]; then
echo "磁盘使用率已达 ${USE}%,请及时清理" | mail -s "磁盘告警" admin@example.com
fi超过 90% 就发邮件提醒,配合前面保留 7 天备份、logrotate 轮转日志的做法,基本能杜绝「磁盘满导致网站打不开」这类事故。
六、任务没执行怎么排查
- crontab 任务:先手动执行一遍脚本,确认脚本本身没问题;再看系统日志 journalctl -u cron 或 /var/log/syslog,搜索你配置的时间点附近有没有执行记录;
- systemd 任务:systemctl status backup.service 看最近一次执行状态,journalctl -u backup -n 50 看具体报错;
- 检查时区:timedatectl 确认服务器时区和预期一致,crontab 还可以在文件开头写 CRON_TZ=Asia/Shanghai 指定时区;
- 检查脚本权限:脚本要有执行权限(chmod +x),且第一行写清楚 #!/bin/bash 这类解释器声明,否则 cron 可能无法直接执行它。
最后提醒一句:排查定时任务问题时,先把脚本里的命令一条条手动执行,排除脚本自身的错误,再怀疑定时器配置。定时器本身很少出错,出问题的往往是脚本细节、环境变量和权限这几样东西。按「脚本 → 环境 → 调度」的顺序排查,效率最高,也最不容易绕弯路。
七、crontab 还是 systemd timer?
选择其实很简单:简单脚本、习惯传统、不想多学,crontab 完全够用,它几十年如一日地稳定;需要依赖管理、失败重试、精确到秒、统一日志的场景,用 systemd timer。两个方案可以共存,不存在冲突。关键不是选哪个,而是先把它配起来——备份和证书续期这种事,晚一天都可能出事。
常见问题(FAQ)
Q:为什么我的脚本手动执行正常,cron 里就是失败? A:十有八九是环境变量问题。cron 的 PATH 默认只有 /usr/bin:/bin,脚本里用全路径调用命令,或者直接在脚本开头写死 PATH,问题立刻消失。
Q:OnCalendar 有哪些常用写法? A:*-*-* 02:30:00 表示每天 2 点 30 分;Mon..Fri 03:00:00 表示工作日凌晨 3 点;*-*-1,15 表示每月 1 号和 15 号。格式拿不准时可以用 systemd-analyze calendar "表达式" 来验证,它会告诉你下次执行时间。
Q:定时任务和时区有关系吗? A:有。cron 和 systemd 默认用系统时区,服务器租在海外而时区没改的话,你以为是凌晨 2 点执行,实际可能是当地时间。先执行 timedatectl set-timezone Asia/Shanghai 把时区改对,再配置定时任务。
Q:服务器关机期间错过的任务会执行吗? A:crontab 错过的任务不会补跑;systemd timer 设置了 Persistent=true 后,开机时会自动补跑错过的执行点,这是它比 crontab 更适合备份任务的原因。
Q:定时任务执行时间不准怎么办? A:先确认系统时间本身准确,用 systemd-timesyncd 或 chrony 保持 NTP 同步,再检查时区。系统时间不对,所有定时任务都会跟着错。