There are several ways to schedule tasks on a GNU/Linux system.
Note: If you are not familiar with the GNU/Linux command line interface, review the Conventions page before proceeding.
systemd Timers
The systemd initialization system offers timer and service units to schedule tasks. In a .timer
unit, a job schedule is defined and the service unit that should be activated is set. The .service
unit is used to set the actual command to be executed.
A systemd timer can be:
- Monotonic
- Realtime
Monotonic Timers
A monotonic timer schedules execution of a task a specific amount of time after a predefined event takes place. There are several keywords that can be used in the [Timer]
section of the .timer
unit to delineate this time.
OnActiveSec
- Relatively schedule the task to the time when the timer unit itself is activated.
OnBootSec
- Relatively schedule the task to the system boot time.
OnStartupSec
- Relatively schedule the task to the time when systemd started.
OnUnitActiveSec
- Relatively schedule the task to the last time the service unit was active.
OnUnitInactiveSec
- Relatively schedule the task to the last time the service unit was inactive.
Seconds are used as the default unit of time. However, you can specify a different time unit after the numerical value (e.g., 10m
for ten minutes).
Realtime Timers
A realtime timer schedules execution of a task in absolute terms. The OnCalendar
keyword and specific allowed time encodings are used to delineate this time.
A list of values can be given as a comma-separated list (e.g., 09:00:00,15:00:00
). A range of values can be specified using ..
(e.g., Monday..Friday
). A *
character is a wildcard and matches any value.
ddd hh:mm:ss
- The task will be executed each
ddd
athh:mm:ss
, e.g.,Mon 09:00:00
. ddd..ddd *-MM-dd
- The task will be executed the
dd
ofMM
of each year, but only on days fromddd
toddd
, e.g.,Mon..Fri *-06-01
. yyyy-MM-dd
- The task will be executed on the
dd
ofMM
of the yearyyyy
at00:00:00
, e.g.,2020-06-01
. ddd,ddd yyyy-*-dd,dd hh:mm:ss
- The task will be executed at
hh:mm:ss
of thedd
anddd
day of each month of the yearyyyy
, but only if the day isddd
orddd
, e.g.,Mon,Tue 2020-*-03,09 09:25:00
. *:mm/mm
- The task will be executed every
mm
minutes starting from minutemm
, e.g.,*:00/04
. hh/hh
- The task will be executed every
hh
hours starting fromhh
, e.g.,19/01
. minutely
- The task will be executed at the beginning of each minute.
hourly
- The task will be executed at the beginning of each hour.
daily
- The task will be executed each day at
00:00:00
. monthly
- The task will be executed the first day of each month at
00:00:00
.
If provided, weekdays must be in English, either as the abbreviated (e.g., Sun
) or complete form (Sunday
).
Run man 7 systemd.time
for more information.
Viewing Active Timers
Active .timer
units can be viewed with the following command:
systemctl list-timers
The output includes six columns:
- NEXT The next time the timer will run.
- LEFT How much time before the timer will run again.
- LAST The last time the timer ran.
- PASSED How much time has passed since the last time the timer ran.
- UNIT The
.timer
unit in which the schedule is set. - ACTIVATES The
.service
unit activated by the timer.
Example
Assume there is a script in our user's Script
directory called fortune.bash
. We want this script to run daily.
#!/usr/bin/env bash
# Author: siliconsoul.org
# Purpose: Print out today's fortune.
fortune > "${HOME}/fortune.txt"
exit 0 # Exit script with successful exit status
The script is simple. It prints out a fortune using the eponymous fortune
command and directs the command's output to a fortune.txt
file in the user's home directory (fortune
is likely not installed on your GNU/Linux distribution, but should be available in the distribution's repository, e.g., # apt install fortune
).
For this example, the job in question only needs to be run for a single user, so it is advantageous for that user to be able to create and manage the job without requiring root privileges. systemd offers this option via user services.
First, make sure that a ${HOME}/.config/systemd/user/
directory exists on your system. If it does not, create it with the following command:
mkdir -p "${HOME}/.config/systemd/user/"
Next, create the .timer
file:
$ {
echo '[Unit]'
echo -e 'Description=Announce daily fortune\n'
echo '[Timer]'
echo 'OnCalendar=daily'
echo -e 'Persistent=true\n'
echo '[Install]'
echo 'WantedBy=timers.target'
} > "${HOME}/.config/systemd/user/fortune.timer"
- The
Description=
option specifies a human readable name for the unit. OnCalendar
defines realtime timers with calendar event expressions (in this case, to operate daily).Persistent=
takes a boolean argument. If true, the time when the service unit was last triggered is stored on disk. When the timer is activated, the service unit is immediately triggered if it would have been triggered at least once during the time when the timer was inactive. This helps to catch up on missed runs of a service when the computer was off.WantedBy=
ensures symbolic links are created in the.wants/
or.requires/
directory of each of the listed units when this unit is installed bysystemctl enable
. This has the effect that a dependency of typeWants=
orRequires=
is added from the listed unit to the current unit. The result is that the current unit will be started when the listed unit is started (i.e.,fortune.timer
is started whentimers.target
is started).
Then, create the corresponding fortune.service
file:
$ {
echo '[Unit]'
echo -e 'Description=Run fortune.bash script\n'
echo '[Service]'
echo "ExecStart=/home/${LOGNAME}/Scripts/fortune.bash"
} > "${HOME}/.config/systemd/user/fortune.service"
ExecStart=
contains commands with their arguments that are executed when this service is started. UnlessType=
is set tooneshot
, exactly one command must be given. IfExecStart=
is specified, but neitherType=
norBusName=
is specified, the default type is automatically set tosimple
(i.e., systemd will consider the unit immediately started after the main service process has been forked off).
Enable and start the fortune.timer
unit:
$ systemctl --user enable --now fortune.timer
Created symlink /home/amnesia/.config/systemd/user/timers.target.wants/fortune.timer → /home/amnesia/.config/systemd/user/fortune.timer.
Confirm that systemd sees the active timer:
$ systemctl --user list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2020-09-02 00:00:00 PDT 15h left n/a n/a fortune.timer fortune.service
1 timers listed.
Pass --all to see loaded but inactive timers, too.
Check the statuses of the new units:
$ systemctl --user status fortune.timer fortune.service
● fortune.timer - Announce daily fortune
Loaded: loaded (/home/amnesia/.config/systemd/user/fortune.timer; enabled; vendor preset: en
Active: active (waiting) since Tue 2020-09-01 08:51:38 PDT; 1min 36s ago
Trigger: Wed 2020-09-02 00:00:00 PDT; 15h left
● fortune.service - Run fortune.bash script
Loaded: loaded (/home/amnesia/.config/systemd/user/fortune.service; static; vendor preset: e
Active: inactive (dead)
Now, a new fortune should be placed in /home/amnesia/fortune.txt
every day at 00:00:00
.
If you change the fortune.timer
file to move to hourly fortunes, make sure to run the systemctl --user daemon-reload
command, so that systemd reloads its unit files.
systemd Timer Script
The steps required to manually create a systemd timer can be onerous. A Bash script can automate this process.
First, the script ensures that the systemd user unit directory exists. If it does not, it creates it.
# Create systemd user unit directory, and parent directories, if they do not exist
if ! "${HOME}/.config/systemd/user" > '/dev/null' 2>&1; then
mkdir -p "${HOME}/.config/systemd/user"
fi
Afterwards, the script outputs a link to a useful systemd directives reference and collects the information needed from the user to create basic timer/service unit files:
# Output systemd directives reference
echo -e '\nsystemd Directives Reference\nhttps://www.freedesktop.org/software/systemd/man/systemd.directives.html'
# Get unit name
echo ''
read -p 'Enter unit name: ' -r unit_name
unit_name="$(clean_name "${unit_name}")" # Sanitize input
# Gather .timer unit description
read -p 'Enter .timer description: ' -r timer_description
timer_description="${timer_description//[^a-zA-Z0-9 ,]/}" # Sanitize input
# Gather .timer unit directive
read -p 'Enter .timer directive: ' -r timer_directive
timer_directive="${timer_directive//[^a-zA-Z0-9 =]/}" # Sanitize input
# Gather .service unit description
read -p 'Enter .service description: ' -r service_description
# Sanitize input
service_description="${service_description//[^a-zA-Z0-9 ,]/}"
# Gather .service unit command
read -p 'Enter .service command: ' -r service_command
Then, two different functions are called that contain the commands that actually create the .timer
and .service
files in the appropriate location:
timer_matter # Create .timer unit
service_matter # Create .service unit
After the unit files are created, the user is given the option to edit the unit files in their chosen unit editor:
while true; do
# Present option to open units in editor
echo ''
read -p 'Open units in editor? (y or n): ' -r edit
if [[ "${edit}" == 'y' || "${edit}" == 'n' ]]; then
break
elif [[ "${edit}" == 'q' ]]; then
exit 0
else
err_msg='\nInvalid input. Try again or enter q to quit.'
echo -e "${err_msg}" 1>&2
fi
done
Finally, the .timer
unit is enabled and activated, and the units' status is output:
# Enable and start units
systemctl --user enable --now "${unit_name}.timer"
# Check units' status
systemctl --user status "${unit_name}.timer" "${unit_name}.service"
The full script is available below:
cron
The most well-known way of scheduling tasks on a GNU/Linux system is via the cron system. The systemd initialization system does support cron, as well as the .timer
unit method.
cron jobs are run by crond
. The cron
daemon's purpose is to execute jobs at periodic intervals. crond
should be automatically started during system boot. No action is required on your part.
crond
reads its task lists once on starting and then keeps them in memory. However, the daemon checks every minute whether any crontab
files have changed. The mtime
(i.e., the last modification time) is used for this. If crond
notices a modification, the task list is automatically reconstructed. In this case, no explicit restart of the daemon is required.
cron jobs are stored in a variety of crontab (cron table) files.
The basic syntax for the crontab
command is:
crontab ex_file
A new crontab will be installed from ex_file
. Alternatively, a new crontab can be installed from the standard input by passing a -
as an argument to crontab
instead of ex_file
.
You can remove your user's crontab file with the -r
option:
crontab -r
User crontabs
User crontabs are stored in either /var/spool/cron/crontabs/
(Debian) or /var/spool/cron/
(Fedora). These files are managed with the crontab
command.
For example, you can view your user's crontab file with the crontab -l
command. If you have root access, you can view any user's crontab file like so:
# crontab -l -u ex_username
To edit your crontab file, use the crontab -e
command. As with crontab -l
, if you have root access, you can edit any user's crontab file:
# crontab -e -u ex_username
Syntax
A crontab line consists of six columns (fields):
- Minute (
0
-59
) - Hour in 24 hour time (
0
-23
) - Day of month (
1
-31
) - Month (
1
-12
, or names, e.g.,dec
) - Day of week (
0
-7
, with0
and7
being Sunday, or names, e.g.,sun
) - Command
To match all values for a column, you can use an asterisk (*
).
This is an example crontab line that runs a shell script at 20:30 every Saturday:
30 20 * * 6 '/home/amnesia/Scripts/export.sh'
Multiple times (e.g., minutes, hours, or months) can be specified by separating the items with a comma (,
), a range can be specified with a hyphen (-
), and both of these methods can be combined. Ranges and lists of names for months and weekdays are not permissible.
You can specify that a job run every N
units of time by creating a step with the forward slash (/
), e.g., */2
.
If you specify both the day of the month and the day of the week, the job will run with either match. Otherwise, all columns must match up for the job to run.
The month and day of week names can be spelled out using the first three letters of each value (e.g., dec
, sun
). The whole does not need to be consistent. Numbers or names can be used at your convenience.
The command you run can be any valid shell code. Percent signs (%
) within the command must be escaped \%
or they will be converted into newline characters. In that case, the command will be considered to extend up to the first (unescaped) percent sign. The following lines will be fed to the command as its standard input.
If you do not want a cron command execution to be logged, you can suppress this by putting a -
as the first character of the line. The final line of a crontab file must end in a newline character. Otherwise, it will be ignored.
Environment
cron has its own minimal environment. The cron environment can be set at the top of the crontab file, just like you would do in any other configuration file.
The following variables are automatically pre-set:
HOME
- The home directory is taken from
/etc/passwd
. Changing its value is allowed. LOGNAME
- The user name is taken from
/etc/passwd
and cannot be changed. MAILTO
crond
sends email containing command output to this address (by default, they go to the owner of the crontab file). Ifcrond
should send no message at all, the variable must be set to a null value, i.e.,MAILTO=''
.
CRON_TZ
is a special crontab environment variable. It used to set an alternate time zone used for the crontab. By default, system time is used.
Anything a cron job prints to the screen is sent to the current user in an email. This can be overridden with the MAILTO
variable.
Nicknames
A crontab nickname replaces the five time columns in a crontab line.
@annually
- Same as
@yearly
. @daily
- Run once a day at midnight,
0 0 * * *
. @hourly
- Run once an hour, on the hour,
0 * * * *
. @midnight
- Same as
@daily
. @monthly
- Run at midnight on the first of the month,
0 0 1 * *
. @reboot
- Run once, at startup.
@weekly
- Run once a week on Sunday at midnight,
0 0 * * 0
. @yearly
- Run once a year at midnight on January 1st,
0 0 1 1 *
.
System crontabs
The system crontab is located at /etc/crontab
.
Package crontabs that are installed when you install packages are located in the /etc/cron.d/
directory.
All of these crontab files are considered system crontabs. These crontabs have an extra column before the command column that indicates which user should be used to execute the cron job.
Convenience crontabs
The convenience crontab directories are located in the /etc/
directory. They are:
/etc/cron.daily/
/etc/cron.hourly/
/etc/cron.monthly/
/etc/cron.weekly/
Files placed into these directories are normally scripts and they do not necessarily run on a predictable schedule (i.e., you cannot know exactly when a script will run within the directories' allotted timeframe). Most of the files in these directories are placed there by installation scripts.
Access Control
The users that can use cron jobs can be restricted by using either the /etc/cron.allow
or /etc/cron.deny
file. If /etc/cron.allow
exists, only users in the file and the root user can use the crontab
command. If /etc/cron.deny
exists, users in the file cannot use the crontab
command.
If both the /etc/cron.allow
and /etc/cron.deny
files exist, /etc/cron.allow
takes precedence. However, you should only use one of these files.
If neither file exists, the GNU/Linux distribution's default settings will determine whether or not a user can use the crontab
command.
anacron
anacron is a simplified cron that is meant to handle jobs that run daily or less frequently (e.g., weekly, monthly), and jobs for which the precise time does not matter. anacron runs jobs that were scheduled to run when the system was off.
At most, anacron is activated once a day. For anacron, the time of day is irrelevant. Instead, it focuses on when the last time a particular job was run.
There are no commands to edit anacron jobs analogous to crontab -e
in the cron system. All jobs are run as the root user and only the system administrator can handle anacron job management.
The anacron table is located at /etc/anacrontab
. If you need to edit this file, use the sudoedit
command:
sudoedit '/etc/anacrontab'
anacron can run as a daemon, but it is typically run from cron itself to check if there are any outstanding jobs. Once it is done processing, anacron exits.
How anacron runs its jobs can be modified with the anacron
command. To manually run an anacron job, pass its job identifier to the anacron
command as an argument:
# anacron ex_job_identifier...
ex_job_identifier
can be a shell search pattern. This can be used to launch groups of jobs with a single anacron invocation. Not specifying any job names at all is equivalent to the job name *
.
Normally, anacron executes jobs independently and without attention to overlaps. # anacron -s
serializes the execution of jobs, such that anacron starts a new job only when the previous one is finished.
Unlike cron, anacron is not a background service, but is launched when the system is booted in order to execute any leftover jobs. Later on, you can execute anacron once a day from cron in order to ensure that it completes its tasks, even if the system is running for a longer period of time than normally expected.
GNU/Linux systems ensure that anacron does nothing while cron is active. For example, /etc/cron.daily/
contains a script called 0anacron
, which is executed as the first job.
This script invokes anacron -u
, which causes anacron to update the time stamps of jobs without actually executing any jobs (which is the next thing that cron will accomplish). When the system is restarted, this will prevent anacron from running jobs unnecessarily, at least if the reboot occurs after cron has completed its tasks.
Syntax
An anacron line consists of four columns (fields):
- Period, in days, between runs of this job. Some
@nicknames
are available (e.g.,@annually
,@daily
,@monthly
,@weekly
,@yearly
). - Delay, in minutes, that anacron will wait before executing the job.
- A tag, called a Job Identifier, for the the job that is unique. anacron uses this to identify the job in log messages and as the name of the file in which
anacron
logs the time the job was last executed. It can contain any characters, except white space and the forward slash. - The command to run.
Long lines can be wrapped with a \
at the end of the line.
Environment
The anacron environment can be set just like in a normal crontab file. Variable definitions are valid until the end of the file or until the same variable is redefined.
Some environment variables have a special meaning to anacron
. For example, with RANDOM_DELAY
, you can specify an additional random delay for the job launches. When you set the variable to a number t
, a random number of minutes between 0
and t
will be added to the delay given in the job description.
START_HOURS_RANGE
lets you denote a range of hours (on a clock) during which jobs will be started (e.g., START_HOURS_RANGE=11-12
allows new jobs to be started only between 11:00 and 12:00).
Like cron, anacron sends job output to the address given by the MAILTO
variable. Otherwise, it sends job output to the user executing anacron (usually the root user).
at and batch
The at
service is used to execute shell commands one time at a point in the future (versus cron, which is preferable for commands that need to be repeatedly executed). The format of the command is:
at ex_time_specification...
After entering the above, you will be presented with an at>
prompt where you can enter the shell command to schedule. To exit the at>
prompt and schedule the job, press Ctrl+d. Afterwards, you will a see a line that echoes the job's scheduling information.
The at
command also accepts input via a pipe or redirection:
echo ex_command | at ex_time_specification...
at ex_time_specification... < ex_file.txt
at
can read commands from a file using the -f ex_file
option, as well.
A summary of your user's at
queue can be shown with either of the following commands:
at -l
atq
An a
in the output denotes a job class, i.e., a letter between a
and z
. You can specify a job class using at
's -q ex_queue
option. Jobs in classes with later letters are executed with a higher nice
value.
The default is a
for at
jobs and b
for batch
jobs. A job that is currently being executed belongs to the special job class =
.
To show a summary of all users' at
queues, you can use one of the following commands:
# at -l
# atq
The commands that make up a job can be verified with the -c
option:
at -c ex_job_number...
An at
job can be deleted with the following commands:
at -d ex_job_number...
atrm ex_job_number...
The batch
command is similar to at
, but makes it possible to execute a command as soon as possible. When that will be depends on the system load, i.e., if the system is busy, batch jobs will need to wait.
An at
-style time specification on batch
is allowed, but not required. If it is given, the commands will be executed some time after the specified time, just as if they had been submitted using the batch
command at that time.
batch
is not suitable for environments in which users compete for resources (e.g., like CPU time).
To schedule a one-time job once the system's 1 minute load average is less than or equal to the system default value of 0.8
(i.e., in the last minute, 80% of processes or less were waiting to be run), or whatever limiting load factor was specified by atd
, you can do:
echo ex_command | batch
at
job execution is controlled by a daemon called atd
. Generally, it is started on system boot and waits in the background for work.
When starting atd
, several options can be specified:
-b
(batch)- Determines the minimum interval between two
batch
job executions. The default is60
seconds. -d
(debug)- Activates debug mode, i.e., error messages will not be passed to the logging system, but written to standard error output.
-l ex_integer
(load)- Determines a limit for the system load, above which
batch
jobs will not be executed. The default is0.8
. - For a system with
N
processors, you wantex_integer
to be slightly less thanN
, e.g., 80% ofN
.ex_integer
is a load average value, i.e., a limiting load factor number over whichbatch
jobs will not be run.
To immediately run all jobs queued by at
, use the atd
command.
The atd
daemon requires the following directories, in which at
and batch
job files are located:
/var/spool/cron/atjobs/
(Debian) or/var/spool/at/
(Fedora). Their access mode should be700
and their owner should beat
./var/spool/atspool/
. This directory serves to buffer job output. Its access mode should be700
and its owner should also beat
.
at
job files are prefixed with an a
. batch
job files are prefixed with a b
.
Specifiers
Specifiers are what are used to specify the time that at
jobs should run.
date
- You can specify any number of
minutes
,hours
,days
, andweeks
from the current time, e.g.,now
,today
,tomorrow
,now +30 minutes
. midnight
- Run the task at
00:00
on the current day. noon
- Run the task at
12:00
on the current day. teatime
- Run the task at
16:00
. time-of-day
- Such as
1:00 PM
,6:00 AM
, or13:00 090820
,13:00 09/08/20
, or13:00 09.08.20
.
If you just specify a date (e.g., 09/08/20
), commands will be executed on the day in question at the current time. If giving both a date a time, the date must come after the time.
at
supports the units minutes
, hours
, days
, and weeks
. When using offsets, a single offset by one single measurement unit must suffice, i.e., the following combinations are not allowed, at noon + 2 hours 30 minutes
or at noon + 2 hours + 30 minutes
.
Environment
at
tries to run the commands in an environment that is as like the one when at
was called as possible. The current working directory, the umask, and the current environment variables (except _
, DISPLAY
, and TERM
) are saved and reactivated before the commands are executed. Output of the commands executed by at
(i.e., standard output and standard error) is sent to you via your user's email address.
Both at
and batch
run from the same environment that existed at the time the job was submitted (i.e., all variables, aliases, and functions in the shell are available to the job that was started in the shell). This is distinct from cron and anacron, which have their own minimal environments.
If you are running the at
command as another user (e.g., through use of the su
command), the commands will be executed using that identity. However, the output mail messages will still be sent to your account's email address.
Access Controls
at
and batch
have the same type of access control as cron does. The at
and batch
access control files are /etc/at.allow
and /etc/at.deny
. If neither file exists, at
and batch
are only available to the root user.
sleep
The sleep
command is used to delay a command from running for a specified period of time.
sleep ex_integer_ex_suffix...
ex_suffix
can be:
d
for daysh
for hoursm
for minutes
If no suffix is specified, seconds is assumed.
If two or more arguments are provided, sleep
pauses for the amount of time specified by the sum of the argument values.
Documentation
For more on systemd timers and related resources, run the following commands:
man 1 systemd
- Introduction to basic concepts and functionality of systemd.
man 1 systemctl
- Command that controls the systemd system and service manager.
man 5 systemd.unit
- Unit configuration information.
man 5 systemd.timer
- Timer unit configuration.
man 5 systemd.service
- Service unit configuration.
The ArchWiki also has a nice reference on systemd user services.
For more on cron
, anacron
, at
, and batch
, examine the Linux User's Manual, either at the command line or online.