Skip to main content

Cron Jobs on Linux - Comprehensive Guide with Examples

In this article, I'll use Ubuntu 22.04 (Debian-derivative) and rockyOS 9.2 (RHEL-derivative) as references. If it is not mentioned, commands are the same for both systems.

Basics #

Cron jobs are scheduled and automated tasks that run commands or scripts on Linux. Common use cases are backups, updates, health checks, and so on. Those tasks can be run as sudo or user context.

Cron is the daemon that runs in the background. The running service is called cron and crond on Ubuntu and rockyOS, respectively.

Make sure the daemon is running #

Make sure that the service is running:

Ubuntu / Debian

sudo systemctl status cron or ps aux | grep cron

rockyOS / RHEL

sudo systemctl status crond or ps aux | grep crond

Show cron jobs #

Before we start, there are several places where you can look for cron jobs.

  1. via the crontab command, as described in the following section Cron tables are saved in /var/spool/cron/crontabs/* in Ubuntu and /var/spool/cron in rockyOS.
  2. in the system-wide /etc/crontab or /etc/cron.d/* configuration files
  3. as script or executable in one of the following directories: /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
List existing cron jobs of crontab:
crontab -l # cron jobs of current user
sudo crontab -l # cron jobs of root
sudo crontab -u USERNAME -l # cron jobs of specific user

Check all cron jobs of all users on a machine:

So, there are multiple ways to do so. I'll show you my preferred way that should cover all cron jobs.

Ubuntu / Debian

root@test-ubu-01:~# grep "^[^#;]" /var/spool/cron/crontabs/* /etc/crontab /etc/cron.d/*
/etc/crontab:SHELL=/bin/sh
/etc/crontab:17 *       * * *   root    cd / && run-parts --report /etc/cron.hourly
/etc/crontab:25 6       * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
/etc/crontab:47 6       * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
/etc/crontab:52 6       1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
/etc/cron.d/e2scrub_all:30 3 * * 0 root test -e /run/systemd/system || SERVICE_MODE=1 /usr/lib/x86_64-linux-gnu/e2fsprogs/e2scrub_all_cron
/etc/cron.d/e2scrub_all:10 3 * * * root test -e /run/systemd/system || SERVICE_MODE=1 /sbin/e2scrub_all -A -r
root@test-ubu-01:~# ls /etc/cron.*
/etc/cron.d:
e2scrub_all

/etc/cron.daily:
apport  apt-compat  dpkg  logrotate  man-db

/etc/cron.hourly:

/etc/cron.monthly:

/etc/cron.weekly:
man-db

rockyOS / RHEL

[root@test-rocky-01 ~]# grep "^[^#;]" /var/spool/cron/* /etc/crontab /etc/cron.d/*
/var/spool/cron/remotesuser:* * * * * echo "hello world as a random ass user
/var/spool/cron/root:* * * * * echo "hello world"
/etc/crontab:SHELL=/bin/bash
/etc/crontab:PATH=/sbin:/bin:/usr/sbin:/usr/bin
/etc/crontab:MAILTO=root
/etc/crontab:* * * * * remotesuser whoami >> /home/remotesuser/logy.logs
/etc/cron.d/0hourly:SHELL=/bin/bash
/etc/cron.d/0hourly:PATH=/sbin:/bin:/usr/sbin:/usr/bin
/etc/cron.d/0hourly:MAILTO=root
/etc/cron.d/0hourly:01 * * * * root run-parts /etc/cron.hourly
/etc/cron.d/cronywhat:SHELL=/bin/bash
/etc/cron.d/cronywhat:PATH=/sbin:/bin:/usr/sbin:/usr/bin
/etc/cron.d/cronywhat:MAILTO=root
/etc/cron.d/cronywhat:* * * * * root whoami >> /home/remotesuser/logy.logs
[root@test-rocky-01 ~]# 
[root@test-rocky-01 etc]# ls /etc/cron.*
/etc/cron.deny

/etc/cron.d:
0hourly

/etc/cron.daily:

/etc/cron.hourly:
0anacron

/etc/cron.monthly:

/etc/cron.weekly:
Add and edit cron jobs #

Side note: crontab will ask you what editor you want to use to edit the file.

Edit cron jobs with crontab user-specific:
crontab -e # edit cron jobs of current user
sudo crontab -e # edit cron jobs of root
sudo crontab -u USERNAME -e # edit cron jobs of specific user

The default syntax is * * * * * command. A detailed description and examples follow in the following section.


A second method would be to add the cron job to the system-wide /etc/cronjob file or create a new file in the /etc/cron.d/ directory. The latter is recommended as the /etc/cronjob is at risk of getting overwritten by an update.

The default syntax is slightly different as it adds the user name on the sixth position * * * * * user command.


Another way to run scripts is to use the /etc/cron.* directories. Save a script in one of the following directories to run it directly as root and system-wide and the schedule you want:

/etc/cron.hourly/
/etc/cron.daily/
/etc/cron.weekly/
/etc/cron.monthly/

Side note: /etc/cron.yearly/ / /etc/cron.annually/ are not there per default but can be added. I have not looked into it.


Important: Make sure that the script is executable: sudo chmod +x script.sh

Remove cron job #

You can either delete single cron jobs with crontab -e or all cron jobs with the following commands:

Removing ALL cron jobs with crontab -r:
crontab -r # removes all cron jobs of current user WITHOUT a prompt
crontab -r -i # -i adds a yes/no prompt before removing all cron jobs
sudo crontab -r # removes all cron jobs of root
sudo crontab -u USERNAME -r # removes all cron jobs of specific user

Example:

[root@test-rocky-01 ~]# crontab -u remotesuser -r -i
crontab: really delete remotesuser's crontab? 

Cron Expressions with Examples #

Side note: I am going to use the crontab command syntax for further references.

There are four things you can add to the table:

Active cron job for a command or script:
0 */12 * * * /path/to/backup.sh # runs a backup script every 12 hours
0 */12 * * * rsync -avh /source/ /destination/ # runs a backup command every 12 hours
A declaration of an environment variable for the following cron jobs:
result="HELLO WORLD"
A comment taht starts the line with a hash #, that is ignored by cron:
# just a comment
Or an empty line, which is also ignored:

Side note: it is recommended to use absolute paths for all scripts or executables.


Explanation from the manual:

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                           
|  |  |  |  |                                                                                                  
*  *  *  *  * command to be executed              
Examples:
* * * * * command - every minute, which is the lowest possible interval
0 * * * * command - every full hour
20 4 * * * command - every day at 4:20 am
0 4 1 * * command - at 4 am on the first day of the month
0 4 1 1 * command - at 4 am on the first day of January
0 4 * * 1 command - at 4 am every Monday

There are some operators to specify the timing even more.

Noted, the following options can be combined! I'll add an example at the end.

The asterisk (*) means every possible value.

Lists:
Lists of values can be created with a comma (,)
0 4,16 * * * command - every day at 4 am and 4 pm
0 4 * * 1,5 command - at 4 am every Monday and Friday
Ranges:
Ranges of values can be created with a hyphen (-)
0 9-17 * * * command - every hour from 9 am to 5 pm
0 4 * * 1-5 command - at 4 am every weekday from Monday to Friday
Steps:
Ranges of values can be created with a slash (/)
*/30 * * * * command - every 30 minutes (00:00,00:30,01:00,[...])
0 */12 * * * command - every 12 hours (00:00 and 12:00)

As mentioned before, you can combine these options like in the following example:

0 9-17/2 * jan-mar 1,5 - every two hours between 9 am and 5 pm, on Monday and Friday from January to March. It's not the best example, but you get the idea.

The following options are limited to only some fields and might be not compatible with every other option. Additionally, they might not be available in all crin implementations.

The (L)ast x of:
does only work for 'Day of month' and 'Day of week'
0 4 L * * command - at 4 am on the last day of the month
0 4 * * 5L command - at 4 am on the last Friday of the month
The nearest (w)eekday within the month:
does only work for 'Day of month'
0 4 15W * * command - at 4 am on nearest weekday (Mon-Fri) to the 15th of the month
must be a single day and not a list or range
The nth day of the month with a hash (#):
does only work for 'Day of week'
0 4 * * 5#2 - at 4 am on the second Friday of every month

Side note: Certain values (besides *) in the 'Day of month' and 'Day of week' fields can cause an OR condition, which creates multiple timings.


Nonstandard Special Strings #

Most implementations support special strings, but some behave a little bit differently. They replace the usual expressions * * * * *.

Special strings
@hourly - every full hour - same as 0 * * * *
@daily or @midnight - daily at midnight - same as 0 0 * * *
@weekly - weekly at midnight on Sunday - same as 0 0 * * 0
@monthly - monthly at midnight on the first day of the month - same as 0 0 1 * *
@yearly or @annually - yearly at midnight on the 1st of January - same as 0 0 1 1 *
@reboot - when the cron daemon is started. Depending on the implementation, some daemons would run the command again after a service restart, and some prevent it. Additionally, it can be beneficial to delay the command for a bit to make sure everything is up and running.
Example: @reboot sleep 300 && command
Environment Variables #

Cron does not source any startup files. We then have to add any environment variable to the crontab to use it.

It was mentioned before, but just declare the environment variable in a new line like this:

result="HELLO WORLD" # this environment variable will be available for all commands or scripts of this cron file

If you want to add an environment variable for just one cron job, you could add it like this:

20 4 * * * TZ="Europe/Berlin" command

Timezones #

By default cron uses the system timezone which can be found in the file /etc/timezone.

cat /etc/timezone
Etc/UTC

Systems often have multiple users that might work in different timezones. You can add CRON_TZ=TIME/ZOME to the cron file of specific users to specify the timezone.

CRON_TZ=Europe/Berlin

Side note: I've read that it works for Ubuntu and rockyOS, but I only tested it successfully on rockOS.

Available options can be found in the /usr/share/zoneinfo directory:

ls /usr/share/zoneinfo
Africa      EST5EDT    Iceland    PRC        Zulu               
America     Egypt      Indian     PST8PDT    iso3166.tab        
Antarctica  Eire       Iran       Pacific    leap-seconds.list  
Arctic      Etc        Israel     Poland     leapseconds        
Asia        Europe     Jamaica    Portugal   local time          
Atlantic    Factory    Japan      ROC        posix              
Australia   GB         Kwajalein  ROK        posixrules         
Brazil      GB-Eire    Libya      Singapore  right              
CET         GMT        MET        Turkey     tzdata.zi          
CST6CDT     GMT+0      MST        UCT        zone.tab
Canada      GMT-0      MST7MDT    US         zone1970.tab
Chile       GMT0       Mexico     UTC       
Cuba        Greenwich  NZ         Universal                  
EET         HST        NZ-CHAT    W-SU                                                                      
EST         Hongkong   Navajo     WET                  
Cron Job Permissions #

There are two configuration files to allow or deny users the use of cron jobs.

/etc/cron.deny # it exists by default, but is empty. Any user that is listed in this file can not use cron jobs.

/etc/cron.allow # if this file exists, users must be listed in this file to be able to use cron jobs. Just for clarification: an empty cron.allow file means that no user can use cron jobs.

If both files are missing, all users on the system can use cron jobs.

If a user is mentioned in both, the affected user can not use cron jobs.

In case you want to deny all users the use of cron jobs, you can either add ALL to the /etc/cron.deny file or create an empty /etc/cron.allow file.

Cron Jobs Logging #

The cron daemon writes logs into the following files by default:

Ubuntu / Debian

/var/log/syslog or /var/log/auth.log

You can filter the logs with grep:

sudo grep -i cron /var/log/syslog /var/log/auth.log

rockyOS / RHEL

/var/log/cron

The logs are not that detailed, and only the basics are logged.


That said, the output / stdout of the command or script is not logged and must be added to the command or script itself.

Example:

0 4 * * 5L command -v >> /var/logs/command.log

Side note: when the cron daemon is not able to run a job - for example when the server is down - all missed cron jobs won't be repeated and must be run manually if they are important.


E-Mail hellofoo@ittafoovern.comcom
Fediverse ITTavern
Lemmy infosec.pub/c/ittavern


More reading: