cron

cron

cron is a commonly confusing and misconfigured aspect of the operating system. Technically, cron is just the clock daemon (/usr/sbin/cron or perhaps /usr/sbin/crond) that executes commands at specific times. However, a handful of configuration files and programs go into making up the cron package. Like many system processes, cron never ends.

The controlling files for cron are the cron-tables or crontabs. The crontabs are often located in /var/spool/cron/crontab. However, on SUSE you will find them in /var/spool/cron/tabs. The names of the files in this directory are the names of the users that submit the cron jobs.

Unlike other UNIX dialects, the Linux cron daemon does not sleep until the next cron job is ready. Instead, when cron completes one job, it will keep checking once a minute for more jobs to run. Also, you should not edit the files directly. You can edit them with text editor like vi, though there is the potential for messing things up. Therefore, you should use the tool that Linux provides: crontab. (see the man-page for more details)

The crontab utility has several functions. It is the means by which files containing the cron jobs are submitted to the system. Second, it can list the contents of your crontab. If you are root, it can also submit and list jobs for any user. The problem is that jobs cannot be submitted individually. Using crontab, you must submit all of the jobs at the same time.

At first, that might sound a little annoying. However, let’s take a look at the process of “adding” a job. To add a cron job, you must first list out the contents of the existing crontab with the -l option. If you are root and wish to add something to another user’s crontab, use the -u option followed by the user’s logname. Then redirect this crontab to a file, which you can then edit. (Note that on some systems crontab has -e (for “edit”), which will do all the work for you. See the man-page for more details.)

Task:

Add a cron-entry for the user jimmo.

  1. login as the root user
  2. Issue the command
    crontab -l -u jimmo >/tmp/crontab.jimmo
  3. Edit the file /tmp/crontab.jimmo using vi.
  4. Append the following line to the bottom of the file:
    15 10,14 * * 1-5 /usr/local/bin/home_backup.sh
  5. Save the file and exit vi.
  6. Active the new cron file with the command:
    crontab -u jimmo /tmp/crontab.jimmo

Note: the script /usr/local/bin/home_backup.sh was created as an exercise in the section Backing Up Your System.

As we saw, to add an entry, you simply add a new line. Save the file, get out of your editor, and run the crontab command again. This time, omit the -l to list the file but include the name of the file as an argument as opposed to redirecting the output. The crontab utility can also accept input from stdin, so you could leave off the file name and crontab would allow you to input the cronjobs on the command line. Keep in mind that any previous crontab is removed no matter what method you use.

I am not a big fan of this for safety reason. I know people who have done it and I have done it in the past myself and something wierd happened in the middle of making the changes (e.g. network connection dropped) and the old file was completed wiped out. So, I have grown used to redirecting the output to a file and then adding that. This way, should something go wrong, I always have the file.

Well, we added the entry, but it probably doesn’t say much. So, let’s take a look at the actual entry. Despite its appearance, each crontab entry consists of only six fields. The first five represent the time the job should be executed and the sixth is the actual command. The first five fields are separated by either a space or a tab and represent the following units, respectively:

  • minutes (0-59)
  • hour (0-23)
  • day of the month (1-31)
  • month of the year (1-12)
  • day of the week (0-6, 0=Sunday)

To specify all possible values, use an asterisk (*). You can specify a single value simply by including that one value. For example, the second line in the previous example has a value of 10 in the first field, meaning 10 minutes after the hour. Because all of the other four time fields are asterisks, this means that the command is run every hour of every day at 10 minutes past the hour.

Ranges of values are composed of the first value, a dash, and the ending value. For example, the fourth line has a range (1-5) in the day of the week column, meaning that the command is only executed on days 1-5, Monday through Friday.

To specify different values that are not within a range, separate the individual values by a comma. In the fourth example, the hour field has the two values 10 and 14. This means that the command is run at 10 a.m. and 2 p.m.

Note that times are additive. Lets look at the example above:

15 10,14 * * 1-5 /usr/local/bin/home_backup.sh

The command is run 15 minutes after hour 10 and 14 on, Monday through Friday. Assume we wanted it to only run on on the 1st and 15, but only of they were a weekday. You might be tempted to do something like this:

15 10,14 1,15 * 1-5 /usr/local/bin/home_backup.sh

If either the first or the 15th were on a weekend, the command would still run because the day of the month field would apply. This is because these are OR-conditions, not AND-conditions. However, this does not mean that if the first is a Monday, the command is run twice.

As of this writing, cron cannot be configure to create complex conditions. If you need to schedule jobs of this nature, take a look at the Open Source Job Scheduler.

Task:

Create a crontab entry to run a job every 10 minutes

The crontab entry can be defined to run at different intervals than just every hour or every day. The granularity can be specified, for example, to every two minutes or every three hours without having to put each individual entry in the crontab.

Lets say we wanted to run the previous command not at 10 minutes after the hour, but every ten minutes. We could make an entry that looked like this:

0,10,20,30,40,50 * 1,16 * 1-5 /usr/local/bin/command

This runs every 10 minutes: at the top of the hour (0 minutes after), 10 minutes after, 20 minutes after, and so on. Although this works, it could be done simpler by creating an entry like this:

*/10 * 1,16 * 1-5 /usr/local/bin/command

This syntax may be new to some administrators, but is still pretty straightforward. The slash (/) says that within the specific interval (in this case, every minute), run the command every so many “units”. In this example, the units are minutes, so we are saying to run the job every 10 minutes. Specifying it as “*/5” would say to run it every 5 minutes.

  1. login as the root user
  2. Issue the command
    crontab -l -u jimmo >/tmp/crontab.jimmo
  3. Edit the file /tmp/crontab.jimmo using vi.
  4. Append the following line to the bottom of the file:
    */10 * * * * /usr/local/bin/command
  5. Save the file and exit vi.
  6. Active the new cron file with the command:
    crontab -u jimmo /tmp/crontab.jimmo

Task:

Create a crontab entry to only run during normal work hours

We can combine both intervals and range.For example, if the job was to run every 10 minutes, but only between 8 AM and 5 PM, Monday through Friday, that is normal work hours. As we mentioned above, a range is specified by the start and end values separated by a dash, so from 8 AM to 5 PM, it would look like this:

8-17

Note that in this case are using a 24 hour clock, so 5 PM is 17.

  1. login as the root user
  2. Issue the command
    crontab -l -u jimmo >/tmp/crontab.jimmo
  3. Edit the file /tmp/crontab.jimmo using vi.
  4. Append the following line to the bottom of the file:
    */10 8-17 * * * 1-5 /usr/local/bin/command
  5. Save the file and exit vi.
  6. Active the new cron file with the command:
    crontab -u jimmo /tmp/crontab.jimmo

In this example, the job runs every ten minutes (*/10), but only from 8-17 (8 AM to 5 PM) and only on weekdays (day 1 to day 5)

What if you wanted it to run at these times, but only every three minutes? The line might look like this:

*/3 8-17 * * * 1-5 /usr/local/bin/command

One really nice thing that a lot of Linux dialects do is allow you to specify abbreviations for the days of the week and the months. Its a lot easier to remember that fri is for Friday instead of 5.

With the exception of certain errors in the time fields, errors are not reported until cron runs the command. All error messages and output is mailed to the users. At least that’s what the crontab man-page says and that is basically true. However, as you see in the previous examples, you are redirecting stdout to /dev/null. If you wanted to, you could also redirect stderr there and you would never see whether there were any errors.

Output is mailed to the user because there is no real terminal on which the cronjobs are being executed. Therefore, there is no screen to display the errors. Also, there is no keyboard to accept input. Does that mean you cannot give input to a cron job? No. Think back to the discussion on shell scripts. We can redefine stdin, stdout and stderr. This way they can all point to files and behave as we expect.

Keep in mind that cron is not exact nor does it run in real-time. It synchronizes itself to the top of each minute. On a busy system in which you lose clock ticks, jobs may not be executed until a couple minutes after the scheduled time. In addition, there may be other processes with higher priorities that delay cron jobs. In some cases, (particularly on very busy systems) jobs might end up being skipped if they are run every minute.

Add a cron job to the system-wide cron table

Cron provides two ways of starting regular jobs. One is on a per-user basis, where the users themselves get to manage their jobs. The second and newer method is the ability of the root user to add jobs to the global cron table that run as a specific user. In this task, you will learn how to add jobs to the global cron table /etc/crontab. This file provides a mechanism for the root user to run jobs as a different user without having to actually switch users (for example, using su). This file is typically only writable by root and in some cases, only root can read it (which is often necessary in high security environments).

Although root usually can run all of the necessary commands, you can use this mechanism when you don’t want to deal with changing permissions after root as run a command. For example, if your script creates a new file it will typically be owned by root and probably won’t be readable my most users. However, if you run the script as the appropriate user, permissions are typically not an issue.

The general syntax is the same as the standard crontabs, with a couple of exceptions. The first difference is the header, which you can see here in the default file:

SHELL=/bin/sh PATH=/usr/bin:/usr/sbin:/sbin:/bin:/usr/lib/news/bin MAILTO=root # # check scripts in cron.hourly, cron.daily, cron.weekly, and cron.monthly # 59 * * * * root rm -f /var/spool/cron/lastrun/cron.hourly 14 0 * * * root rm -f /var/spool/cron/lastrun/cron.daily 29 0 * * 6 root rm -f /var/spool/cron/lastrun/cron.weekly 44 0 1 * * root rm -f /var/spool/cron/lastrun/cron.monthly

The SHELL variable defines the shell under which each command will run. The PATH variable is like the normal PATH environment variable and defines the search path. The MAILTO variable says who should get email messages, which includes error messages and the standard output of the executed commands.

The structure of the actual entries is pretty much the same with the exception of the user name (root in each case here). This way, the root user (or whoever can edit /etc/crontab) can define which user executes the command. Keep in mind that this can be a big security hole. If someone can write to this file, they can create an entry that runs as root and therefore has complete control of the system.

We will assume that we want to start the script /usr/local/bin/backup.sh every day at exactly 4 AM as the user root.

  1. Login as as root
  2. Edit the file /etc/crontab
  3. Add the following line at the bottom of the file:
    0 4 * * * root /usr/local/bin/backup.sh
  4. Save the file and quit.

In this example, we specific the minutes (0) and the hour (4) and we used an asterisk (wildcard) for the remaining entries. This effectively means every day of every month. We could have also used an asterisk for any of the values, as needed. If we had wanted the script to only be started on Sundays, the entry might have looked like this:

0 4 * * 0 root /usr/local/bin/backup.sh

Or, if we had wanted another script to be started once a year, it might look like this:

0 4 1 1 * root /usr/local/bin/backup.sh

If both the day of the month and day of the week are specified, cron will run the job when either fields matches. For example, specifying “0 4 1 * 1” would mean the first of every month and every Monday.

Note that we logged in as root in order to edit the/etc/crontab file, but that put no restrictions on the user we specified in the fifth field. Instead we could have specified a different users. This mechanisms allows you to run jobs as specific users without giving that user privileges to start cron-jobs on their own. That is the goal of the next task.

Note that cron actually starts a shell before executing the respective command. As a result, basically anything applies that would apply to any command issue from within a shell. For example, you can redirect stdin, stdout, and stderr. For example, to redirect stderr for the previous command, it might look like this

0 4 * * 0 root /usr/local/bin/backup.sh 2>/tmp/backup.errors

One thing I would like to point out is that I do not advocate doing redirection in the command field of the crontab. I personally like doing as little there as possible. Instead, I put the absolute path to a shell script. I can then test the crontab entry with something simple. Once that works, I can make changes to the shell script without having to resubmit the cronjob.

Task:

Allow the user jimmo to create cron entries

Access is permitted to the cron facility through two files, both in /etc. If you have a file cron.allow, you can specify which users are allowed to use cron. The cron.deny says who are specifically not allowed to use cron. If neither file exists, only the system users have access. However, if you want everyone to have access, create an entry cron.deny file. In other words, no one is denied access.

  1. login as the root user
  2. Edit the file /etc/cron.allow
  3. Append the following line to the bottom of the file:
    jimmo
  4. Save the file and exit vi.

If you have experience with other aspects of Linux, this mechanism may appear familiar, where the file SERVICE.allow gives you permissions to access the respective service and SERVICE.deny prevents you from accessing it. So, to disable access to cron, you could issue the command:

echo ALL >>/etc/cron.deny

Note that if neither of this files exist, then all users can access cron. Also, should cron.allow exist, only the users listed will have access. This is essentially the same as having “ALL” in cron.deny

Some Linux distributions store the files in /var/spool/cron. So, to disallow access you would edit the file /var/spool/cron/deny.