返回博客

Cron 还是 systemd timer:该用哪个?

2026年9月11日

在现代发行版上,自己拥有的任务用 systemd timer,cron 留给一行式的用户任务和你没亲手搭的服务器。 timer 把每次运行都记进 journal,能补跑机器关机期间错过的排期,而且依赖的是和系统其他部分统一的 unit 文件。cron 赢在简短——一行 crontab -e 干掉两个 unit 文件——而且它仍是最小容器和奇怪 Unix 主机上唯一保证存在的调度器。代价是:cron 的失败是静默的。任务凌晨 3 点报错,cron 只会给一个没人看的本地邮箱发邮件;timer 给你的是 journalctl -u mytimer.service,带完整输出。下面:真实的差异、各自怎么测试、timer 为什么可能不触发,以及一张决策表。

cron 和 systemd timer 有什么区别?

cron 是一个守护进程,读一张行表——五个时间字段加一条命令——墙钟对上就执行。模型就这么大。它没有”任务即对象”的概念:没有 unit、没有状态、没有依赖、也没有自己的日志。

systemd timer 是一个 unit 文件(foo.timer),在排期命中时触发另一个 unit 文件(foo.service)。任务是一等公民,和任何其他服务一样有 startstatuslogs 和失败跟踪。排期既可以是日历式(类 cron),也可以是单调式(OnBootSec=15min,墙钟变化骗不了它)。

实际后果:

  • 日志:timer 把 stdout/stderr 按 unit 记进 journal;cron 顶多给本地用户发邮件。
  • 错过的运行:带 Persistent=true 的 timer 会在开机后补跑一次错过的排期;cron 直接跳过。
  • 依赖:timer 可以等 network-online.target 或挂载点;cron 得在脚本里手搓重试逻辑。
  • 语法:cron 一行;timer 两个文件。这就是切换的全部成本。

哪种排期语法更省事——crontab 还是 OnCalendar?

cron 的五个字段是精简的在位者:*/15 * * * * 是每 15 分钟,多数管理员闭着眼都能读。systemd 的 OnCalendar= 更啰嗦但表达力严格更强,而且 systemd-analyze calendar 会在你提交之前告诉你接下来的触发时间——cron 没有等价的 dry-run。

# Cron:每天 03:30
30 3 * * * /usr/local/bin/backup.sh

# systemd:同样的排期,保存前就能验证
systemd-analyze calendar "*-*-* 03:30:00"
# -> 下次触发:Fri 2026-09-11 03:30:00 ...

OnCalendar 能表达 cron 根本写不利索的东西:Mon..Fri *-*-* 09..17:00:00(工作日、上班时间),或者 *:0/15RandomizedDelaySec=10m,免得一千台机器在同一瞬间捶一个服务器。

不干等排期,怎么手动测 systemd timer?

三条命令回答一切。列出所有排期和下次触发时间,按 timer 会做的那样手动跑一次任务,然后读日志:

systemctl list-timers --all                 # 所有 timer,下次 + 上次运行
sudo systemctl start backup.service         # 现在就跑一次,和 timer 触发的是同一个 unit
journalctl -u backup.service -f             # 实时盯着它的输出

注意这个分工:systemctl start backup.timer 是给排期上膛;service 才是任务本身。如果 list-timers 能看到你的 timer,systemctl status backup.service 是绿的,journal 里有你的输出,整条链路就是通的。

怎么手动跑一个 cron 任务?

cron 任务跑在一个精简过的环境里,这正是”我 shell 里好好的,进 cron 就挂”这一类 bug 的由来。要忠实复现 cron,用 cron 会用的那套环境经 sh 跑一遍:

crontab -l                                  # 确认那一行到底写的什么
env -i SHELL=/bin/sh PATH=/usr/bin:/bin sh -c '/usr/local/bin/backup.sh'
grep CRON /var/log/syslog | tail            # cron 到底触发过没有?(Debian/Ubuntu)
journalctl -u cron -n 20                    # 同样的事,systemd 发行版用这条

那行 env -i 是诚实的手动测试:裸环境加 cron 的默认 PATH。大多数 cron 静默失败是一个裸 PATH 或者命令里没转义的 %(cron 把 % 当换行),这两样在这里都会立刻现形。

为什么我的 systemd timer 不触发?

四个原因几乎覆盖所有情况,按排查顺序:

  1. service 和 timer 名字对不上。 foo.timer 触发 foo.service——OnFailure 打了错字或者 unit 改过名,timer 就”运行”进了虚空。systemctl cat foo.timer 会显示它到底指向什么。
  2. timer 改过但没重载。 改完 unit 文件之后:sudo systemctl daemon-reload && sudo systemctl restart foo.timer。不做这个,新排期不会生效。
  3. Persistent=trueOnCalendar 语义和你想的不一样——或者你看的是 systemctl status foo.timer(空闲时也显示 active)而不是 list-timers
  4. 它触发的 service 在秒挂,于是 timer 看起来死了。journalctl -u foo.service --since -1h 会展示 list-timers 藏起来的那次崩溃。

等你真去看日志时,journalctl 速查表里能让这件事变快的过滤器(-u--since-f)都在。

Cron 与 systemd timer:决策表

cronsystemd timer
上手成本一行 crontab两个 unit 文件
日志本地邮件,基本没人看journal,按 unit 分
错过的运行(关机)直接跳过Persistent=true 补跑一次
依赖/顺序无——脚本里自己想办法完整的 unit 依赖
随机延迟自己用 $RANDOMRandomizedDelaySec=
排期试跑/预览没有systemd-analyze calendar
到处都存在是——容器、BSD、嵌入式需要 systemd(PID 1)
无 root 的用户任务crontab -esystemd --user unit

经验法则:在你自己的 systemd 机器上部署任务 → timer。临时个人提醒,或者一台不是你装的机器 → cron。任何带依赖、带重试、或者需要知道它到底跑没跑成的 → 毫无疑问 timer。一个常见模式是用 timer 跑维护任务——比如每晚一次的 rsync 备份——Persistent=true 保证就算机器在排期那一刻睡着,备份照样发生,这是 cron 给不了的。

— mrsaynothing

Get the next one by email

One email per post. No spam, no algorithms.

self-hosted · no third parties · one-click unsubscribe

what is this?

llama.cpp 还是 Ollama:2026 年该跑哪一个?

喜欢这些文章?我的本职工作就是这样的工程。 雇用我