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 %}}