It is often useful to schedule tasks (i.e. jobs, processes) to occur at regular intervals. Perhaps you need to backup a database every night, pull the latest updates from a popular git repository, sync data between multiple systems, or punch a time-clock. Maybe you need to let your colleagues know you’ve got a hangover, or you have a coffee addiction. On *nix systems, anything you can run from the command line can be scheduled with cron.

A crontab (short for “cron table”) file stores the information required to execute scheduled jobs. On Mac OS, crontab files are located at /var/at/tabs, however these files should rarely be edited directly—instead the crontab command line utility should be used to interact with these files.

# ┌───────── Minute  (0-59)
# │ ┌─────── Hour    (0-23)
# │ │ ┌───── Day     (1-31)
# │ │ │ ┌─── Month   (1-12)
# │ │ │ │ ┌─ Weekday (0-7)  (Sunday=0||7)
> * * * * *  Command_to_execute
  • The asterisk (*) operator specifies all possible values for a field, e.g. every hour or every day.
  • The comma (,) operator specifies a list of values, e.g. 1,3,4,7,8.
  • The dash (-) operator specifies a range of values, e.g. 1-6 is equivalent to 1,2,3,4,5,6.
  • The slash (/) operator specifies stepped values, e.g. */3 in the hour field is equivalent to 0,3,6,9,12,15,18,21.
Field Value Description
minute 0-59 The exact minute that the command sequence executes
hour 0-23 The hour of the day that the command sequence executes
day 1-31 The day of the month that the command sequence executes
month 1-12 The month of the year that the command sequence executes
weekday 0-6 The day of the week that the command sequence executes (Sunday=0, Monday=1, Tuesday=2…)
command special The complete sequence of commands to execute. The command string must conform to Bourne shell syntax. Commands, executables (such as scripts), or combinations are acceptable.
Shorthand Description Equivalent
@reboot Runs at startup and when the cron daemon is restarted  
@hourly Runs once an hour 0 * * * *
@daily Runs every day at midnight 0 0 * * *
@midnight Runs every day at midnight @daily
@weekly Runs every Sunday at midnight 0 0 * * 0
@monthly Runs on the first day of each month at midnight 0 0 1 * *
@yearly Runs on January 1 at midnight 0 0 1 1 *
@annually Runs on January 1 at midnight @yearly

Tip: The command can optionally be prefixed by @AppleNotOnBattery' to tell cron` not to run the command when functioning on battery power.

Examples

cron_entry='0 */6 * * * /usr/local/bin/brew update &>/dev/null'
if ! crontab -l | fgrep "$cron_entry" >/dev/null; then
  (crontab -l 2>/dev/null; echo "$cron_entry") | \
    crontab -
fi
# Schedule Google Fonts Updates
read -d '' cron_entry <<-CRON
0 */6 * * * sh -c 'cd "${GOOGLE_FONTS_DIR}" && git fetch -fp --depth 1 && \
git reset --hard @{upstream} && git clean -dfx' &>/dev/null
CRON
if ! crontab -l | fgrep "$cron_entry" >/dev/null; then
  (crontab -l 2>/dev/null; echo "$cron_entry") | \
    crontab -
fi

Note: This and other cool command line tools can be found on my github project, starter.

# Running SSL renewal twice per day - at a random minute within the hour
# (it won't do anything until certificates are due for renewal or revoked,
# but running it regularly would give your site a chance of staying online
# in case a revocation occured.
8 */12 * * * /usr/bin/certbot renew --quiet --post-hook "/usr/local/apache2/bin/apachectl graceful"

Tips

Because cron is a non-interactive shell, run by root.

Change the shell used by cron with:

# Use /bin/bash, instead of /bin/sh (default)
SHELL=/bin/bash
# Set the PATH variable to locate commands
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

or

BASH_ENV="/root/.bashrc"
Screenshot of 'You have new mail' message in macOS Terminal
# Disable mail
MAILTO=""

A Better Way

Apple’s documentation on launchd makes reference to cron.

Note: Although it is still supported, cron is not a recommended solution. It has been deprecated in favor of launchd.

… Because installing cron jobs requires modifying a shared resource (the crontab file), you should not programmatically add a cron job.

https://ole.michelsen.dk/blog/schedule-jobs-with-crontab-on-mac-osx.html https://alvinalexander.com/mac-os-x/mac-osx-startup-crontab-launchd-jobs http://www.adminschoice.com/crontab-quick-reference https://www.schiff.io/blog/2017/08/31/automating-slack-status-launchd