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.
- 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. - in the system-wide
/etc/crontab
or/etc/cron.d/*
configuration files - 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 usersudo crontab -l
# cron jobs ofroot
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 usersudo crontab -e
# edit cron jobs ofroot
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 promptcrontab -r -i
#-i
adds a yes/no prompt before removing all cron jobssudo crontab -r
# removes all cron jobs ofroot
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 hours0 */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 bycron
: # 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 interval0 * * * * command
- every full hour20 4 * * * command
- every day at 4:20 am0 4 1 * * command
- at 4 am on the first day of the month0 4 1 1 * command
- at 4 am on the first day of January0 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 pm0 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 pm0 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 month0 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
n
th 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 as0 * * * *
@daily
or@midnight
- daily at midnight - same as0 0 * * *
@weekly
- weekly at midnight on Sunday - same as0 0 * * 0
@monthly
- monthly at midnight on the first day of the month - same as0 0 1 * *
@yearly
or@annually
- yearly at midnight on the 1st of January - same as0 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.
Most recent Articles: