published on in blog

How (and Why) to Replace cron Jobs With systemd Timers

Quick Links

One of the changes systemd brought in was a new way to schedule jobs, with more finesse than cron. Some Linux distributions no longer ship cron. It’s time to check out systemd timers.

Why systemd Timers Are Replacing cron

The origins of cron date all the way back to 1975, in Version 7 Unix. Its dependability soon made it a favorite tool for scheduling tasks to run at specified dates and times. Admittedly, its syntax is pretty quirky. If you don’t use it frequently, you’ll probably need to look up the finer points each time you want to schedule a job.

In cron schedules, days and months are numbered starting at one. The days of the week, however, are numbered from zero to six, for Sunday through to Saturday. And on some systems, seven means Sunday, too. But, as quirky as this might be, it works.

The systemd service manager brought much more to the table than a simple replacement for the init boot manager. Part of what it delivered was a modern replacement for cron, in the form of systemd timers. These offer more flexibility than cron does, and without the need for another external utility. They’re built right into all systemd distributions.

That means timers behave the same way on all systemd installations. There are many versions of cron and cron-like substitutes. If you need to have standardization across a number of computers, systemd makes your life easier. The same timers will run the same way on all of them. In fact, some systemd-based distributions no longer ship cron as part of their standard offering.

Not surprisingly, the Red Hat-derived distributions including Fedora don’t ship cron, because systemd is a Red Hat initiative. Arch and its derivatives don’t include cron, but that's probably more to do with them providing such a minimal distribution that you populate with applications to suit. Other distributions, such as Solus, don't see a need to include cron either. Of course, you could install cron on any distribution you like, but there’s no compelling argument to do so.

Related: Why Linux's systemd is Still Divisive After All These Years

How systemd Timers Work

With systemd timers, you need to create two files. One is a service file. When the service runs, it launches your process for you. So the service file needs to know about your target process.

The second file you need to create is a timer file. This determines when the service is launched. So the timer file needs to know about your service file.

Timers can be real-time or monotonic. Real-time timers are triggered by calendar events. Monotonic timers are triggered at some duration after a system event, such as booting. Log entries are added to the system journal for timer events, which can help with debugging.

You can list the timers on your computer by using the status option of the systemctl command. Several system timers are created automatically, so even if you haven’t created any timers, there will be output to this command.

systemctl status "*timer" 

Each description contains the following information:

  • The timer’s name and description, if one was provided.
  • Loaded: Shows the loaded status. Normally our timer is read and loaded into memory. The directory path to the timer file is shown. We’d expect our timer to be “enabled”, but it might be temporarily “disabled” if we’ve chosen to turn it off. The vendor preset indicates whether, when the timer was first created, it was set to “enabled” or “disabled.”
  • Active: Shows the active status, including the date and time when the timer was activated. Typically, you’d expect to see a timer listed as “active”, and “waiting” for its next launch time and date.
  • Until: Confusingly, this line doesn’t apply to timers. We can ignore it.
  • Trigger: Shows the name of the process that is launched by the timer.
  • Triggers: Shows when the timer will next be triggered, and when the process will next be launched.
  • Docs: Some timers have associated documentation. This line is optional, and not always present.

Because timers are actually time-controlled services, we can use the systemctl command to control them.

Creating a Simple systemd Timer

The written word is a static medium, making it difficult to illustrate a process being triggered at a specific time. What we’ll do is create a script that’ll be launched by our new timer, and have it write a timestamp to a logfile. That’ll tell us our service is working, and when it was last triggered.

We’ll use the date command to generate the timestamp, and we’ll redirect it to a file called “timer.log” in our home directory.

We’ll create the script in the “/usr/local/bin/” directory. We’re using the “gedit” editor, but you can use whichever editor you prefer.

sudo gedit /usr/local/bin/geek-timer.sh  

Copy these lines into your editor, save the file as “geek-timer.sh”, and close your editor.

#!/bin/bash

echo "Timer fired: $(date)" >> /home/dave/timer.log

We’ll need to make our script executable.

sudo chmod +x /usr/local/bin/geek-timer.sh 

Let’s just check that our script does what it is meant to.

geek-timer.sh
cat timer.log

That verifies that our script executes as expected and puts a timestamp into the “timer.log” file.

Now we’ll create a service file to define the service we want to have launched when the timer is triggered.

sudo gedit /etc/systemd/system/geek-timer.service  

Copy these lines to your editor, save the file as “/etc/systemd/system/geek-timer.service”, and close your editor.

[Unit]
Description="How-To Geek systemd timer"
Requires=geek-timer.timer

[Service]
Type=simple
ExecStart=/usr/local/bin/geek-timer.sh
User=dave

The “[Unit]” section contains two lines. The "Description=" line is a simple one-liner that says what your service is for. The “Requires=” line indicates that this service relies on the “geek-timer.timer” timer file. We’ll create this file next.

There’s a little bit more in the “[Service]” section. The “Type=” is "simple", meaning this is a basic service. Other options include “oneshot”, which would mean the service ran once only.

The “ExecStart=” line indicates which process the service should start. This is pointing to our script that we created earlier.

The “User=” line defines which user should run the command. Without this, the process would be launched by root.

Now we’ll create the timer file. This defines when the service is launched. It’s good practice to use the same base name for the server and timer files, with different extensions.

sudo gedit /etc/systemd/system/geek-timer.timer 

Copy these lines to your editor, save the file as “/etc/systemd/system/geek-timer.timer”, and close your editor.

[Unit]
Description="Timer for the geek-timer.service"

[Timer]
Unit=geek-timer.service
OnBootSec=5min
OnUnitActiveSec=1min

[Install]
WantedBy=timers.target

The “[Unit]” section contains a “Description=” line of text. The “[Timer]” section contains three settings. The “Unit=” line indicates which service should be launched when this timer triggers. The “OnBootSec=” line tells the system to launch the service five minutes after the computer boots. The “OnUnitActiveSec=” line tells the timer to launch the service one minute after it last activated.

In other words, five minutes after the computer is booted, the service will launch. The service will then repeat with an interval of one minute.

In the “[Install]” section, we've include a "WantedBy=" line that specifies "timers.target." The "timers.target" is a special target unit that sets up all timer units that are active after boot, and is suitable for all basic systemd timers.

We can use the systemctl status command to look at our new timer.

systemctl status geek-timer.service 

There are no errors reported, which is good. It is inactive because we haven’t started the service. Even if we reboot now, our service and timer won’t be enabled. Let’s start and enable our timer.

sudo systemctl enable geek-timer.timer
sudo systemctl start geek-timer.timer

I rebooted the test computer to see if the five-minute grace period after boot-up was respected, and checked its status once more.

systemctl status geek-timer.service 

This shows the PC booted at 12:18, and our timer is going to be triggered at 12:23. Our requested five minutes after boot option is being handled correctly. I waited for the five minutes to expire and then quickly ran the same command.

systemctl status geek-timer.service 

We can see that the trigger time has moved on one minute from 12:23 to 12:24. This means we’re now into the "repeating every minute phase" of our timer.

Checking our logfile a few minutes later, shows the initial trigger time of 12:23, then subsequent launches occur at about a minute apart.

cat timer.log

Related: How to List Linux Services With systemctl

Fine Tuning the Timings

The trigger times of repeating events are slightly fuzzy. That's why I said the events in the logfile are about one minute apart. There’s a small random element added to the trigger time to make sure that timers that are scheduled to trigger at precisely the same time are actually triggered in a staggered fashion. Usually, for tasks like starting a backup or other system housekeeping activities, that’s accurate enough.

If you need to have a tighter resolution, you can use microsecond timing. Adding this line to the “[Timer]” section of your timer file sets the system to use a one-microsecond resolution.

AccuracySec=1us 

These are the accuracy settings you can use, and how you can refer to them.

  • Microseconds: us, µs msec, ms
  • Seconds: seconds, second, sec, s
  • Minutes: minutes, minute, min, m
  • Hours: hours, hour, hr, h
  • Days: days, day, d
  • Weeks: weeks, week, w
  • Months: months, month, M
  • Years: years, year, y

To have a timer trigger at a particular time and date is achieved using the “OnCalendar” setting. This goes in the “[Timer]” section of the timer file. The general format is:

OnCalendar=DayOfTheWeek YYYY-MM-DD HH:MM:SS 

DayOfTheWeek is optional. Any of the other values be replaced by an asterisk ‘*’ to mean “every”, like every minute or every hour. You can use names or numbers for the days and months, and comma-separated lists to represent selections of values. Ranges of values are indicated by using two periods “..” to separate the start and values of a range.

This would set a timer to trigger at 01:15 on the first Friday of each month.

OnCalendar=Fri *-*-1..7 01:15:00 

This would run a process at 19:00 every day.

OnCalendar=*-*-* 19:00:00 

A timer can have more than one trigger time set. This would run a process at a different time on business days than on the weekend.

OnCalendar=Mon..Fri 23:00:00
OnCalendar=Sat,Sun 19:00:00

The systemd time man page full description of time formats and many useful tips and tricks.

Great Flexibility and Ease of Use

Much of the flexibility of systemd timers resides in the way you can cater for odd-ball calendar events. For example, to set a timer to trigger at 15:00 on all days apart from Fridays, you can use this format:

Mon..Thu,Sat,Sun *-*-* 15:00:00 

By combining comma-separated lists and ranges you’ll find it easy to create real-time triggers for all manner of complicated requirements.

Once you've got the idea that you create a service to launch your process, and a timer file to govern the launching of the service, you're 90 percent of the way to understanding systemd timers. The last 10 percent is embracing the graceful power of calendar events.

Related: How to Run a Linux Program at Startup With systemd

ncG1vNJzZmivp6x7qbvWraagnZWge6S7zGipnqiclrCmecKrpqdlmqSvtHnWoquhZaOuwLWxzJ1kraGdmr%2B0ew%3D%3D