Beginners Guide to Scheduling Jobs in Linux Using cron and systemd timer

Jobs scheduled to run repeatedly are called recurring jobs. Most Linux systems ship with the crond daemon, provided by the cronie package, enabled and started by default specifically for recurring jobs. The crond daemon reads multiple configuration files: one per user (edited with the crontab command), and a set of system-wide files. These configuration files give users and administrators fine-grained control over when their recurring jobs should be executed.

If a scheduled command produces any output or error that is not redirected, the crond daemon attempts to email that output or error to the user who owns that job (unless overridden) using the mail server configured on the system. Depending on the environment, this may need an additional configuration. The output or error of the scheduled command can be redirected to different files.

Scheduling Recurring User Jobs

Normal users can use the crontab command to manage their jobs. This command can be called in four different ways:

Crontab Examples

crontab -l List the jobs for the current user.
crontab -r Remove all jobs for the current user.
crontab -e Edit jobs for the current user.
crontab filename Remove all jobs, and replace with the jobs read from filename. If no file is specified, stdin is used.

Describing user job format

The crontab -e command invokes vim by default, unless the EDITOR environment variable has been set to something different. Enter one job per line. Other valid entries include: empty lines, typically for ease of reading; comments, identified by lines starting with the number sign (#); and environment variables using the format NAME=value, which affects all lines below the line where they are declared. Common variable settings include the SHELL variable, which declares which shell to use to interpret the remaining lines of the crontab file; and the MAILTO variable, which determines who should receive any emailed output.

Fields in the crontab file appear in the following order:

  • Minutes
  • Hours
  • Day of month
  • Month
  • Day of week
  • Command
15 12 15 * Fri command

The first five fields all use the same syntax rules:

  • * for “Do not Care”/always.
  • A number to specify a number of minutes or hours, a date, or a weekday. For weekdays, 0 equals Sunday, 1 equals Monday, 2 equals Tuesday, and so on. 7 also equals Sunday.
  • x-y for a range, x to y inclusive.
  • x,y for lists. Lists can include ranges as well, for example, 5,10-13,17 in the Minutes column to indicate that a job should run at 5, 10, 11, 12, 13, and 17 minutes past the hour.
  • */x to indicate an interval of x, for example, */7 in the Minutes column runs a job every seven minutes.

Additionally, 3-letter English abbreviations can be used for both months and weekdays, for example, Jan, Feb, and Mon, Tue.

The last field contains the command to execute using the default shell. The SHELL environment variable can used to change the shell for the scheduled command. If the command contains an unescaped percentage sign (%), then that percentage sign is treated as a newline character, and everything after the percentage sign is passed to the command on stdin.

Example Recurring User Jobs

This section describes some examples of recurring jobs.

1. The following job executes the command /usr/local/bin/yearly_backup at exactly 9 a.m. on February 2nd, every year.

0 9 2 2 * /usr/local/bin/yearly_backup

2. The following job sends an email containing the word Chime to the owner of this job, every five minutes between 9 a.m. and 5 p.m., on every Friday in July.

*/5 9-16 * Jul 5 echo "Chime"

The preceding 9-16 range of hours means that the job timer starts at the ninth hour (09:00) and continues until the end of the sixteenth hour (16:59). The job starts executing at 09:00 with the last execution at 16:55 because five minutes from 16:55 is 17:00 which is beyond the given scope of hours.

3. The following job runs the command /usr/local/bin/daily_report every weekday at two minutes before midnight.

58 23 * * 1-5 /usr/local/bin/daily_report

4. The following job executes the mutt command to send the mail message Checking in to the recipient on every workday (Monday to Friday), at 9 a.m.

0 9 * * 1-5 mutt -s "Checking in" [email protected] % Hi there boss, just checking in.

Describing recurring system jobs

System administrators often need to run recurring jobs. Best practice is to run these jobs from system accounts rather than from user accounts. That is, do not schedule to run these jobs using the crontab command, but instead use system-wide crontab files. Job entries in the system-wide crontab files are similar to those of the users’ crontab entries, excepting only that the system-wide crontab files have an extra field before the command field; the user under whose authority the command should run.

The /etc/crontab file has a useful syntax diagram in the included comments.

# For details see man 4 crontabs
# 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 ...
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed

Recurring system jobs are defined in two locations: the /etc/crontab file, and files within the /etc/cron.d/ directory. You should always create your custom **crontab **files under the **/etc/ cron.d **directory to schedule recurring system jobs. Place the custom crontab file in /etc/cron.d to protect it from being overwritten if any package update occurs to the provider of /etc/crontab, which may overwrite the existing contents in /etc/crontab. Packages that require recurring system jobs place their **crontab **files in /etc/cron.d/ containing the job entries. Administrators also use this location to group related jobs into a single file.

The crontab system also includes repositories for scripts that need to run every hour, day, week, and month. These repositories are directories called /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, and /etc/cron.monthly/. Again, these directories contain executable shell scripts, not crontab files.

A command called run-parts called from the /etc/cron.d/0hourly file runs the /etc/cron.hourly/* scripts. The run-parts command also runs the daily, weekly, and monthly jobs, but it is called from a different configuration file called /etc/anacrontab.

The purpose of /etc/anacrontab is to make sure that important jobs always run, and not skipped accidentally because the system was turned off or hibernating when the job should have been executed. For example, if a system job that runs daily was not executed last time it was due because the system was rebooting, the job is executed when the system becomes ready. However, there may be a delay of several minutes in starting the job depending on the value of the Delay in minutes parameter specified for the job in /etc/anacrontab.

There are different files in **/var/spool/anacron/ **for each of the daily, weekly, and monthly jobs to determine if a particular job has run. When crond starts a job from /etc/anacrontab, it updates the time stamps of those files. The same time stamp is used to determine when a job was last run. The syntax of **/etc/anacrontab **is different from the regular crontab configuration files. It contains exactly four fields per line, as follows.

Period in days

The interval in days for the job that runs on a repeating schedule. This field accepts an integer or a macro as its value. For example, the macro @daily is equivalent to the integer 1, which means that the job is executed on a daily basis. Similarly, the macro @weekly is equivalent to the integer 7, which means that the job is executed on a weekly basis.

Delay in minutes

The amount of time the crond daemon should wait before starting this job.

Job identifier

The unique name the job is identified as in the log messages.


The command to be executed.

The /etc/anacrontab file also contains environment variable declarations using the syntax NAME=value. Of special interest is the variable START_HOURS_RANGE, which specifies the time interval for the jobs to run. Jobs are not started outside of this range. If on a particular day, a job does not run within this time interval, the job has to wait until the next day for execution.

Introuducing systemd timer

With the advent of systemd in CentOS/RHEL 7, a new scheduling function is now available: systemd timer units. A systemd timer unit activates another unit of a different type (such as a service) whose unit name matches the timer unit name. The timer unit allows timerbased activation of other units. For easier debugging, systemd logs timer events in system journals.

Sample Timer Unit

The sysstat package provides a systemd timer unit called sysstat-collect.timer to collect system statistics every 10 minutes. The following output shows the configuration lines of /usr/lib/systemd/system/sysstat-collect.timer.

...output omitted...
Description=Run system activity accounting tool every 10 minutes



The parameter OnCalendar=*:00/10 signifies that this timer unit activates the corresponding unit (sysstat-collect.service) every 10 minutes. However, you can specify more complex time intervals. For example, a value of 2019-03-* 12:35,37,39:16 against the OnCalendar parameter causes the timer unit to activate the corresponding service unit at 12:35:16, 12:37:16, and 12:39:16 every day throughout the entire month of March, 2019. You can also specify relative timers using parameters such as OnUnitActiveSec. For example, the OnUnitActiveSec=15min option causes the timer unit to trigger the corresponding unit 15 minutes after the last time the timer unit activated its corresponding unit.

After you change the timer unit configuration file, use the systemctl daemon-reload command to ensure that systemd is aware of the changes. This command reloads the systemd manager configuration.

[root@host ~]# systemctl daemon-reload

After you reload the systemd manager configuration, use the following systemctl command to activate the timer unit.

[root@host ~]# systemctl enable --now [unitname].timer