ZetCode

Linux cron and crontab

last modified October 23, 2026

cron is the classic Linux job scheduler. The cron daemon runs in the background and executes commands at specified times: every minute, every night at 2:30, on weekdays during working hours, and so on. Each user has a personal table of jobs, the crontab, which is managed with the crontab command.

Cron is used for backups, log rotation, report generation, cleanup tasks, and other recurring maintenance. The examples use the cron implementation from Debian; other implementations such as cronie behave the same for the features shown here.

Crontab syntax

Each job is one line with five time fields followed by the command.

# ┌───────── minute (0-59)
# │ ┌─────── hour (0-23)
# │ │ ┌───── day of month (1-31)
# │ │ │ ┌─── month (1-12 or jan-dec)
# │ │ │ │ ┌─ day of week (0-7 or sun-sat; 0 and 7 are Sunday)
# │ │ │ │ │
  * * * * * command

A field can contain a single value, * for every value, a list such as 1,15, a range such as 9-17, or a step such as */15. If both the day of month and the day of week are restricted, the job runs when either of them matches. Lines starting with # are comments.

ScheduleMeaning
* * * * *Every minute
*/5 * * * *Every five minutes
30 2 * * *Every day at 2:30
0 9 * * 1-5At 9:00 on weekdays
*/15 9-17 * * 1-5Every 15 minutes from 9:00 to 17:45 on weekdays
0 0 1 * *At midnight on the first day of every month
0 4 * * sunAt 4:00 every Sunday

Listing the crontab

The -l option prints the current user's crontab.

crontab -l

A new user has no crontab yet.

$ crontab -l
no crontab for jano

Installing jobs

The usual way to edit the crontab is crontab -e, which opens it in the editor set in the EDITOR or VISUAL variable and installs it after saving. A crontab can also be installed from a file, which is convenient for keeping jobs in version control.

jobs.cron
# m h  dom mon dow  command
* * * * * date >> $HOME/cron-test.log
* * * * * env > $HOME/cron-env.txt
* * * * * echo "today is $(date +\%F)" >> $HOME/dates.log
* * * * * $HOME/work/bin/backup.sh >> $HOME/backup.log 2>&1
30 2 * * * $HOME/work/bin/backup.sh
*/15 9-17 * * 1-5 $HOME/work/bin/check.sh

The first four jobs run every minute so that we can see their effect: they log the date, save the environment of a cron job, use the date in a command, and run a backup script. The last two are typical production schedules. Note that installing a file replaces the whole crontab; it does not add to it.

$ crontab jobs.cron
$ crontab -l
# m h  dom mon dow  command
* * * * * date >> $HOME/cron-test.log
* * * * * env > $HOME/cron-env.txt
* * * * * echo "today is $(date +\%F)" >> $HOME/dates.log
* * * * * $HOME/work/bin/backup.sh >> $HOME/backup.log 2>&1
30 2 * * * $HOME/work/bin/backup.sh
*/15 9-17 * * 1-5 $HOME/work/bin/check.sh

The cron environment

After the next full minute, cron runs the jobs. A job runs with a very minimal environment, not with the environment of our login shell.

cat ~/cron-test.log
cat ~/cron-env.txt

The job ran at the start of the minute. Its PATH contains only /usr/bin and /bin, and commands are executed by /bin/sh, not Bash. Variables from ~/.bashrc are not available. This is the most common reason why a command works in the terminal but fails in cron. Use full paths, or set variables at the top of the crontab, for example PATH=/usr/local/bin:/usr/bin:/bin or SHELL=/bin/bash.

$ cat ~/cron-test.log
Fri Oct 23 11:51:01 CEST 2026
$ cat ~/cron-env.txt
HOME=/home/jano
LOGNAME=jano
PATH=/usr/bin:/bin
SHELL=/bin/sh
PWD=/home/jano

Percent signs

In a crontab, the % character has a special meaning: it ends the command and starts standard input. It must be escaped as \%.

* * * * * echo "today is $(date +\%F)" >> $HOME/dates.log

Thanks to the backslash, date receives the %F format and the job works. Without it, the command would be cut off at the percent sign. Moving complex commands into a script avoids the issue.

$ cat ~/dates.log
today is 2026-10-23

Logging output and errors

By default, cron mails the output of a job to the user, which often goes unnoticed or fails when no mail system is installed. It is better to redirect the output to a log file.

bin/backup.sh
#!/bin/bash

echo "backup started at $(date +%T)"
tar -czf /tmp/docs.tar.gz "$HOME/docs"
echo "backup finished"

The job line $HOME/work/bin/backup.sh >> $HOME/backup.log 2>&1 appends both standard output and standard error to a log. The log reveals that the backup failed because the docs directory does not exist, yet the script still reported success. Adding set -e to the script would stop it at the first error. A script must be executable (chmod +x) to be run directly by cron.

$ cat ~/backup.log
backup started at 11:51:01
tar: Removing leading `/' from member names
tar: /home/jano/docs: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors
backup finished

Special schedule strings

Most cron implementations support shortcuts for common schedules.

@reboot  $HOME/bin/start-agent.sh
@hourly  $HOME/bin/sync.sh
@daily   $HOME/bin/backup.sh
@weekly  $HOME/bin/cleanup.sh
@monthly $HOME/bin/report.sh

@daily is equivalent to 0 0 * * *, and the other strings work similarly. @reboot runs the job once when the cron daemon starts after boot.

System cron jobs

Besides user crontabs, there are system-wide jobs in /etc/crontab and /etc/cron.d/, and directories of scripts run at fixed intervals.

grep -v "^#" /etc/crontab
ls /etc/cron.daily
run-parts --test /etc/cron.daily

System crontab lines have an extra field with the user name the job runs as. On Debian and Ubuntu, /etc/crontab runs the scripts in /etc/cron.hourly, cron.daily, cron.weekly, and cron.monthly with run-parts; when anacron is installed, it takes over the daily, weekly, and monthly runs so they are not skipped while the computer is off. The --test option lists the scripts that would be run. Note that run-parts ignores file names containing dots, so a script named backup.sh in these directories is silently skipped.

$ grep -v "^#" /etc/crontab

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

17 *	* * *	root	cd / && run-parts --report /etc/cron.hourly
25 6	* * *	root	test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
47 6	* * 7	root	test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; }
52 6	1 * *	root	test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; }
$ ls /etc/cron.daily
apt-compat
dpkg
$ run-parts --test /etc/cron.daily
/etc/cron.daily/apt-compat
/etc/cron.daily/dpkg

Preventing overlapping runs

If a job takes longer than its interval, cron starts another instance anyway. The flock command prevents this with a lock file.

*/5 * * * * flock -n /tmp/backup.lock $HOME/bin/backup.sh

To demonstrate, we hold the lock with a long-running command and try to acquire it again. With -n, flock does not wait for the lock; it gives up immediately with exit status 1, so the second instance does not run.

$ flock -n /tmp/backup.lock sleep 5 &
$ flock -n /tmp/backup.lock echo "running"; echo "exit: $?"
exit: 1

Managing crontabs

Root can manage the crontab of any user with -u, and -r removes the crontab.

sudo crontab -u alice -l
crontab -r

The crontab -r command deletes all jobs without asking; since r is next to e on the keyboard, it is wise to keep a backup with crontab -l > crontab.bak. Access to cron can be restricted with the /etc/cron.allow and /etc/cron.deny files.

$ sudo crontab -u alice -l
no crontab for alice
$ crontab -r
$ crontab -l
no crontab for jano

Troubleshooting

When a job does not run as expected, check the following:

On systemd-based distributions, systemd timers are an alternative to cron with built-in logging and dependency handling.

Best practices for cron

Source

crontab(5) manual page

In this article, we have scheduled jobs with cron, examined the cron environment, logged job output, worked with system cron directories, and prevented overlapping runs with flock.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.