最近经常有人问我Linux上怎么做定时任务,我自己摸索了挺久,踩过不少坑,把目前实际在用的方法整理了一下。

现在主要有两种方案:Cron 和 Systemd Timer。我自己现在新项目基本都用后者,主要是因为 Systemd Timer 比较新,但 Cron 也没完全淘汰,还是能满足日常需求。

定时需求场景

定时任务说白了就是让系统自己定时干活,常见的有:

  • 每周日定时备份我的博客数据库
  • 定期清理Docker镜像和日志
  • 自动检查某个服务是不是还活着,挂了就重启或者告警
  • 定时更新部署的开源服务,保持最新版本

当然大厂不会这样直接使用命令行来维护定时任务,我是个人服务器上使用,所以直接使用命令比较简单,也能满足我的需求。

下面介绍这两种定时任务使用方法:

Cron守护进程

作为开发,你可能多多少少了解过 Cron 表达式,所以 Cron 配置的时间也是下面的格式:

分钟 小时 日 月 星期 命令

用法很简单,只需在 Cron 表达式后面加一下 命令或bash文件 就可以了,我们输入 crontab -e 进行编辑模式:

# 每天凌晨2点备份
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
 
# 每5分钟检查一次服务
*/5 * * * * /usr/local/bin/check.sh
 
# 工作日早上9点发报告
0 9 * * 1-5 /usr/local/bin/report.sh

管理起来也很简单,crontab -e 编辑,crontab -l 查看。我一般会把输出都重定向到日志文件,方便查看定时任务执行日志,定位问题。

注意文件路径必须要是绝对路径,不然跑不起来!

还有定时任务执行是读取不到顶层环境变量,建议在定时任务运行的脚本文件头部添加你的环境变量,例如在文件 /usr/local/bin/check.sh 添加环境变量:

#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin
export MY_VAR=my_value
export LANG=zh_CN.UTF-8

Cron 使用就这么简单,接下来介绍下 Systemd Timer。

Systemd Timer

现在新机器我基本都切到 Systemd Timer 了。它要写两个文件,稍微麻烦一点,但使用起来感觉更舒服点。

举个我实际在用的例子,每天凌晨3点自动更新Docker Compose 项目。

先写服务文件 /etc/systemd/system/docker-update.service:

[Unit]
Description=Daily Docker Compose Update
After=docker.service
Requires=docker.service
 
[Service]
Type=oneshot
WorkingDirectory=/opt/myapp
ExecStart=/bin/bash -c 'docker compose pull && docker compose up -d'

这里我踩过坑。一开始直接写 ExecStart=docker compose pull && docker compose up -d,结果根本不执行。systemd 默认不走 shell,&& 会被当成参数传进去。后来改成用 /bin/bash -c 包一层才正常。一般复杂一点的逻辑我现在都写成脚本再调用。

再写定时器文件 /etc/systemd/system/docker-update.timer:

[Unit]
Description=Run Docker Update Daily at 3 AM
 
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300
 
[Install]
WantedBy=timers.target

Persistent=true 这个我配置很喜欢,机器如果刚好在定时执行时间点死机重启了,下次开机它会补跑一次。

RandomizedDelaySec 是为了避免一堆机器同时去拉镜像把仓库打挂。

启用定时任务:

sudo systemctl daemon-reload
sudo systemctl enable --now docker-update.timer

之后用 systemctl list-timers 看下次什么时候跑,用 journalctl -u docker-update.service 看日志,我觉得比 Cron 清爽多了。

时间格式我也记了几个常用的,更复杂的可以让 AI 帮你生成。

  • 每天凌晨:*-*-* 02:00:00 或者直接写 daily
  • 每周一早上:Mon *-*-* 09:00:00
  • 开机后5分钟:OnBootSec=5min

如何选择定时任务

简单说一下我现在的判断标准:

场景 我更倾向用
一行就能搞定的小脚本 Cron
需要看日志、依赖其他服务、机器可能关机 Systemd Timer
要限制资源、做权限隔离 Systemd Timer
需要在非systemd的环境跑 Cron

生产环境我现在几乎都用Timer了。日志能直接用journalctl查,失败了也能看到原因,补跑机制也省心。Cron我留着处理一些临时的小任务,或者老机器上的遗留脚本。

其他几个我也偶尔用到的

  • at:只想跑一次的任务,比如“今天下午三点执行这个命令”。
  • anacron:笔记本或者经常关机的机器,它能处理错过的任务。
  • 用户级Timer:不需要root权限的时候,把文件放到 ~/.config/systemd/user/ 下面,用 systemctl --user 管理。

出问题了怎么查

  • 任务没跑:先看 systemctl list-timers 或者 crontab -l,确认有没有启用。
  • 跑了但失败:Timer的话直接执行 journalctl -u 服务名 看日志,Cron 看重定向的日志。
  • 执行的时间不对:用 timedatectl 看一下系统时区,两者默认都用本地时间。

写在最后

我现在的习惯是:新任务优先用 Systemd Timer,老的小脚本继续用 Cron。两者都能干活,但Timer在日志和可靠性上确实更省心。尤其是你也在做自动更新、备份这类事的时候,花点时间把Timer配好,后面会少很多排查时间。