Skip to content
Home » How to Use crontab on macOS (and Why Cron Jobs Don’t Run)

How to Use crontab on macOS (and Why Cron Jobs Don’t Run)

cron still works on macOS. The crontab command is built in — there is nothing to install — and it schedules jobs with the same five-field syntax you would use on Linux. The trouble Mac users run into almost never comes from the syntax. It comes from a job that sits in the crontab and never seems to run: no error, nothing in any obvious log, just silence.

On macOS that silence usually traces back to three things the crontab man page does not mention: Full Disk Access, a stripped-down environment, and the fact that Apple folded cron into launchd a long time ago. Here is the working setup, followed by the Mac-specific reasons a job fails.

The three crontab commands you actually use

Each user has their own crontab. You never edit the stored file directly — you go through the crontab command, which validates the table before installing it. Three forms cover almost everything:

CommandWhat it does
crontab -lPrint your current crontab to standard output
crontab -eEdit it in the editor named by $EDITOR or $VISUAL (vi if neither is set)
crontab -rRemove your crontab entirely
crontab fileReplace your whole crontab with the contents of file
crontab -u name ...Act on another user’s crontab (needs sudo)

Two things surprise people here. crontab -e drops you into vi if you have not set an editor, and both crontab -e and crontab file replace the entire crontab rather than appending to it. A full install-and-remove round trip on macOS 26.5.2 looks like this:

$ cat mycron
# clean old temp files every Monday at 3:30am
30 3 * * 1 /usr/bin/find "$HOME/Downloads" -name "*.tmp" -mtime +7 -delete

$ crontab mycron          # install from a file
$ crontab -l              # list what is installed
# clean old temp files every Monday at 3:30am
30 3 * * 1 /usr/bin/find "$HOME/Downloads" -name "*.tmp" -mtime +7 -delete

$ crontab -r              # remove your crontab
$ crontab -l
crontab: no crontab for osxhub

The username in the last line is redacted to osxhub; every other character is the real output. Note the exit behavior: listing an empty crontab returns a non-zero status with that no crontab for message, which is handy in scripts that check whether a crontab exists.

How to read a crontab line

Five time fields, then the command. Each field is a number, a range, a list, a step, or an asterisk for “every”.

FieldAllowed values
minute0–59
hour0–23
day of month1–31
month1–12 (or names like jan)
day of week0–7 (0 or 7 is Sunday, or names like sun)

A range is 8-11 (hours 8, 9, 10, 11). A list is 1,2,5,9. A step follows a range or asterisk with a slash: */2 in the hour field means every other hour, and 0-23/2 means the same thing spelled out. So 0 9 * * 1-5 runs at 9:00 on weekdays.

If you do not care about an exact minute, macOS cron accepts eight shorthand strings in place of all five fields: @reboot, @hourly, @daily (also @midnight), @weekly, @monthly, and @yearly (also @annually). @reboot runs the job once at startup.

Why your cron job doesn’t run

This is the part that sends people to search engines. The schedule is correct, the command works when you paste it into Terminal, and yet nothing happens. On macOS there are three common causes, and none of them are documented in man crontab.

It needs Full Disk Access

The cron daemon runs under the same privacy protections (TCC) as any other process. If your job reads or writes anything in a protected location — Desktop, Documents, Downloads, or an external volume — it fails silently until you grant Full Disk Access to the cron binary itself. In System Settings, open Privacy & Security, then Full Disk Access, click the plus button, press Command-Shift-G, and enter /usr/sbin/cron. This is the single most common reason a Mac cron job “does nothing.” Because it involves a GUI permission grant, treat it as the documented cause rather than something you can confirm from the command line.

It runs with a minimal environment

cron does not load your shell profile, and it runs with a short PATH. A bare find, python3, or brew that works fine in Terminal can come back as command not found under cron. Use absolute paths — /usr/bin/find, not find — or set PATH= on its own line at the top of the crontab. If you are unsure where a command lives, which brew tells you the full path to hard-code. This is the same PATH problem covered in how to add to PATH on macOS, except cron never sees the PATH you set in .zshrc.

Its output goes to mail, not to you

By default cron mails a job’s output to the local user. There is no Terminal window attached, so you never see it, and local mail is rarely set up on a Mac. Redirect the output to a file so you can actually read what happened: end the command with >> /tmp/myjob.log 2>&1, then check that log after the job’s scheduled time. Nine times out of ten the log holds the real error, and the schedule was never the problem.

cron doesn’t run until a crontab exists

Apple does not keep cron running all the time. launchd holds a job description at /System/Library/LaunchDaemons/com.vix.cron.plist that starts /usr/sbin/cron on demand. That plist watches the crontab spool directory /usr/lib/cron/tabs and the system file /etc/crontab, and brings cron up when either one changes. That is why your first crontab -e is the thing that actually activates cron — before any crontab exists, there is nothing for the daemon to do.

The spool directory is owned by root, which you can see for yourself:

$ ls -la /usr/lib/cron/tabs/
ls: /usr/lib/cron/tabs/: Permission denied

Your crontab lives in that root-only directory, which is exactly why you edit it through the setuid crontab command instead of opening a file. The man page states this plainly: cron and crontab are officially supported under Darwin, but “their functionality has been absorbed into launchd,” which is the direction Apple points you for anything more involved.

crontab or launchd: which to use

For a one-line recurring job you will edit by hand, crontab is faster to write and easier to remember. launchd is what Apple actually maintains, and it does things cron cannot: it can catch up on a job that was missed while the Mac was asleep, start an agent when a file changes, and keep a process alive. If your task matters and needs to survive reboots and sleep reliably, write it as a launchd job instead — see managing services with launchctl. For a quick nightly cleanup, crontab is fine.

Who is allowed to use cron

Access to crontab is gated by two optional files. If /usr/lib/cron/cron.allow exists, only users listed in it may use the command. If that file is absent but /usr/lib/cron/cron.deny exists, anyone not listed in it may use cron. On a standard macOS install neither file exists, so ordinary users can manage their own crontab without sudo — which matches the round trip above.

Put together: crontab on macOS is the familiar Vixie syntax with three Mac-specific catches layered on top. Grant Full Disk Access to /usr/sbin/cron, use absolute paths, and log the output to a file. If a job still will not run after that, read /tmp/myjob.log for the actual error rather than adjusting the schedule — the timing is rarely what is broken.

Leave a Reply

Your email address will not be published. Required fields are marked *