You Don't Love systemd Timers Enough
You Don’t Love systemd Timers Enough
你对 systemd 定时器的爱还不够深
My favorite metonymic technology term is “cron job”: even though cron may not literally be the daemon that executes actions on a schedule, we apply the term to anything that walks like a cron and quacks like a cron. As Patrick McKenzie likes to point out, cron jobs are one of the most eminently useful computing primitives. They offer utility that’s almost immediately obvious for plenty of use cases that almost everybody has: do this every day; do that once a month. And yet. You probably shouldn’t use literal cron (or its more modern cousins) for scheduled tasks! In 2026 there are more modern options available, and my favorite is the humble systemd timer. I love systemd timers. If you don’t love them yet, maybe I can show you the reasons why you should love them, too.
我最喜欢的借代技术术语是“cron job”(定时任务):尽管 cron 可能并非字面上那个按计划执行操作的守护进程,但我们习惯将任何像 cron 一样运行、像 cron 一样工作的任务都称为 cron job。正如 Patrick McKenzie 所指出的,cron job 是计算领域中最实用的原语之一。它们为几乎每个人都有的许多用例提供了显而易见的便利:每天做这个,每月做那个。然而,你可能不应该再使用传统的 cron(或其更现代的变体)来执行定时任务了!在 2026 年,有更多现代化的选择,而我最喜欢的就是朴实无华的 systemd 定时器。我热爱 systemd 定时器。如果你还没爱上它们,也许我可以向你展示为什么你也应该爱上它们。
My cron? Cooked?
我的 cron?凉了吗?
A systemd timer is a type of unit that schedules other units (usually a service) on a particular schedule. (How a systemd service unit works is another article, but you can logically consider the .service target of a systemd timer to be a script.) Timers are effectively a functional replacement for a traditional cron daemon (though you could conceivably run both), and timer calendar settings offer some similarities to help bridge the gap from traditional cron-like expressions. At this point the systemd haters peer out of the woodwork in anticipation of torpedoing timers because they are part of the systemd project and because they replace mature (if clunky) technology. I’d rather not spend our time arguing about cron, so briefly consider why newer solutions like systemd timers that benefit from years of hindsight are better:
systemd 定时器是一种按特定计划调度其他单元(通常是服务)的单元类型。(systemd 服务单元的工作原理是另一个话题,但你可以从逻辑上将 systemd 定时器的 .service 目标视为一个脚本。)定时器实际上是传统 cron 守护进程的功能替代品(尽管你可以想象同时运行两者),并且定时器的日历设置提供了一些相似之处,以帮助弥合与传统 cron 表达式之间的鸿沟。此时,systemd 的反对者们可能会跳出来试图抨击定时器,仅仅因为它们属于 systemd 项目,并且取代了成熟(尽管笨重)的技术。我不想把时间浪费在争论 cron 上,所以请简要考虑一下为什么像 systemd 定时器这样受益于多年经验教训的新方案更好:
-
Ambiguous $PATH settings make cron script execution difficult to predict.
-
stdout and stderr output often ends up in a black hole (and, often, sent to the host’s mail system, which is usually not what you want to happen.)
-
Execution history is difficult to follow and interrogate.
-
You might feel cool knowing the scheduling grammar by heart, but
01,31 04,05 1-15 1,6 *isn’t easy or intuitive for humans to read. -
模糊的 $PATH 设置使得 cron 脚本的执行难以预测。
-
stdout 和 stderr 的输出通常会进入黑洞(而且经常被发送到主机的邮件系统,这通常不是你想要的)。
-
执行历史难以追踪和查询。
-
你可能因为背下了调度语法而感到酷,但
01,31 04,05 1-15 1,6 *对人类来说既不简单也不直观。
Incidentally, timers solve all these problems (and more.) 顺便提一下,定时器解决了所有这些问题(以及更多问题)。
Prime Time for a Timer Primer
定时器入门的最佳时机
We can cover the basics without a lot of ceremony. First you need a target for a timer to execute. On a Linux host with systemd operational, placing the following unit contents at /etc/systemd/system/roulette.service installs a service with a 1 in 10 chance to be free (i.e., shut down your computer):
我们可以直接进入基础知识,无需过多铺垫。首先,你需要一个供定时器执行的目标。在运行 systemd 的 Linux 主机上,将以下单元内容放置在 /etc/systemd/system/roulette.service 中,即可安装一个有十分之一概率“获得自由”(即关机)的服务:
[Unit]
Description=1 in 10 chance to break your chains
[Service]
ExecStart=/usr/bin/env bash -c '[[ $(($RANDOM % 10)) == 0 ]] && systemctl poweroff || echo LIVE ANOTHER DAY'
Update: [2026-05-05 Tue] Twitter mutual HSVSphere points out that the service option ExecCondition= offers a native way to handle conditional execution. This is a more tightly-integrated way to express “should I continue to execute?” and I agree that it offers a clearer way to express intent at the unit level (I’m using absolute paths here for a NixOS system):
更新:[2026-05-05 周二] Twitter 互相关注的好友 HSVSphere 指出,服务选项 ExecCondition= 提供了一种处理条件执行的原生方式。这是一种更紧密集成的表达“我是否应该继续执行?”的方法,我同意它在单元级别提供了更清晰的意图表达方式(我在这里为 NixOS 系统使用了绝对路径):
[Unit]
Description=1 in 10 chance to break your chains
[Service]
ExecCondition=/run/current-system/sw/bin/bash -c '[[ $(($RANDOM % 10)) == 0 ]]'
ExecStart=/run/current-system/sw/bin/systemctl poweroff
This has the same effect as the prior bash conditional, and you end up with different wording in the journal that (in my opinion) expresses the situation more clearly for you when the condition is met:
May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.
这与之前的 bash 条件判断效果相同,并且你在日志中会得到不同的措辞,(在我看来)当条件满足时,这能更清晰地向你表达情况:
May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.
In general, leaning into the options that systemd presents is a better experience than scripting your own. (Another example would be to use OnFailure= to react when your service scripts fail or Restart= to attempt recovery in the case of ephemeral failures.) Associate that service with a timer by placing a file with the same file stem (roulette) at /etc/systemd/system/roulette.timer:
总的来说,利用 systemd 提供的选项比自己编写脚本体验更好。(另一个例子是使用 OnFailure= 来应对服务脚本失败的情况,或者使用 Restart= 在出现临时故障时尝试恢复。)通过在 /etc/systemd/system/roulette.timer 中放置一个具有相同文件前缀(roulette)的文件,将该服务与定时器关联起来:
[Unit]
Description=impending destruction
[Timer]
OnCalendar=10:00
[Install]
WantedBy=timers.target
What I mean by associate is that, by default, a timer’s Unit= setting will choose a service unit with a matching stem suffixed by .service. In this case, roulette.service. You can always change this if you want to execute a service with a different unit name.
我所说的“关联”是指,默认情况下,定时器的 Unit= 设置会选择一个具有相同前缀并以 .service 结尾的服务单元。在本例中,即 roulette.service。如果你想执行一个不同名称的服务单元,随时可以更改此设置。