Lloyd Rochester's Geek Blog

We can use systemd instead of cron timers in Unix. Using systemd timers is easy and provides numerous benefits over using Cron which are documented below. These timers more are complicated than cron, however, they are much more powerful.

Creating a systemd timer

Let us look at the logical hierarchy of a timer on systemd and the files associated before we look at an example.

Logical Requirements

To create a systemd timer the following are required:

  1. A service of type oneshot that executes the program
  2. A timer that will run the oneshot service on an interval
  3. A program that needs to run at specified times

Files Required

We will create three files for these requirements:

  1. Install example.service in the directory /lib/systemd/system/
  2. Install example.timer in the directory /lib/systemd/system/
  3. Install example script in the directory /usr/local/bin/

Quickstart

Here are the commands to install, run, and verify the timer is working:

$ sudo bash create-example-timer # the script below installs required systemd
$ sudo systemctl start example.timer # let systemd run the timer
$ sudo systemctl status example.timer # see when the timer will activate
$ sudo systemctl status example # see the status of the service and exit code of the script
$ ls -l /tmp/example # we see file timestamp update every 3 minutes

Example

Let’s create an example that will touch a file every 3 minutes. The touch command will merely change the file’s timestamp and can be see by long listing the file - ls -l.

Below is a script called create-example-timer that we can run with sudo bash create-example-timer to get our example running.

#!/bin/bash

SYSTEMDPATH=/lib/systemd/system
PROGRAM=/usr/local/bin/example
EVENTS="*-*-* *:00/3:00" # every 3 minutes

# create the service file that runs our program
cat << EOF > $SYSTEMDPATH/example.service
[Unit]
Description=An example oneshot service that runs a program

[Service]
Type=oneshot
ExecStart=$PROGRAM

[Install]
WantedBy=multi-user.target
EOF

# create the timer that kicks off our service every 3 minutes
cat << EOF > $SYSTEMDPATH/example.timer
[Unit]
Description=A timer that runs our example service

[Timer]
OnCalendar=*-*-* *:00/3:00

[Install]
WantedBy=timers.target
EOF

# create a program that touches a file
cat << EOF > $PROGRAM
#!/bin/sh

touch /tmp/example
EOF

chmod 755 $PROGRAM

Running the Example

Once we’ve dropped the files down on the filesystem we can view the service and timer with systemd.

Starting the Timer

We can see the timer exists, however, but it isn’t active yet.

$ systemctl status example.timer
● example.timer - A timer that runs our example service
   Loaded: loaded (/lib/systemd/system/example.timer; disabled; vendor preset: enabled)
   Active: inactive (dead)
  Trigger: n/a

Let’s start the timer and see the state change from inactive to active.

$ sudo systemctl start example.timer
$ sudo systemctl status example.timer
● example.timer - A timer that runs our example service
   Loaded: loaded (/lib/systemd/system/example.timer; disabled; vendor preset: enabled)
   Active: active (waiting) since Sat 2022-07-09 12:09:58 MDT; 4s ago
  Trigger: Sat 2022-07-09 12:12:00 MDT; 1min 57s left

Jul 09 12:09:58 penguin systemd[1]: Started A timer that runs our example service.

We can also list all active timers. This command allows us to see when the next activation time is as well as the last time it was activated successfully. It is useful if there are many timers to view in the system.

$ systemctl list-timers
NEXT                         LEFT        LAST                         PASSED       UNIT                         ACTIVATES
Sat 2022-07-09 11:36:00 MDT  18s left    Sat 2022-07-09 11:33:22 MDT  2min 18s ago example.timer                example.service
...

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

Listing the Service

We can see the service and even start it. Since this service is oneshot it will show inactive as it runs and exits. We are observing the last run of the service that was activated by the timer and it’s exit code.

$ systemctl status example
● example.service - An example oneshot service that runs a program
   Loaded: loaded (/lib/systemd/system/example.service; disabled; vendor preset: enabled)
   Active: inactive (dead) since Sat 2022-07-09 11:36:22 MDT; 20s ago
  Process: 3367 ExecStart=/usr/local/bin/example (code=exited, status=0/SUCCESS)
 Main PID: 3367 (code=exited, status=0/SUCCESS)

Confirming the timer is working

In general the systemctl status example.timer provides us information on when the service was run last and will run in the future. The systemctl status example will show us the exit code of the program that we activated.

To see our program actually ran we can either do an ls -l /tmp/example or watch ls -l /tmp/example and see the timestamp of the file is changing every 3 minutes.

Events

The heart of this example is the timer’s OnCalendar field which runs every 3 minutes. This is done with the value *-*-* *:00/3:00. To understand this format see the manual page for the time format. This page provides the syntax and examples. Once the OnCalendar value is constructed we can easily test it with systemd-analyze. This command will print some details about the when the timer will kick off.

$ systemd-analyze calendar '*-*-* *:00/3:00'
Normalized form: *-*-* *:00/3:00
    Next elapse: Sat 2022-07-09 11:30:00 MDT
       (in UTC): Sat 2022-07-09 17:30:00 UTC
       From now: 30s left

The systemd-analyze command will ensure our OnCalendar value is correct.

$ systemd-analyze calendar '*-* zz*:00/3:00'
Failed to parse calendar specification '*-* zz*:00/3:00': Invalid argument

Advantages to systemd timers over cron

As we’ve seen a systemd timer has more moving parts than a simple entry in the crontab file. However, by using systemd we have some advantages:

This information has been taken from the systemd.timer manpage.

References

time format manpage systemd.timer manpage systemd-analyze manpage