Schedule Jobs on Linux

Calendar_Icon

If you want to schedule a program to run on a Unix based OS, there are a handful of options, but the prominent ones found on Linux are cron and systemd timers.

Initially released in 1975, cron has stood the test of time when it comes to running a task on a schedule and it continues to be the standard solution for all kinds of users.

How does it work

There is a file called crontab which tells the cron daemon what to run and when to run it.

Daemons

There are several cron daemons out there and the default one will depend on what distro your using, but they all understand the same crontab, which is what ends up getting executed in the end. The cron package on Arch is called cronie or just cron on Debian based distros.

Crontab

The system crontab file, is found at /etc/crontab, but there is also a place where users can create their own crontab file by using crontab -e.

The system crontab does require you to specify which user to run the command as whether that be root or someone else.

On my system, the user crontabs are stored in the /var/spool/cron/ directory in a file with the same name as $USER.

Syntax

An overview of the syntax is as follows:

There are five fields you must specify:

minute, hour, day, month, day of the week.

┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of the month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday;
│ │ │ │ │                                   7 is also Sunday on some systems)
│ │ │ │ │
│ │ │ │ │
* * * * * command to execute

So running something every hour would be done like this:

0 * * * *	echo "hourly job"

To run something every minute you can do:

* * * * *	echo "minute job"

And to run something every day you can do:

* 0 * * *	echo "day job"

etc.

You can also specify ranges with a -, steps with a /, and use , as a separator

An example using all:

*/30 9-17 * * 1,5

In English:

Every 30 minutes past every hour from 9:00-5:00 on Monday and Friday.

Other tools

It can get a bit confusing if you have a complicated set of times you wish to run a certain program.

A site I would recommend is https://crontab.guru/, which puts this syntax in plain English and has some common examples to use. As always the archwiki is a great resource for this kind of thing.

One thing to note is cron expects a system to be persistent and always on, meaning if your machine is down for whatever reason, the job will not be run when it is booted back up. To combat this, anacron was created and that is what many desktop distributions use along with cron.

Check jobs and logs

As a user, to see what’s in your crontab, you can run crontab -l.

One way to check if what cron tasks were run or not is to check the log.

On a box running systemd, you can use journalctl -u <cron daemon> to check out the logs, which should report any jobs that were run.

Anacron

The syntax and functionality of anacron is different from cron, but it mostly serves the same purpose which is to run a task periodically.

The default anacrontab looks very similar to a crontab with some minor differences.

# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# the maximal random delay added to the base delay of the jobs
RANDOM_DELAY=45
# the jobs will be started during the following hours only
START_HOURS_RANGE=3-22

#period in days   delay in minutes   job-identifier   command
1	5	cron.daily		nice run-parts /etc/cron.daily
7	25	cron.weekly		nice run-parts /etc/cron.weekly
@monthly 45	cron.monthly		nice run-parts /etc/cron.monthly

With anacron, there are shortcuts for common periods of time. Like @monthly shown in the example, there is also @daily, @weekly, and so on.

In this file you can also see run-parts being used to run all the scripts in certain /etc/cron.* directories. This means that instead of creating a special entry in a crontab, a user can simply place a script in one of these directories and it will be run as the name suggests.

Time Resolution

A minute is the shortest measure of time in cron, as opposed to systemd timers, which can handle seconds, milliseconds, and nanoseconds.

Of course there are other daemons that can handle this kind of task. And there is always the opportunity to write one :)

A commonplace solution is systemd-timers because systemd is more or less the default init system for most Linux distros.

Systemd timers

Checking on what timers are enabled on a system is done with:

$ systemctl list-timers

NEXT                        LEFT     LAST                        PASSED       UNIT                         ACTIVATES
Tue 2020-07-21 15:08:22 EDT 12h left Mon 2020-07-20 14:05:40 EDT 12h ago      systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Wed 2020-07-22 00:00:00 EDT 21h left Tue 2020-07-21 00:00:01 EDT 2h 15min ago atop-rotate.timer            atop-rotate.service
Wed 2020-07-22 00:00:00 EDT 21h left Tue 2020-07-21 00:00:01 EDT 2h 15min ago man-db.timer                 man-db.service
Wed 2020-07-22 00:00:00 EDT 21h left Tue 2020-07-21 00:00:01 EDT 2h 15min ago shadow.timer                 shadow.service

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

A systemd .timer unit file is usually associated with a .service unit.

Let’s look at a timer unit file. The man-db.timer is located at /usr/lib/systemd/system/man-db.timer on my system.

# man-db.timer

[Unit]
Description=Daily man-db regeneration
Documentation=man:mandb(8)

[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=true

[Install]
WantedBy=timers.target

Note the WantedBy=timers.target and the time directives under [Timer] like OnCalendar= and AccuracySec=. Systemd gives more directives for controlling when a service runs. man systemd.timers also online is the best resource to find this out.

This unit file only describes when to run a job, but now how. That is the purpose of a service file (/usr/lib/systemd/system/man-db.service in this case).

# man-db.service

[Unit]
Description=Daily man-db regeneration
Documentation=man:mandb(8)
ConditionACPower=true

[Service]
Type=oneshot
# Recover from deletion, per FHS.
ExecStart=+/usr/bin/install -d -o root -g root -m 0755 /var/cache/man
# Expunge old catman pages which have not been read in a week.
ExecStart=/usr/bin/find /var/cache/man -type f -name *.gz -atime +6 -delete
# Regenerate man database.
ExecStart=/usr/bin/mandb --quiet
User=root
Nice=19
IOSchedulingClass=idle
IOSchedulingPriority=7

Here we can see what the service is actually running with ExecStart= and some other details of how the processes get run, including User= and Nice=. This is a pretty common looking service file, the main difference being that it gets run from a timer, and not at one of the boot time run levels.

Cron or Systemd

Well, I think there is a case for either one depending on the situation. If you already have a unit file made for your script/program, it would be rather easy to run it by creating and enabling a systemd timer.

Cron does have the advantage of simplicity and ease of use – although that is debatable depending on how you feel about the syntax. Not requiring the user to create extra files to schedule running things is very powerful, and all it requires is knowing the 5 *’s and some of the caveats of the crontab.