Contents

Systemd Timer Units

Systemd, along with being an init system, contains many other functions that aim to improve long running Linux mainstays. Timers are the systemd solution to cron jobs with added functionality and features.

Boiling it down, a timer is just a .timer unit file containing a time definition and a .service unit file describing the program to run once time has elapsed. Where the magic comes in, is the tight integration with systemd.

Why use timers over cron?

Benefits

  • Dependency handling
    Timers leverage the benefits of the systemd .service unit file. In the .service file you can define Requires=, After=, and Before= dependencies. This gives you control to, for example, ensure that your job does not run until after networking is up. Truthfully, all niceties provided by .service files can be considered a feature of timers as compared to legacy cron.
  • Absolute and relative timing
    Along with timing based on absolute calendar time, you can also set a job to run relative to system boot, timer unit activation, last job activation, or even time of job inactivity. Timers allow multiple time definitions in their unit file. For example, you can specify a job to run on boot, every Wednesday, and on July 21st 2022, all in the same file.
  • Logging
    By default, all job output is logged to the systemd journal. This allows easy tracking of program output and can provide an simple debug path when things are not working as expected.
  • Job status
    This is perhaps one of the best features of timers. To view all timers on a system, you can run systemctl list-timers. This command will output detailed information about each job scheduled to run.
    [user@local]$ systemctl list-timers
    NEXT                        LEFT       LAST                        PASSED             UNIT                         ACTIVATES                     
    Sun 2021-04-11 15:45:20 MDT 18min left Sat 2021-04-10 17:53:07 MDT 21h ago            dnf-makecache.timer          dnf-makecache.service                        
    Mon 2021-04-12 00:00:00 MDT 8h left    Sun 2021-04-11 14:31:16 MDT 55min ago          mlocate-updatedb.timer       mlocate-updatedb.service              
    Mon 2021-04-12 00:21:54 MDT 8h left    Sat 2021-04-03 14:48:22 MDT 1 weeks 1 days ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service

Drawbacks

Not all is peaches and rainbows of course, timers do have a few drawbacks.

  • Require two files for each job you wish to run
  • Take more time to setup as their configuration options are much deeper
  • No easy mail functionality like cron.
  • Part of systemd, so not standard on every nix.

How time is represented in systemd

I can not talk about timers without first mentioning how time is represented in systemd.
In cron, you normally define time with a string formatted as minute, hour, day of month, month, day of week. The string 12 3 * * * would tell cron to run your job every day at 3:12am. Systemd defines time as year-month-day hour-min-sec. The same time in systemd would be *-*-* 03:15:00.
Much like cron, systemd also allows shorthand such as mon and fri to represent days of the week as well as many other abbreviations for common intervals. Where required, time can also be specified in microsecond granularity!
For detailed information and a listing of shorthand, please check out the official documentation

Creating a timer

Lets go ahead and step through building a quick timer for a basic system maintenance job. As mentioned earlier, we need two files, a .service unit and a .timer unit

.service

We will use Let’s Encrypt certificate renewal as our scheduled task. Lets call this lets-encrypt-renew.service and save the file under /etc/systemd/system/
Define the Type as oneshot. This allows the execution of multiple ExecStart directives in the order which they are defined. Very helpful when running job that may consist of multiple steps.
ExecStart will contain our script or command we wish to run.

[Unit]
Description=Renew Let's Encrypt Certificates

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --preferred-challenges http --quiet --agree-tos --disable-renew-updates --non-interactive --nginx

.timer

Named identically to the .service, the let-encrypt-renew.timer file is where we will define the schedule we want to renew our certificates on. This will also be saved under /etc/systemd/system/.

Let’s Encrypt certificates have a lifetime of 90 days with automatic renewal allowed after 60. The certbot client actually checks if you are within the 30 day until expiry window before reaching out to the Let’s Encrypt servers. Because of this, the recommendation is a daily check in order to account for any Let’s Encrypt maintenance or downtime that might impact a job you only run once a month.

We are actually going to take advantage of a benefit of timers and define two different times we want to check for renewal. First, we will check 5 minuets after boot using OnBootSec=5min. This allows us to ensure certificates are up to date, even if the server has been offline for awhile. The second time is going to be our normal daily check using OnCalendar=daily. The key word daily is shorthand which expands to *-*-* 00:00:00.

[Unit]
Description=Renew Let's Encrypt Certificates --timer

[Timer]
OnBootSec=5min
OnCalendar=daily
Unit=lets-encrypt-renew.service

[Install]
WantedBy=timers.target

Enable and start

Like any other unit, you must enable and start it. We do this on the .timer and not the .service. Enabling makes sure it starts on reboot and start tells it to run now. Run being the start of your timer, important if your job execution is relative to the start time of the service.
systemctl enable let-encrypt-renew.timer
systemctl start let-encrypt-renew.timer

You should now be able to view your timer and how much time left until the next run using systemctl --list-timers.

That is all! Of course, this is pretty much the bare bones in setting up a job to run on a timer. There are many more configurable options and non listed defaults in both the .service and .timer file. I recommend checking out the official documentation listed below for much greater detail.