惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CERT Recently Published Vulnerability Notes
U
Unit 42
T
The Blog of Author Tim Ferriss
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Securelist
L
Lohrmann on Cybersecurity
Blog — PlanetScale
Blog — PlanetScale
Recorded Future
Recorded Future
D
DataBreaches.Net
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
I
Intezer
P
Palo Alto Networks Blog
Simon Willison's Weblog
Simon Willison's Weblog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
I
InfoQ
宝玉的分享
宝玉的分享
Security Latest
Security Latest
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threatpost
Cisco Talos Blog
Cisco Talos Blog
P
Proofpoint News Feed
博客园 - 司徒正美
H
Hacker News: Front Page
Y
Y Combinator Blog
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
NISL@THU
NISL@THU
月光博客
月光博客
有赞技术团队
有赞技术团队
Cloudbric
Cloudbric
酷 壳 – CoolShell
酷 壳 – CoolShell
G
Google Developers Blog
A
Arctic Wolf
博客园 - 【当耐特】
W
WeLiveSecurity
V
Visual Studio Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
V
V2EX
C
Cyber Attacks, Cyber Crime and Cyber Security
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
The Cloudflare Blog
Stack Overflow Blog
Stack Overflow Blog

Hacker News

Introducing Claude Opus 4.7 Qwen Studio The Future of Everything is Lies, I Guess: Where Do We Go From Here? GitHub - SeanFDZ/macmind: Single-layer transformer in HyperTalk for the classic Macintosh Show HN: Agent-cache – Multi-tier LLM/tool/session caching for Valkey and Redis Moving a large-scale metrics pipeline from StatsD to OpenTelemetry / Prometheus GitHub - Nightmare-Eclipse/RedSun: The Red Sun vulnerability repository GitHub - SethPyle376/hiraeth: Local AWS emulator focused on fast integration testing, with SQS support, SQLite-backed state, and a debug-friendly web UI. GitHub - macOS26/Agent: Any AI, replaces Claude Code, Cursor, OpenClaw. Over 18 LLM providers (Claude, OpenAI, Gemini, Ollama, Zai, HF, Qwen) wired into a native Mac app that writes code, builds Xcode projects, bumps versions, manages git, automates Safari, use AppleScript, JS or Accessibility, extend Agent! w/ MCP Servers, run tasks from your iPhone via Messages. YouTube now lets you turn off Shorts I Made a Terminal Pager Burgers | マクドナルド公式 Commands — HackerNews CLI documentation ChatGPT for Excel PiCore - Raspberry Pi Port of Tiny Core Linux Live Nation illegally monopolized ticketing market, jury finds Google Broke Its Promise to Me. Now ICE Has My Data. Founding Engineer at Adaptional | Y Combinator CRISPR takes important step toward silencing Down syndrome’s extra chromosome GitHub - saffron-health/libretto: The AI toolkit for building reliable browser automations US v. Heppner (S.D.N.Y. 2026) no attorney-client privilege for AI chats [pdf] Unexpected €54k billing spike in 13 hours: Firebase browser key without API restrictions used for Gemini requests Retrofitting JIT Compilers into C Interpreters IPv6 – Google The Accursèd Alphabetical Clock Cybersecurity Looks Like Proof of Work Now Fragments: April 14 Cal.com Goes Closed Source: Why AI Security Is Forcing Our Decision | Cal.com - Scheduling Software for Online Bookings Laravel raised money and now injects ads directly into your agent When moving fast, talking is the first thing to break Too much Discussion of the XOR swap trick – Heather Cafe Introduction to Spherical Harmonics for Graphics Programmers The Grand Line Building a Z-Machine in the worst possible language High-Level Rust: Getting 80% of the Benefits with 20% of the Pain GitHub - duguyue100/midnight-captain: Inspired by Midnight Commander, tailored to my taste. How to build a `git diff` driver · Jamie Tanna | Software Engineer Center for Responsible, Decentralized Intelligence at Berkeley The Local Universe’s Expansion Rate Is Clearer Than Ever, but Still Doesn’t Add Up - A new synthesis of astronomical measurements confirms a persistent mismatch that could point to physics beyond current models The air throughout our homes is infused with microplastics. But there are things you can do to breathe less of them The disturbing white paper Red Hat is trying to erase from the internet – OSnews The Future of Everything is Lies, I Guess: Annoyances ‘Abhorrent’: the inside story of the Polymarket gamblers betting millions on war Productive procrastination — Max van IJsselmuiden maps, territory and LMs 447 Terabytes per Square Centimetre at Zero Retention Energy: Non-Volatile Memory at the Atomic Scale on Fluorographane Show HN: Pardonned.com – A searchable database of US Pardons 20 Years on AWS and Never Not My Job The Seasons are Wrong Artemis II crew splashes down near San Diego after historic moon mission We gave an AI a 3 year retail lease in SF and asked it to make a profit | Andon Labs How a dancer with ALS used brainwaves to perform live On filing the corners off my MacBooks Installing every* Firefox extension OpenClaw’s memory is unreliable, and you don’t know when it will break Steve Blank Nowhere Is Safe Chimpanzees in Uganda locked in vicious 'civil war', say researchers watgo - a WebAssembly Toolkit for Go linux/Documentation/process/coding-assistants.rst at master · torvalds/linux GitHub - callumlocke/json-formatter: Makes JSON easy to read. Founding Product Engineer at Bild AI | Y Combinator A compelling title that is cryptic enough to get you to take action on it GitHub - Keychron/Keychron-Keyboards-Hardware-Design: Industrial design files for Keychron keyboards and mice. 100+ models with CAD assets in STEP, DXF, DWG, and PDF. Source-available, with commercial use allowed for original compatible accessories within the license terms. [ANNOUNCE] WireGuardNT v0.11 and WireGuard for Windows v0.6 Released 1D-Chess Helium Is Hard to Replace Cooperative Vectors Introduction | Evolve Keeping a Postgres queue healthy — PlanetScale Our response to the Axios developer tool compromise Do Americans read print books, e-books or audiobooks more? The Zettelkasten Method in Obsidian: A Practical Setup Guide Artemis II Is Competency Porn and We Are Starving For It WeakC4 Flight Viz — Cockpit View A Mexican surveillance giant you’ve never heard of is now watching the U.S. border Surelock: Deadlock-Free Mutexes for Rust RISC-V 101 – what is it and what does it mean for Canonical? | Ubuntu The Problem That Built an Industry How Much Linear Memory Access Is Enough? | Solidean Investigating Split Locks on x86-64 Simplest hash functions Sybilproof reputation mechanisms (2005) [pdf] What is a property? How Complex is my Code? Static code analysis in Kotlin — tools overview Toffoli gates are all you need PGLite evangelism dcmake: a new CMake debugger UI Clojure on Fennel part one: Persistent Data Structures Fragments: April 2 Python Release Python install manager 26.1 The Life and Death of the Book Review - Liberties Introducing Database Traffic Control — PlanetScale Bitcoin miners are losing $19,000 on every BTC produced as difficulty drops 7.8% God sleeps in the minerals Building slogbox Apple Silicon and Virtual Machines: Beating the 2 VM Limit Who was “Not Even Wrong” first? Pokemon Evolution Vs Darwinian Evolution The APL Programming Language Source Code
You Don't Love systemd Timers Enough
Tyler Langlois · 2026-06-02 · via Hacker News

« You Don't Love systemd Timers Enough

  • 5 May, 2026
  • 2,139 words
  • 9 minute read time

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.

My cron? Cooked?

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:

  • 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.

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

  • Font used to highlight strings.
  • Font used to highlight keywords.
  • Font used to highlight type and class names.
[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):

Systemd

  • Font used to highlight strings.
  • Font used to highlight keywords.
  • Font used to highlight type and class names.
[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.

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

  • Font used to highlight keywords.
  • Font used to highlight type and class names.
[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.

I want to call out a few things right away:

  • Per normal service unit semantics, the ExecStart= target does not run as a shell command by default. You should treat the absolute path target like a script or, in our case, an interpreter that expects a script as a string argument. For example, ExecStart=/usr/bin/echo Hello | /usr/bin/awk straight-up won't work; the pipe makes no sense in context here.
  • The ExecStart= argument does not inherit any environment variables by default (outside of some system manager defaults), so we begin with a pretty bare $PATH by default. Executing /usr/bin/env is a shortcut to ensure things like systemctl are available, but out of the box, you get a clean state to begin with. If we had used a bare ExecStart=/usr/bin/bash, we'd have the basics in $PATH, but using env here is an extra safeguard.

You can roll the dice without the aid of the timer at all:

shell

Although note that you cannot enable this service without any usable [Install] section: our timer is the canonical way to make the service run in a consistent way. Also useful to highlight that systemctl operates on roulette.service by default without any explicit suffix.

When applied to a .timer unit, the systemctl start subcommand puts it on the clock, per se, but does not actually execute the Unit= target.

shell

systemctl start roulette.timer

The timer is now active, but not the service.

Depending on the moment in time, status will tell you when to next expect the timer to decide your fate:

shell

systemctl status roulette.timer

You'll see plenty of information about the timer on its status page, including the next time it'll fire:

Trigger: Sat 2026-04-18 10:00:00 MDT; 35min left

That's the simplest timer onboarding: create a target, place the target service file alongside a timer with a schedule, and start the timer (not the target) to get the schedule started. Because the .timer defines an WantedBy= within [Install], we can ensure the timer comes up at boot time too, not just when we start it:

shell

systemctl enable roulette.timer

Let's move on past the basics.

Time Lord

Arguably the most important bit of information about timers is how to express a schedule, whether a repeating period of time (which the manual usually refers to as a time span) versus a calendar event (or a timestamp). Fortunately, I think the man page for this under systemd.time(7) is actually very good with plenty of examples. You should use it as the first resource when writing timers; it's good (or better) than, uh, casual blog posts by casual writers.

systemd also ships with a command-line tool called systemd-analyze which includes the ability to validate and explain time expressions from the command line directly in an imperative way to help understand them. You can even disambiguate the classic wildcard cron expression which systemd-analyzer can parse and then explain to you, complete with the expected execution times:

shell

systemd-analyze calendar '*-*-* *:*:*'
Normalized form: *-*-* *:*:*
    Next elapse: Sat 2026-04-18 16:44:26 MDT
       (in UTC): Sat 2026-04-18 22:44:26 UTC
       From now: 431ms left

This blog post is not the place to reproduce the entirety of systemd.time(7) verbatim, so I encourage you to Read The Helpful Manual (RTHM). Writ small, you can pretty simply define either a recurring wallclock period or, in contrast to plain old cron, a recurring period of time against some previous event.

The first category of time expressions is easy to envision. For example, in fully-qualified form, daily means:

*-*-* 00:00:00
│ │ │ │  │  ╰── at second 00
│ │ │ │  ╰───── at minute 00
│ │ │ ╰──────── at hour 00
│ │ ╰────────── every day
│ ╰──────────── every month
╰────────────── every year

You can use shorthand terms like daily, write out the complete form, or use any other supported value listed out in systemd.time(7) and subsequently validate your assumptions against systemd-analyze.

The second category of time expressions apply to "run this relative to some other event." This distinction from "run at the same time very day" is very often what you actually want. Consider a job that clears out a temporary directory, for example: if a cron expression lapsed right after boot, there probably isn't much to clean out of /tmp at all. But if you encode "execute an hour after my computer has started and then every hour after that", the schedule logic is meaningful for what the related service is actually doing.

This is easy to do in a timer:

Systemd

  • Font used to highlight keywords.
  • Font used to highlight type and class names.
[Timer]
OnBootSec=1h
OnUnitActiveSec=1h

That is: "run an hour after the machine starts" (which will execute once) and also "run one hour after my Unit= runs" (which implicitly makes the timer repeat indefinitely.)

Periodic time spans like this fit the "every once in a while" use case surprisingly more often than "run at this minute every hour" and similar expressions. Another good example is a timer I use every December to poll the Advent of Code API for a Slack bot I wrote for some friends. The */15 cron expression honors the "every 15 minutes" policy that their API requests, but since that's the easiest way to express it in cron language, I'm sure it makes spiky traffic alongside everyone else polling the API! Starting my timer when I've made a code fix that runs whenever 15 minutes has lapsed is all I care about, and probably creates less of a thundering herd problem.

Calendar versus time span units is probably the biggest conceptual leap from a traditional cron job, but timers offer more, too.

Bird's-Eye Countdown

My favorite high-level command to get a picture of a machine's timer situation is the list-timers subcommand. Here's my host's summary:

shell

NEXT                                 LEFT LAST                                  PASSED UNIT                         ACTIVATES
Mon 2026-04-20 15:15:00 MDT      1min 40s Mon 2026-04-20 15:00:05 MDT        13min ago zfs-snapshot-frequent.timer  zfs-snapshot-frequent.service
Mon 2026-04-20 15:32:16 MDT         18min Mon 2026-04-20 14:22:15 MDT        51min ago fwupd-refresh.timer          fwupd-refresh.service
Mon 2026-04-20 16:00:00 MDT         46min Mon 2026-04-20 15:00:05 MDT        13min ago logrotate.timer              logrotate.service
Mon 2026-04-20 16:00:00 MDT         46min Mon 2026-04-20 15:00:05 MDT        13min ago zfs-snapshot-hourly.timer    zfs-snapshot-hourly.service
Tue 2026-04-21 00:00:00 MDT            8h Mon 2026-04-20 09:43:22 MDT     5h 29min ago zfs-snapshot-daily.timer     zfs-snapshot-daily.service
Tue 2026-04-21 07:31:28 MDT           16h Sun 2026-04-19 20:15:47 MDT           7h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2026-04-27 00:00:00 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago zfs-snapshot-weekly.timer    zfs-snapshot-weekly.service
Mon 2026-04-27 01:09:27 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago fstrim.timer                 fstrim.service
Mon 2026-04-27 04:28:38 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago zpool-trim.timer             zpool-trim.service
Fri 2026-05-01 00:00:00 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-snapshot-monthly.timer   zfs-snapshot-monthly.service
Fri 2026-05-01 03:17:17 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-scrub.timer              zfs-scrub.service

11 timers listed.
Pass --all to see loaded but inactive timers, too.

From one command you glean a total picture of anything executing on a timer schedule. Very useful.

list-timers is part of a family of systemd subcommands that I use fairly often. Others that are useful include list-units and list-paths (the latter is a more recent addition to systemctl.)

Suspended Reanimation

Waking a suspended system to run an important script even if you're not around to perform the physical action of, say, lifting a laptop lid sounds like a daunting feat until you find WakeSystem=:

WakeSystem=
    Takes a boolean argument. If true, an elapsing timer will
    cause the system to resume from suspend, should it be
    suspended and if the system supports this.
...

You can imagine the utility for something like this. On a distribution that supports downloading package updates before using them (like Arch or NixOS, for example), you can pre-fetch update packages late a night for morning updates when you're at a keyboard, and there are plenty of other ideas you could apply this too as well. The man page highlights that you'll need to manually re-suspend if you intend for that to happen after your .service is done.

Splay-away

I touched on the thundering herd problem a few paragraphs ago, which is the systems problem of, "what happens when a set of processes all wake up at the same time?" If every Debian system in the world were hard-coded to apt update at 00:00:00, midnight would be a bad, spiky time for everyone.

Two timer options called FixedRandomDelay= and RandomizedOffsetSec= help:

FixedRandomDelay=
    Takes a boolean argument. When enabled, the randomized delay
    specified by RandomizedDelaySec= is chosen deterministically,
    and remains stable between all firings of the same timer,
    even if the manager is restarted. ...

RandomizedOffsetSec=
    Offsets the timer by a stable, randomly-selected, and evenly
    distributed amount of time between 0 and the specified time
    value. ...

I've used this for real systems that check in to update software. Not only does it help with thundering herd problems, but spreading out execution along a uniform distribution ensures that the behavior is consistent and avoids disruptive activities like restarting daemons that may be coordinating distributed services.

In general, timing options are very configurable and expose a great deal of granularity (again, all of which are explained in the man page.)

Insistent Persistence

This option is particularly well-suited for scripts on a schedule that shouldn't ever be skipped due to a suspended laptop but may not warrant WakeSystem=:

Persistent=
    Takes a boolean argument. If true, the time when the service
    unit was last triggered is stored on disk. When the timer is
    activated, the service unit is triggered immediately if it
    would have been triggered at least once during the time when
    the timer was inactive. ...

If you're scheduling a system to checkin to configuration management but the host has undergone downtime, slapping Persistent= on the .timer can mean the difference between converging onto correct state immediately after coming online versus whenever the timer may normally fire (which could be a long time.) There are other good examples of service activations you don't want to wait for if the timer detects a missed activation: system updates, checking for batch jobs, that kind of thing.

A Quick Recap

If you start writing timers in earnest, bear the following in mind:

  • Timers that run in the context of a user manager (the kind you interact with via systemctl --user) are totally valid, but keep an eye on the target you use for [Install]. Sometimes the appropriate target to use for this is default.target depending on the distribution.
  • The usual caveats about keeping a correct system clock apply like they always did with cron. Fellow systemd cult members can rely on timedatectl timesync-status to peek at their synchronization status.
  • Many editors natively support the systemd unit file format which helps when unit files become large. I use the emacs systemd package.