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 )
| File | Format | Installed with |
|---|---|---|
| user crontab | 5 fields + command | crontab -e |
/etc/crontab | 5 fields + user + command | a text editor, as root |
/etc/cron.d/* | 5 fields + user + command | dropped 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
| Requirement | Detail |
|---|---|
| Executable | yes, chmod 755, or run-parts skips it |
| Filename | ASCII letters, digits, _, and - only, no dots |
| Shebang | required, it is executed, not sourced |
| Order | lexical sort in the C locale, so 10-first before 20-second |
| Arguments | none, 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
| Column | Meaning |
|---|---|
| period | days between runs, or @monthly |
| delay | minutes to wait after anacron starts, staggers the jobs |
| job-identifier | name of the timestamp file in /var/spool/anacron/ |
| command | what 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
| Situation | Put 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 account | that user's crontab -e |
| Needs ordering, retries, resource limits, or dependencies | a 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