Scheduling Jobs In Linux

An essential task for any System Administrator is scheduling tasks to run at off-hours. Linux offers several scheduler options including cron, anacron, at and systemd-timers.

cron – Standard Scheduling

cron is a system daemon that will run a task at a specified time (to the minute). This is exceptionally useful to run re-occurring jobs, such as system maintenance or backups. It has been the standard scheduling tool in Linux and UNIX for several decades.

Each user has a separate cron schedule, which can be accessed via crontab -e when logged in as that user. In server environments, individual users often do not have (m)any scheduled jobs, but the root user (or other power-user, such as the database user) may have hundreds.

The scheduler can be entered using the crontab -e command and the format for entering a scheduled job is as follows:

* * * * * commands
- - - - -
| | | | |
| | | | ------ Day of Week (0 - 7) (Sunday = 0 or 7)
| | | -------- Month (1 - 12)
| | ---------- Day of Month (1 - 31)
| ------------ Hour (0 - 23)
-------------- Minute (0 -59)

So the following entry in crontab -e would run every 30 minutes:

0/30 * * * * /random/maintenance/script.sh

Additionally, crontab -e will accept these values:

string         meaning
------         -------
@reboot        Run once, at startup.
@yearly        Run once a year, "0 0 1 1 *".
@annually      (same as @yearly)
@monthly       Run once a month, "0 0 1 * *".
@weekly        Run once a week, "0 0 * * 0".
@daily         Run once a day, "0 0 * * *".
@midnight      (same as @daily)
@hourly        Run once an hour, "0 * * * *".

Crontab Guru is a handy interactive guide to scheduling crontab -e entries. Additionally, the official documentation is available at man 5 crontab and will fill in many more details.

There are a couple of “gotchas” with cron. Firstly, there is no STDOUT with cron. Scripts that produce standard output (writes to the terminal) will fail. Instead, redirect the output to a log file (either within crontab -e or within the script itself):

# crontab entry with output redirected to a log file
0/30 * * * * /random/maintenance/script.sh >> /home/brad/cronLogs/maintenanceScript.log

The second thing to remember within crontab -e is not to use relative paths. Absolute paths are strongly recommended (even within the shell scripts running). However, this is an easy task to accomplish within any shell script by setting a $BASEDIR variable:

#!/bin/bash
                    
# Declare the base directory
$BASEDIR='/myMainteanceScripts/'
                   
# Run some scripts
$BASEDIR/backupDirs.sh
$BASEDIR/deleteOldBackups.sh
$BASEDIR/cleanTempFiles.sh

anacron – Loosely Scheduled

anacron is an adaptation of cron, but allows for tasks to be loosely scheduled based on system activity. When a script is placed within the /etc/cron.daily directory, it will run at some point during the 24-hour cycle whenever the system load is low enough to accommodate it. Other scheduling options include the /etc/cron.hourly, /etc/cron.weekly or /etc/cron.monthly directories.

anacron is great for servers, but it is even more helpful for personal devices such as laptops, as scheduled tasks will be scheduled to run whenever the device is available. This means that if a device is powered off, anacron will perform the job later (within the scheduled parameters). This is different than cron, where if the time is missed for any reason, the task won’t run.

A fairly standard listing of tasks scheduled via anacron in Ubuntu 20.04 includes:

cron.hourly:
total 0

cron.monthly:
total 4
-rwxr-xr-x 1 root root 313 May 29  2017 0anacron

cron.weekly:
total 16
-rwxr-xr-x 1 root root 312 May 29  2017 0anacron
-rwxr-xr-x 1 root root 211 Nov 12  2018 update-notifier-common
-rwxr-xr-x 1 root root 813 Feb 10  2019 man-db
-rwxr-xr-x 1 root root 730 Mar  8 12:15 apt-xapian-index

cron.daily:
total 52
-rwxr-xr-x 1 root root  384 Dec 12  2012 cracklib-runtime
-rwxr-xr-x 1 root root  311 May 29  2017 0anacron
-rwxr-xr-x 1 root root  376 Nov 20  2017 apport
-rwxr-xr-x 1 root root  355 Dec 29  2017 bsdmainutils
-rwxr-xr-x 1 root root 1478 Apr 20  2018 apt-compat
-rwxr-xr-x 1 root root  214 Nov 12  2018 update-notifier-common
-rwxr-xr-x 1 root root  377 Jan 21  2019 logrotate
-rwxr-xr-x 1 root root 1123 Feb 10  2019 man-db
-rwxr-xr-x 1 root root 1187 Jul 16  2019 dpkg
-rwxr-xr-x 1 root root  543 Jul 16  2019 mlocate
-rwxr-xr-x 1 root root  539 Apr 13 14:19 apache2

at – One-Time Tasks

System Administrators (and power users) often need to schedule one-time tasks for later (but don’t want the job to re-occur on a schedule). Linux has a handy tool for this: at.

Quick note -> at isn’t installed by default within some Debian distributions. It can be installed with apt install at. It is installed by default on RHEL-based distributions.

Before using at, ensure the atd service is running with systemctl status atd. If the service isn’t active, systemctl enable --now atd will get the service running in the background. Using at is simple using its basic interactive shell:

[brad@fedora32 Documents]$ at 4pm
   warning: commands will be executed using /bin/sh
   at> /home/brad/myBackupScript.sh
   at> /home/brad/cleanupDownloads.sh
   at> 
   job 3 at Sat Aug 29 16:00:00 2020

These commands have set up two jobs to run at 4 pm: a backup script and a cleanup script. Note that the output is caused by Ctrl-D, which exits the interactive shell. To view scheduled jobs, run atq:

[brad@fedora32 Documents]$ atq
3       Sat Aug 29 16:00:00 2020 a brad

Finally, to remove a scheduled job, atrm will take care of it:

[brad@fedora32 Documents]$ atrm 3
[brad@fedora32 Documents]$ atq
[brad@fedora32 Documents]$

systemd-timers

systemd timers is a more modern and updated way to schedule jobs in systems that are running systemd (most of the mainstream distributions now run systemd). Many new packages (or redeveloped packages) are beginning to utilize it. The documentation includes man 7 systemd-timer and man 7 systemd-time. The timer files can be found at /usr/lib/systemd/system. Listing the timers that are on a system is simple:

[root@fedora32 ~]# systemctl list-unit-files --type=timer
UNIT FILE                       STATE     VENDOR PRESET
chrony-dnssrv@.timer            disabled  disabled
dnf-makecache.timer             enabled   enabled
fstrim.timer                    enabled   enabled
fwupd-refresh.timer             disabled  disabled
logrotate.timer                 enabled   enabled
mdadm-last-resort@.timer        static    disabled
mlocate-updatedb.timer          enabled   enabled
raid-check.timer                disabled  disabled
systemd-tmpfiles-clean.timer    static    disabled
unbound-anchor.timer            enabled   enabled

10 unit files listed.

To schedule a job, both a .timer file and a .service file are required. The .timer file contains information about when the job will run, and the .service file includes what will run. The advantage is that this system allocates significantly more options and controls. For example, in the .timer file below, the OnBootSec property determines the job will run 10 minutes after bootup, but then the OnUnitInactiveSec property determines that the job will also run every hour:

[root@fedora32 ~]# systemctl cat dnf-makecache.timer
   # /usr/lib/systemd/system/dnf-makecache.timer
   [Unit]
   Description=dnf makecache --timer
   ConditionKernelCommandLine=!rd.live.image
   # See comment in dnf-makecache.service
   ConditionPathExists=!/run/ostree-booted
   Wants=network-online.target
	
   [Timer]
   OnBootSec=10min # Runs 10 minutes after boot
   OnUnitInactiveSec=1h #  Runs every hour thereafter
   Unit=dnf-makecache.service
	
   [Install]
   WantedBy=timers.target

To activate the timer, use systemctl enable .timer and systemctl start .timer and it will start running on its intended schedule.

To determine when a job last ran, issue systemctl status dnf-makecache.service:

[root@fedora32 ~]# systemctl status dnf-makecache.service
● dnf-makecache.service - dnf makecache
Loaded: loaded (/usr/lib/systemd/system/dnf-makecache.service; static; vendor preset: disabled)
Active: inactive (dead) since Sat 2020-08-29 14:38:30 ADT; 55min ago
TriggeredBy: ● dnf-makecache.timer
Main PID: 2963 (code=exited, status=0/SUCCESS)
CPU: 12.803s

Aug 29 14:38:22 fedora32.personalLaptop dnf[2963]: RPM Fusion for Fedora 32 - Free - Updates kB/s | 9.9 kB     00:00
Aug 29 14:38:23 fedora32.personalLaptop dnf[2963]: RPM Fusion for Fedora 32 - Free - Updates kB/s | 618 kB     00:00
Aug 29 14:38:24 fedora32.personalLaptop dnf[2963]: RPM Fusion for Fedora 32 - Free kB/s | 3.1 kB     00:00
Aug 29 14:38:25 fedora32.personalLaptop dnf[2963]: RPM Fusion for Fedora 32 - Nonfree - Updates kB/s | 9.7 kB     00:00
Aug 29 14:38:25 fedora32.personalLaptop dnf[2963]: RPM Fusion for Fedora 32 - Nonfree - Updates kB/s | 139 kB     00:00
Aug 29 14:38:26 fedora32.personalLaptop dnf[2963]: RPM Fusion for Fedora 32 - Nonfree    kB/s | 3.1 kB     00:00
Aug 29 14:38:30 fedora32.personalLaptop dnf[2963]: Metadata cache created.
Aug 29 14:38:30 fedora32.personalLaptop systemd[1]: dnf-makecache.service: Succeeded.
Aug 29 14:38:30 fedora32.personalLaptop systemd[1]: Finished dnf makecache.
Aug 29 14:38:30 fedora32.personalLaptop systemd[1]: dnf-makecache.service: Consumed 12.803s CPU

Notice that the service ran 55 minutes ago, meaning it will run again in 5 minutes.

Finally, a quick look at the service file itself shows what is executed every hour with this systemd timer:

[root@fedora32 ~]# systemctl cat dnf-makecache.service
# /usr/lib/systemd/system/dnf-makecache.service
[Unit]
Description=dnf makecache
# On systems managed by either rpm-ostree/ostree, dnf is read-only;
# while someone might theoretically want the cache updated, in practice
# anyone who wants that could override this via a file in /etc.
ConditionPathExists=!/run/ostree-booted

After=network-online.target

[Service]
Type=oneshot
Nice=19
IOSchedulingClass=2
IOSchedulingPriority=7
Environment="ABRT_IGNORE_PYTHON=1"
ExecStart=/usr/bin/dnf makecache --timer

Notice that much more control is available. For example, the Nice=19 parameter means the system scheduler will give the job a very low priority so that it will get done, but not at the expense of higher priority jobs.


While cron is still the most widely used scheduling tool in Linux, systemd timers are becoming increasingly popular, particularly among package developers. All System Administrators and power users should become familiar with cron, anacron, at and systemd-timers.