379 lines
14 KiB
Markdown
379 lines
14 KiB
Markdown
# 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 <a href="#basics" id="basics">#</a>
|
|
|
|
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 <a href="#daemon" id="daemon">#</a>
|
|
|
|
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 <a href="#show-cron-jobs" id="show-cron-jobs">#</a>
|
|
|
|
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 <a href="#edit-cron-jobs" id="edit-cron-jobs">#</a>
|
|
|
|
**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 <a href="#remove-cron-jobs" id="remove-cron-jobs">#</a>
|
|
|
|
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 <a href="#cron-jobs-expressions" id="cron-jobs-expressions">#</a>
|
|
|
|
**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 <a href="#special-strings" id="special-strings">#</a>
|
|
|
|
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 <a href="#env-variables" id="env-variables">#</a>
|
|
|
|
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 <a href="#timezones" id="timezones">#</a>
|
|
|
|
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 <a href="#permissions" id="permissions">#</a>
|
|
|
|
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 <a href="#logging" id="logging">#</a>
|
|
|
|
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.
|
|
|
|
---
|