在现代发行版上,自己拥有的任务用 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)。任务是一等公民,和任何其他服务一样有 start、status、logs 和失败跟踪。排期既可以是日历式(类 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/15 配 RandomizedDelaySec=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 不触发?
四个原因几乎覆盖所有情况,按排查顺序:
- service 和 timer 名字对不上。
foo.timer触发foo.service——OnFailure打了错字或者 unit 改过名,timer 就”运行”进了虚空。systemctl cat foo.timer会显示它到底指向什么。 - timer 改过但没重载。 改完 unit 文件之后:
sudo systemctl daemon-reload && sudo systemctl restart foo.timer。不做这个,新排期不会生效。 Persistent=true的OnCalendar语义和你想的不一样——或者你看的是systemctl status foo.timer(空闲时也显示 active)而不是list-timers。- 它触发的 service 在秒挂,于是 timer 看起来死了。
journalctl -u foo.service --since -1h会展示list-timers藏起来的那次崩溃。
等你真去看日志时,journalctl 速查表里能让这件事变快的过滤器(-u、--since、-f)都在。
Cron 与 systemd timer:决策表
| cron | systemd timer | |
|---|---|---|
| 上手成本 | 一行 crontab | 两个 unit 文件 |
| 日志 | 本地邮件,基本没人看 | journal,按 unit 分 |
| 错过的运行(关机) | 直接跳过 | Persistent=true 补跑一次 |
| 依赖/顺序 | 无——脚本里自己想办法 | 完整的 unit 依赖 |
| 随机延迟 | 自己用 $RANDOM 拼 | RandomizedDelaySec= |
| 排期试跑/预览 | 没有 | systemd-analyze calendar |
| 到处都存在 | 是——容器、BSD、嵌入式 | 需要 systemd(PID 1) |
| 无 root 的用户任务 | crontab -e | systemd --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.
what is this?llama.cpp 还是 Ollama:2026 年该跑哪一个?
喜欢这些文章?我的本职工作就是这样的工程。 雇用我