cron: Operation not permitted on macOS

Give the cron daemon Full Disk Access. Six clicks, then the job works.

The fix

  1. Open System Settings › Privacy & Security › Full Disk Access.
  2. Click +. An authentication prompt appears; unlock it.
  3. In the file chooser press ⇧⌘G (Go to Folder) and type /usr/sbin/cron, then Return.
  4. Choose cron and click Open.
  5. Make sure the new cron row is switched on.
  6. Wait for the job's next run, or run the command by hand to confirm.

The chooser hides /usr/sbin by default, which is why step 3 uses Go to Folder. You are adding the cron daemon itself, not your script and not Terminal.

Why it is needed

macOS protects some folders behind TCC, the permission system that also guards the camera and the microphone. The protected set includes ~/Desktop, ~/Documents, ~/Downloads, iCloud Drive, Photos, Mail, Messages, Time Machine backups, and removable volumes under /Volumes.

TCC asks who is responsible for the access. When you run a script in Terminal, that is Terminal, which is why the same script works when you paste it in and macOS prompts you once. When cron runs it there is nobody to prompt: cron is a background daemon started by launchd, so the access is simply refused and your script gets Operation not permitted, EPERM, or in Python a PermissionError. Adding cron to Full Disk Access is how you answer that prompt in advance.

Check it worked

Put a one-minute job in your crontab that reads a protected folder and writes the result where anyone can read it:

* * * * * /bin/ls ~/Documents > /tmp/fda-test.log 2>&1

Wait two minutes, then look:

cat /tmp/fda-test.log

A listing means the permission is in place. ls: /Users/you/Documents: Operation not permitted means it is not. Take the test line out again when you are done.

When it still fails

Taking the permission away again

Full Disk Access for cron applies to every cron job on the Mac, yours and anyone else's, so it is worth removing when the job that needed it is gone. Select the cron row in that list and click , or just switch it off. Nothing else depends on it; cron keeps running, and only the jobs that reach into protected folders start failing again.

The alternative: do not use a protected folder

Nothing under ~/Library, /tmp, /usr/local or a folder you made yourself at the top of your home directory is protected. A backup script that reads ~/src and writes ~/backups needs no permission at all. If the data has to live in Documents, Full Disk Access is the only way.

Or be told before the job fails

CronMon reads the command of every cron job on your Mac and flags the ones that touch Desktop, Documents, Downloads or a removable volume, with the System Settings path to fix it. It shows the same warning for a launchd agent, against the program that agent runs.

A cron job in CronMon: the schedule, the next runs, the command, and a Worth knowing warning that the job uses a folder cron cannot read, with the Full Disk Access steps as the fix.

Download CronMon