Alright, let’s talk about the elephant in the room: cron is old. It’s the venerable, grumpy grandparent of task scheduling. It works, it’s everywhere, but it has some deeply weird habits, like emailing you a letter every time it takes out the trash. For modern Linux systems, there’s a new sheriff in town, and it’s wearing the same uniform as everything else: systemd.

Yes, systemd absorbed this too. Love it or hate it, its timer system is incredibly powerful and integrated. Instead of the scattered, edit-in-isolation approach of cron, systemd timers are managed like any other service—with consistent logging, dependency handling, and a unified control interface. It’s the difference between a standalone appliance and one that’s wired into your smart home.

Why systemd Timers? The cron Comparison

First, why bother? If cron works, why learn a new syntax? Because cron has some sharp edges you’ve probably already bled on.

  • Dependency Chaos: A cron job doesn’t know or care if your network is up, if the filesystem is mounted, or if another vital service is running. It just blindly executes. This is a fantastic way to have a job spam errors for an hour at 3 a.m.
  • Lousy Logging: By default, cron only captures stdout and stderr and, if configured, mails it to you. Debugging often involves squinting at syslog. systemd timers, on the other hand, log directly to the journal (journalctl), which means you get rich, structured logs with metadata alongside everything else your system is doing.
  • Flexible Scheduling: cron can do “every Tuesday” or “every 5 minutes.” systemd timers can do that and “15 minutes after boot” or “24 hours after the last timer finished,” which is huge for preventing pile-ups of long-running tasks.

The Nuts and Bolts: A Service and Its Timer

This is the core concept cron users often miss: a systemd timer doesn’t do the thing. It triggers a systemd service that does the thing. You need two files: a .service file and a .timer file. This separation is brilliant because it means your actual task unit can be started on its own for testing, and your timer unit is just a dedicated trigger mechanism.

Let’s say you want to run a script that cleans out your download directory every night. First, you write the service file.

# /etc/systemd/system/cleanup-downloads.service
[Unit]
Description=Clean up the downloads directory

[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup-downloads.sh
User=yourusername

Note Type=oneshot. This is for jobs that run and exit, which covers 99% of what you’d put in a cron job. It tells systemd not to expect a long-running daemon.

Now, the timer that activates it.

# /etc/systemd/system/cleanup-downloads.timer
[Unit]
Description=Run cleanup-downloads.service daily at 2am

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

[Install]
WantedBy=timers.target

The OnCalendar directive is your new crontab. The syntax is powerful but can make your eyes glaze over. *-*-* 02:00:00 means “any year, any month, any day, at 02:00.” You can use more human-friendly shortcuts like daily or weekly, but specifying it gives you control.

Persistent=true is a fantastic feature. If your machine was off or asleep at 2 a.m., this tells systemd to trigger the service immediately upon the next boot. It persists the missed event. cron just skips it, which is often not what you want.

Getting Your Timers Running

You can’t just create the files. You have to tell systemd to reload its configuration, enable the timer (so it starts on boot), and then start it.

sudo systemctl daemon-reload
sudo systemctl enable cleanup-downloads.timer
sudo systemctl start cleanup-downloads.timer

Check its status with systemctl list-timers to see all active timers and when they’re next scheduled to run. It’s wonderfully clear.

The Killer Feature: Monotonic Timers

This is where systemd timers truly outclass cron. OnCalendar is for real-time clocks (calendar time). systemd also supports monotonic timers, which trigger relative to an event like startup.

[Timer]
OnBootSec=15min
OnUnitActiveSec=24h

OnBootSec=15min runs the service 15 minutes after the system boots. OnUnitActiveSec=24h runs it 24 hours after the service itself last finished. This is perfect for maintenance scripts where you care about the interval between completions, not the time of day. Trying to do this in cron is a nightmare of locking files and flock commands.

The Pitfalls: What to Watch Out For

It’s not all roses. The biggest gotcha is user context. If you’re creating user-level timers (stored in ~/.config/systemd/user/), you must enable linger for the user to allow them to run at boot without being logged in: loginctl enable-linger yourusername. Then you use systemctl --user to manage them. Forget this, and your user timers will simply not run after a reboot.

Also, the OnCalendar syntax, while powerful, is complex. You’ll find yourself checking the systemd.time(7) man page more than once. Want “every other Tuesday at 9 AM”? Good luck. It’s Tue *-*-1..7/2 09:00:00 (Tue, any month, any year, on the 1st-7th and then every 2 days within that week). I told you it was complex. Sometimes the old cron syntax is just more readable.

Ultimately, systemd timers offer a level of integration and robustness that cron can’t match. For system-level tasks, they are almost always the superior choice. For user-level quick-and-dirty one-liners? cron might still be your friend. But for anything you want to actually rely on, it’s time to learn the timer syntax. Your future self, debugging at 2 a.m., will thank you for the excellent journal logs.