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**
```markdown
root@test-ubu-01:~# grep "^[^#;]" /var/spool/cron/crontabs/* /etc/crontab /etc/cron.d/*
```
```markdown
/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
```
```markdown
root@test-ubu-01:~# ls /etc/cron.*
```
```markdown
/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**
```markdown
[root@test-rocky-01 ~]# grep "^[^#;]" /var/spool/cron/* /etc/crontab /etc/cron.d/*
```
```markdown
/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 ~]#
```
```markdown
[root@test-rocky-01 etc]# ls /etc/cron.*
```
```markdown
/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:
```markdown
/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:**
```markdown
[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:
```markdown
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 `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 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`.
```markdown
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:
```markdown
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.
---
{{% contact %}}
---
**More reading:**
{{% post-list tags=linux stop=5 %}}{{% /post-list %}}
{{% post-list stop=5 %}}{{% /post-list %}}