Skip to content

Day 10 - Scheduling tasks

Introduction

Linux has a rich set of features for running scheduled tasks. One of the key attributes of a good sysadmin is getting the computer to do your work for you (sometimes misrepresented as laziness!) - and a well configured set of scheduled tasks is key to keeping your server running well.

The time-based job scheduler cron(8) is the one most commonly used by Linux sysadmins. It’s been around more or less in it’s current form since Unix System V and uses a standardized syntax that’s in widespread use.

Using at to schedule oneshot tasks

If you’re on Ubuntu, you will likely need to install the at package first.

sudo apt update
sudo apt install at

We’ll use the at command to schedule a one time task to be ran at some point in the future.

Next, let’s print the filename of the terminal connected to standard input (in Linux everything is a file, including your terminal!). We’re going to echo something to our terminal at some point in the future to get an idea of how scheduling future tasks with at works.

vagrant@ubuntu2204:~$ tty
/dev/pts/0

Now we’ll schedule a command to echo a greeting to our terminal 1 minute in the future.

vagrant@ubuntu2204:~$ echo 'echo "Greetings $USER!" > /dev/pts/0' | at now + 1 minutes
warning: commands will be executed using /bin/sh
job 2 at Sun May 26 06:30:00 2024

After several seconds, a greeting should be printed to our terminal.

...
vagrant@ubuntu2204:~$ Greetings vagrant!

It’s not as common for this to be used to schedule one time tasks, but if you ever needed to, now you have an idea of how this might work. In the next section we’ll learn about scheduling time-based tasks using cron and crontab.

For a more in-depth exploration of scheduling things with at review the relevant articles in the further reading section below.

Using crontab to schedule jobs

In Linux we use the crontab command to interact with tasks scheduled with the cron daemon. Each user, including the root user, can schedule jobs that run as their user.

Display your user’s crontab with crontab -l.

vagrant@ubuntu2204:~$ crontab -l
no crontab for vagrant

Unless you’ve already created a crontab for your user, you probably won’t have one yet. Let’s create a simple cronjob to understand how it works.

Using the crontab -e command, let’s create our first cronjob. On Ubuntu, if this is you’re first time editing a crontab you will be greeted with a menu to choose your preferred editor.

vagrant@ubuntu2204:~$ crontab -e
no crontab for vagrant - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/nano        <---- easiest
  2. /usr/bin/vim.basic
  3. /usr/bin/vim.tiny
  4. /bin/ed

Choose 1-4 [1]: 2

Choose whatever your preferred editor is then press Enter.

At the bottom of the file add the following cronjob and then save and quit the file.

* * * * * echo "Hello world!" > /dev/pts/0

NOTE: Make sure that the /dev/pts/0 file path matches whatever was printed by your tty command above.

Next, let’s take a look at the crontab we just installed by running crontab -l again. You should see the cronjob you created printed to your terminal.

vagrant@ubuntu2204:~$ crontab -l
* * * * * echo "Hello world!" > /dev/pts/0

This cronjob will print the string Hello world! to your terminal every minute until we remove or update the cronjob. Wait a few minutes and see what it does.

vagrant@ubuntu2204:~$ Hello world!
Hello world!
Hello world!
...

When you’re ready uninstall the crontab you created with crontab -r.

Understanding crontab syntax

The basic crontab syntax is as follows:

* * * * * command to be executed
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday=0 or 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)
  • Minute values can be from 0 to 59.
  • Hour values can be from 0 to 23.
  • Day of month values can be from 1 to 31.
  • Month values can be from 1 to 12.
  • Day of week values can be from 0 to 6, with 0 denoting Sunday.

There are different operators that can be used as a short-hand to specify multiple values in each field:

Symbol Description
* Wildcard, specifies every possible time interval
, List multiple values separated by a comma.
- Specify a range between two numbers, separated by a hyphen
/ Specify a periodicity/frequency using a slash

There’s also a helpful site to check cron schedule expressions at crontab.guru.

Use the crontab.guru site to play around with the different expressions to get an idea of how it works or click the random button to generate an expression at random.

Your Tasks Today

  1. Schedule daily backups of user’s home directories
  2. Schedule a task that looks for any backups that are more than 7 days old and deletes them

Automating common system administration tasks

One common use-case that cronjobs are used for is scheduling backups of various things. As the root user, we’re going to create a cronjob that creates a compressed archive of all of the user’s home directories using the tar utility. Tar is short for “tape archive” and harkens back to earlier days of Unix and Linux when data was commonly archived on tape storage similar to cassette tapes.

As a general rule, it’s good to test your command or script before installing it as a cronjob. First we’ll create a backup of /home by manually running a version of our tar command.

vagrant@ubuntu2204:~$ sudo tar -czvf /var/backups/home.tar.gz /home/
tar: Removing leading `/' from member names
/home/
/home/ubuntu/
/home/ubuntu/.profile
/home/ubuntu/.bash_logout
/home/ubuntu/.bashrc
/home/ubuntu/.ssh/
/home/ubuntu/.ssh/authorized_keys
...

NOTE: We’re passing the -v verbose flag to tar so that we can see better what it’s doing. -czf stand for “create”, “gzip compress”, and “file” in that order. See man tar for further details.

Let’s also use the date command to allow us to insert the date of the backup into the filename. Since we’ll be taking daily backups, after this cronjob has ran for a few days we will have a few days worth of backups each with it’s own archive tagged with the date.

vagrant@ubuntu2204:~$ date
Sun May 26 04:12:13 UTC 2024

The default string printed by the date command isn’t that useful. Let’s output the date in ISO 8601 format, sometimes referred to as the “ISO date”.

vagrant@ubuntu2204:~$ date -I
2024-05-26

This is a more useful string that we can combine with our tar command to create an archive with today’s date in it.

vagrant@ubuntu2204:~$ sudo tar -czvf /var/backups/home.$(date -I).tar.gz /home/
tar: Removing leading `/' from member names
/home/
/home/ubuntu/
...

Let’s look at the backups we’ve created to understand how this date command is being inserted into our filename.

vagrant@ubuntu2204:~$ ls -l /var/backups
total 16
-rw-r--r-- 1 root root 8205 May 26 04:16 home.2024-05-26.tar.gz
-rw-r--r-- 1 root root 3873 May 26 04:07 home.tar.gz

NOTE: These .tar.gz files are often called tarballs by sysadmins.

Create and edit a crontab for root with sudo crontab -e and add the following cronjob.

0 5 * * * tar -zcf /var/backups/home.$(date -I).tar.gz /home/

This cronjob will run every day at 05:00. After a few days there will be several backups of user’s home directories in /var/backups.

If we were to let this cronjob run indefinitely, after a while we would end up with a lot of backups in /var/backups. Over time this will cause the disk space being used to grow and could fill our disk. It’s probably best that we don’t let that happen. To mitigate this risk, we’ll setup another cronjob that runs everyday and cleans up old backups that we don’t need to store.

The find command is like a swiss army knife for finding files based on all kinds of criteria and listing them or doing other things to them, such as deleting them. We’re going to craft a find command that finds all of the backups we created and deletes any that are older than 7 days.

First let’s get an idea of how the find command works by finding all of our backups and listing them.

vagrant@ubuntu2204:~$ sudo find /var/backups -name "home.*.tar.gz"
/var/backups/home.2024-05-26.tar.gz
...

What this command is doing is looking for all of the files in /var/backups that start with home. and end with .tar.gz. The * is a wildcard character that matches any string.

In our case we need to create a scheduled task that will find all of the files older than 7 days in /var/backups and delete them. Run sudo crontab -e and install the following cronjob.

30 5 * * * find /var/backups -name "home.*.tar.gz" -mtime +7 -delete

NOTE: The -mtime flag is short for “modified time” and in our case find is looking for files that were modified more than 7 days ago, that’s what the +7 indicates. The find command will be covered in greater detail on Day 11 - Finding things….

By now, our crontab should look something like this:

vagrant@ubuntu2204:~$ sudo crontab -l
# Daily user dirs backup
0 5 * * * tar -zcf /var/backups/home.$(date -I).tar.gz /home/
# Retain 7 days of homedir backups
30 5 * * * find /var/backups -name "home.*.tar.gz" -mtime +7 -delete

Setting up cronjobs using the find ... -delete syntax is fairly idiomatic of scheduled tasks a system administrator might use to manage files and remove old files that are no longer needed to prevent disks from getting full. It’s not uncommon to see more sophisticated cron scripts that use a combination of tools like tar, find, and rsync to manage backups incrementally or on a schedule and implement a more sophisticated retention policy based on real-world use-cases.

System crontab

There’s also a system-wide crontab defined in /etc/crontab. Let’s take a look at this file.

vagrant@ubuntu2204:~$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
# You can also override PATH, but by default, newer versions inherit it from the environment
#PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name command to be executed
17  *  *  *  *  root      cd / && run-parts --report /etc/cron.hourly
25  6  *  *  *  root      test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47  6  *  *  7  root      test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52  6  1  *  *  root      test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

By now the basic syntax should be familiar to you, but you’ll notice an extra field user-name. This specifies the user that runs the task and is unique to the system crontab at /etc/crontab.

It’s not common for system administrators to use /etc/crontab anymore and instead user’s are encouraged to install their own crontab for their user, even for the root user. User crontab’s are all located in /var/spool/cron. The exact subdirectory tends to vary depending on the distribution.

vagrant@ubuntu2204:~$ sudo ls -l /var/spool/cron/crontabs
total 8
-rw------- 1 root    crontab  392 May 26 04:45 root
-rw------- 1 vagrant crontab 1108 May 26 05:45 vagrant

Each user has their own crontab with their user as the filename.

Note that the system crontab shown above also manages cronjobs that run daily, weekly, and monthly as scripts in the /etc/cron.* directories. Let’s look at an example.

vagrant@ubuntu2204:~$ ls -l /etc/cron.daily
total 20
-rwxr-xr-x 1 root root  376 Nov 11  2019 apport
-rwxr-xr-x 1 root root 1478 Apr  8  2022 apt-compat
-rwxr-xr-x 1 root root  123 Dec  5  2021 dpkg
-rwxr-xr-x 1 root root  377 Jan 24  2022 logrotate
-rwxr-xr-x 1 root root 1330 Mar 17  2022 man-db

Each of these files is a script or a shortcut to a script to do some regular task and they’re run in alphabetic order by run-parts. So in this case apport will run first. Use less or cat to view some of the scripts on your system - many will look very complex and are best left well alone, but others may be just a few lines of simple commands.

vagrant@ubuntu2204:~$ cat /etc/cron.daily/dpkg 
#!/bin/sh

# Skip if systemd is running.
if [ -d /run/systemd/system ]; then
  exit 0
fi

/usr/libexec/dpkg/dpkg-db-backup

As an alternative to scheduling jobs with crontab you may also create a script and put it into one of the /etc/cron.{daily,weekly,monthly} directories and it will get ran at the desired interval.

A note about systemd timers

All major Linux distributions now include “systemd”. As well as starting and stopping services, this can also be used to run tasks at specific times via “timers”. See which ones are already configured on your server with:

systemctl list-timers

Use the links in the further reading section to read up about how these timers work.

Further reading

License

Some rights reserved. Check the license terms here

Comments