别再写 crontab 了:systemd timer 才是定时任务的现代答案

crontab 的四个老毛病

crontab 用了几十年,但它身上的毛病一直没改:

  1. 日志靠自己重定向:不写 >> /var/log/xxx.log 2>&1 就等于没日志,排查全靠猜;
  2. 关机错过的任务直接丢:笔记本合盖一晚上,早上的备份任务就无声无息地没了;
  3. PATH 环境变量玄学:cron 的环境和你登录 shell 不一样,command not found 是家常便饭;
  4. 秒级不支持:最小粒度 1 分钟,想 30 秒跑一次得写 sleep Hack。

systemd timer 把这些问题一次性解决了:日志进 journald(journalctl -u 直接看),Persistent=true 开机补跑错过的任务,环境变量在 service 文件里写死,最小粒度精确到秒。

写法:两个文件分工

一个 .service 定义做什么,一个 .timer 定义何时做。比如每天凌晨 3 点跑备份:

# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 03:00

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

OnCalendar 的语法比 cron 直观:*-*-* 03:00:00 就是每天 3 点。Persistent=true 是精髓——机器关机错过的任务,开机后会自动补跑一次。

启用就三行:

systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers  # 看所有 timer 的下次触发时间

迁移对照:cron 那行怎么写成 timer

crontimer 的 OnCalendar
0 3 * * **-*-* 03:00:00
*/30 * * * **:0/30
0 0 * * 0(周日午夜)Sun *-*-* 00:00:00
@rebootservice 里加 [Install] WantedBy=multi-user.target

最常忘的一步:改完 timer 文件一定要 systemctl daemon-reload,不然 systemd 读的还是旧配置。这个坑我踩过三次。

结论

新任务直接写 timer,别再开 cron 的历史倒车。老 cron 也不用急着全迁——迁一个试试,你会发现回不去了:journalctl -u backup.service 查日志的爽快感,crontab 这辈子给不了。