Systemd Timers - a better alternative for crontab?

Cron is the ubiquitous job scheduler in the Linux world. Anyone who wants to run a periodic automation or a one-off script at a predetermined time interval has used cron. Plenty of housekeeping tasks run as scheduled jobs by default on all Linux systems. The files under /etc/cron.* will give you an idea on these cron jobs.
For a very long time crontab has been the undisputed choice for scheduling jobs on Linux systems. But with the wide adoption of systemd as the system and service manager, it has given us a new option - systemd timers.
Most casual Linux users consider systemd as a modern replacement for the SysV init system. While it is partially true, systemd is much more than that. To quote the systemd website:
systemd provides aggressive parallelization capabilities, uses socket and D-Bus activation for starting services, offers on-demand starting of daemons, keeps track of processes using Linux control groups, maintains mount and automount points, and implements an elaborate transactional dependency-based service control logic.
While a discussion on the systemd capabilities is outside the scope of this article, the following points will serve as a refresher to better understand this article:
Systemd owns PID1 and is started by the Linux kernel
All other processes in the system are descendants of systemd
It is responsible for filesystem initialization
The fundamental building block of the systemd ecosystem is the concept of units
A unit is a system resource that systemd knows how to manage
There are different types of units, including services, devices, mountpoints, sockets, etc
The definition and configuration of a unit is stored in unit files under different directories managed by systemd
There are system level units and user level units
With that background set, let's get back to running crons with systemd timers.
Systemd Timers
Systemd provides an alternative to crons in the form of systemd timers. Timers are a type of unit files that define how a job or a service can be run on calendar events or monotonic events. Calendar events are similar to cron time and date fields, set based on a calendar time, and can be recurring as well. Monotonic events are configured as a time delta from the occurrence of an event, say boot time.
A systemd timer unit file will have the extension .timer
For every systemd timer unit file, there will be another unit file with the same name and a .service extension
The timer unit defines the “when to run”, whereas the service unit defines the “what to run”
Let's use an example to demonstrate how timers work.
Consider the scenario of taking a MySQL backup every three hours. A crontab entry would look like this:
0 */3 * * * /usr/local/bin/mysql-backup.sh
And the script would be
cat /usr/local/bin/mysql-backup.sh
#!/bin/bash
/usr/bin/mysqldump inventory > /opt/db-backup/inventory-db-backup_`date +%H-%d-%m-%Y`.sql
The authentication will be taken care of with /root/.my.cnf
[client]
host=localhost
user=MYSQL_BACKUP_USER
password=MYSQL_BACKUP_PASSWORD
Now let us see how to move this cron to a systemd timer.
First we need to translate our backup script into a systemd service.
cat /etc/systemd/system/mysql-backup.service
[Unit]
Description="MySQL Backup Service"
[Service]
ExecStart=/usr/local/bin/mysql-backup.sh
Now we need to create the timer unit
cat /etc/systemd/system/mysql-backup.timer
[Unit]
Description="Run mysql-backup.service every 3 hours"
[Timer]
OnCalendar=*-*-* 00/3:00:00
Unit=mysql-backup.service
[Install]
WantedBy=multi-user.target
Most parts of these unit files are self-explanatory and are not different from normal unit files. The timer section describes what service to run ( mysql-backup.service ) and one or more time options. In this particular case, we are giving a specific calendar timing that executes the service once every three hours. We will look at a few more timer options later.
Now, verify the files are syntactically correct.
sudo systemd-analyze verify /etc/systemd/system/mysql-backup.*
If this command doesn't return any output, then we are good to proceed.
Reload systemd to update the system about new unit files.
sudo systemctl daemon-reload
Enable and start the timer unit.
sudo systemctl enable --now mysql-backup.timer
The timer is active now.
sudo systemctl status mysql-backup.timer
● mysql-backup.timer - "Run mysql-backup.service every 3 hours
Loaded: loaded (/etc/systemd/system/mysql-backup.timer; disabled; vendor preset: enabled)
Active: active (waiting) since Sat 2023-03-08 21:07:23 IST; 3s ago
Trigger: Sun 2023-03-09 00:00:00 IST; 2h 52min left
Triggers: ● mysql-backup.service
As you can see, the timer is enabled and the next run time is also listed.
Now let us look at the few ways the timers can be configured. As mentioned in the beginning, there are two categories of timers - real time ( calendar based ) and monotonic ( based on events ).
Monotonic timers
Monotonic timers are triggered after a specific time elapsed from an event, like boot time. There are different options to configure the monotonic timers, some of them are given below.
OnBootSec: time after the machine boots up
OnActiveSec: time after the timer unit is activated
OnUnitActiveSec: time after the service unit was last activated
OnUnitInactiveSec: time after the service unit was last deactivated
OnStartupSec: time after the service manager is started
There are various formats in which you can provide values to the monotonic timer options. Some of them are
5hours
34minutes
5hours 34minutes
1y 3month
Real-time timer
Triggered by calendar events, the real-time timers have only one option: “OnCalendar”. This is the option that closely resembles crontab timers, so let's have a quick comparison.
Crontab Format: minute hours day-of-the-month month day-of-the-week
This is a five-part format that can use *, absolute values, ranges, and lists for each part.
Systemd Timer Format: Day Of the week Year-Month-Date Hour:Minute:Second
This three-part format works as follows:
First part is the day of the week with 3 character values from Mon to Sun
Second part is Year-Month-Date in number format with 4 digit years, two digit month and day
Third part is time - 2 digits per hour, minute, and second
A continuous range of values can be indicated with two dots. Eg: Mon..Wed mean Mon, Tue, Wed
A list of values can be indicated with a comma. Eg: Sat,Sun
Asterisk can be used as a wild card to match all valid values. Eg: “*” in the first part means all weekdays - Mon..Sun
“/” can be used as a repetition option
Default values can be skipped and the syntax can be shortened in specific ways
Shorthands like minutely, hourly, yearly, etc can be used as a timer
The systemd time manpage covers the time formats in detail. Refer - https://man.archlinux.org/man/systemd.time.7
A few values for OnCalendar option are:
Sat,Sun --* 23:00 - Every Saturday and Sunday at 11
2023-02-14 00:00:01 - 1 second into 14 Feb 2023
--* 00/3:00:00 - Every three hours
23:40/5 - Every day every 5 minutes starting 11:40 PM
The variety of values in the OnCalendar option can be confusing. To ensure we provide the right values, systemd provides a command-line option. Let us validate the last value from this list as follows:
sudo systemd-analyze calendar --iterations=2 "23:40/5"
Original form: 23:40/5
Normalized form: *-*-* 23:40/5:00
Next elapse: Sat 2023-03-08 23:40:00 IST
(in UTC): Sat 2023-03-08 18:10:00 UTC
From now: 17min left
Iter. #2: Sat 2023-03-08 23:45:00 IST
(in UTC): Sat 2023-03-08 18:15:00 UTC
From now: 22min left
As you can see, the command interprets the shorthand and expands into the normalized form, it also provides when the next few occurrences of the schedule will be ( controlled with –iterations ). It also tells you how much time from your current time will the event occur.
Conclusion
As you can see, the systemd timers are very versatile. Crontab provides the simple and efficient form of scheduling, and is available ubiquitously on all Linux operating systems, unlike systemd, which is not guaranteed to be available on all variants of Linux. Having said that, systemd is available on most popular operating systems and these are some of the advantages of timers.
With service units, the jobs can be independently tested and run anytime without timers
A job can be configured to depend on another systemd unit. For example, run the MySQL backup unit only if the mysqld service is running.
Can be resource controlled with cgroups and slices
Easy debugging with journalctl
Time formats allow more control. Years and seconds are supported.
Do experiment with systemd timers. Most of the systemd options that can be used to customize the unit files can be used to better configure your timers and services. If you are running scheduled jobs for production systems, using the systemd timers makes more sense, and is easier to automate with configuration management tools.

