cron Cheatsheet

System Cron Directories

Use this cron reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

/etc/crontab has a sixth field

The system crontab looks like a user crontab with one extra column: the user to run the command as, between the five time fields and the command.

# /etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h  dom mon dow user  command
17 *  * * *   root  cd / && run-parts --report /etc/cron.hourly
25 6  * * *   root  test -x /usr/sbin/anacron || \
                      ( cd / && run-parts --report /etc/cron.daily )
FileFormatInstalled with
user crontab5 fields + commandcrontab -e
/etc/crontab5 fields + user + commanda text editor, as root
/etc/cron.d/*5 fields + user + commanddropped in by a package

Forgetting the user field in /etc/crontab is a classic error. The first word of your command gets consumed as a username, cron cannot resolve it, and the line is rejected with a syslog error rather than a visible failure.

Unlike user crontabs, /etc/crontab and /etc/cron.d are edited directly. Cron watches their mtimes and reloads within a minute, so there is no install step.

/etc/cron.d, the drop-in directory

One file per package or per concern, same format as /etc/crontab.

# /etc/cron.d/app-cleanup
MAILTO=ops@example.com
PATH=/usr/local/bin:/usr/bin:/bin

0 4 * * * deploy /srv/app/bin/cleanup.sh >> /var/log/cleanup.log 2>&1

Rules that make files here silently ignored:

- The filename must contain no dots. app-cleanup.sh and app.cron are skipped. Letters, digits, underscores, and hyphens only, the same restriction run-parts uses. - The file must be owned by root and not writable by anyone else. - The file must not be executable. That is the opposite of the cron.daily directories, and it catches everyone once. - It must end with a newline. - Files may be symlinks only if both the link and the target are root owned.

Files in /etc/cron.d are independent of /etc/crontab and do not inherit its PATH, SHELL, or MAILTO, so each file sets its own.

Prefer /etc/cron.d over editing /etc/crontab, because a package upgrade can overwrite /etc/crontab and because one file per job is greppable and removable.

cron.hourly, daily, weekly, monthly

These are directories of executable scripts, run by run-parts from the /etc/crontab lines shown above. There is no schedule inside the script, the directory is the schedule.

sudo install -m 755 backup /etc/cron.daily/backup    # note: no .sh
sudo run-parts --test /etc/cron.daily                # what would run
sudo run-parts --report /etc/cron.daily              # run it now, verbosely
RequirementDetail
Executableyes, chmod 755, or run-parts skips it
FilenameASCII letters, digits, _, and - only, no dots
Shebangrequired, it is executed, not sourced
Orderlexical sort in the C locale, so 10-first before 20-second
Argumentsnone, and there is no way to pass any

The no-dots rule is the one that bites. backup.sh in /etc/cron.daily never runs, silently, and run-parts --list is how you confirm it. With --lsbsysinit, a wider namespace is allowed, which is why some distributions tolerate dotted names and others do not.

Exact run times vary by distribution, and on Debian they are configured in /etc/crontab (hourly at :17, daily at 06:25) while RHEL delegates the daily, weekly, and monthly directories to anacron entirely.

anacron, for machines that are not always on

Cron assumes the machine is running at the scheduled moment. A laptop that is asleep at 06:25 simply misses that day's cron.daily. anacron schedules by elapsed days instead of by wall-clock time, and runs a missed job the next time the machine is up.

# /etc/anacrontab
SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
RANDOM_DELAY=45
START_HOURS_RANGE=3-22

# period  delay  job-identifier   command
1         5      cron.daily       run-parts --report /etc/cron.daily
7         25     cron.weekly      run-parts --report /etc/cron.weekly
@monthly  45     cron.monthly     run-parts --report /etc/cron.monthly
ColumnMeaning
perioddays between runs, or @monthly
delayminutes to wait after anacron starts, staggers the jobs
job-identifiername of the timestamp file in /var/spool/anacron/
commandwhat to run

- START_HOURS_RANGE restricts the hours during which anacron will start a job. If that whole window is missed, for example the machine was off from 03:00 to 23:00, the job is not run that day at all. - RANDOM_DELAY adds 0 to N random minutes, to avoid a fleet hitting a mirror at once. - State is one timestamp file per job identifier in /var/spool/anacron/. Delete it and the job runs on the next anacron pass. - Resolution is one day. anacron cannot do hourly, which is why cron.hourly stays in cron.

sudo anacron -T              # test /etc/anacrontab syntax
sudo anacron -d -n           # run now, in the foreground, ignore delays
sudo anacron -u              # only update timestamps, do not run anything
cat /var/spool/anacron/cron.daily

Which directory should a new job go in

SituationPut it
Runs as a specific non-root user/etc/cron.d/<name>
Needs a precise time/etc/cron.d/<name>
Ships with a package/etc/cron.d/<name>
"Once a day, do not care when"/etc/cron.daily/<name>
Personal, for one accountthat user's crontab -e
Needs ordering, retries, resource limits, or dependenciesa systemd timer

The distinction that matters: files in cron.daily are executable scripts with no schedule, files in cron.d are non-executable crontab lines with a schedule and a user. Getting those two backwards is why a job appears to be installed and never runs.

# Audit everything the system schedules outside user crontabs
cat /etc/crontab
grep -r . /etc/cron.d/ 2>/dev/null
run-parts --list /etc/cron.hourly /etc/cron.daily \
                 /etc/cron.weekly /etc/cron.monthly
cat /etc/anacrontab
systemctl list-timers --all