Since macOS Mojave (10.14), Apple implemented stricter System Integrity Protection (SIP) that affects certain system directories including /private/var/at
. This manifests when trying to edit crontabs:
$ crontab -e
crontab: tmp/tmp.XXXXXX: Operation not permitted
The issue stems from Mojave's new privacy protections. Even with sudo, you'll notice:
$ sudo touch /private/var/at/test
touch: /private/var/at/test: Operation not permitted
This occurs because /private/var/at
is now protected by SIP's filesystem restrictions, similar to how /usr/bin
is protected.
Option 1: Temporary SIP Disable (Not Recommended)
While you could disable SIP, this compromises system security:
# Reboot into Recovery Mode (Cmd+R)
# Open Terminal
csrutil disable
reboot
Option 2: Approved Mojave Workaround
Use macOS's built-in launchd instead. Create a plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.myjob</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/your/script.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>
Load it with:
launchctl load ~/Library/LaunchAgents/com.example.myjob.plist
Option 3: Manual Crontab Editing (Advanced)
For those who absolutely need classic crontab functionality:
# 1. Create temp file
TMPFILE=$(mktemp /tmp/crontab.XXXXXX)
# 2. Export existing crontab
crontab -l > $TMPFILE
# 3. Edit manually
vi $TMPFILE
# 4. Import back (using sudo -E to preserve environment)
sudo -E crontab $TMPFILE
# 5. Cleanup
rm $TMPFILE
• Mojave's privacy protections extend to many system directories
• Apple is gradually deprecating traditional Unix tools in favor of launchd
• Consider using /usr/local/bin
for custom scripts to avoid permission issues
Since macOS Mojave (10.14), Apple implemented stricter System Integrity Protection (SIP) that affects several system directories, including /private/var/at
. When you attempt to modify crontab entries, the system prevents writing temporary files to this protected location.
First, let's verify the current permissions and SIP status:
# Check SIP status
csrutil status
# Examine directory permissions
ls -laO /private/var/at
ls -laOd /private/var/at
Method 1: Temporary Disable SIP (Not Recommended for Production)
- Reboot into Recovery Mode (Command+R during startup)
- Open Terminal from Utilities menu
- Run:
csrutil disable
- Reboot and test crontab
- Re-enable SIP afterward:
csrutil enable
Method 2: Alternative crontab Directory (Recommended)
Create a custom crontab directory with proper permissions:
sudo mkdir -p /usr/local/var/cron
sudo chown $(whoami) /usr/local/var/cron
export CRON_TMPDIR=/usr/local/var/cron
crontab -e
Make this permanent by adding to your shell profile:
echo 'export CRON_TMPDIR=/usr/local/var/cron' >> ~/.zshrc
Method 3: Using launchd Instead (macOS Native Alternative)
Create a plist file for your cron job:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.myjob</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/your/script.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>
Load it with:
launchctl load ~/Library/LaunchAgents/com.example.myjob.plist
- Check Console logs for detailed error messages
- Verify Terminal has Full Disk Access in System Preferences
- Consider creating a dedicated user for cron jobs with appropriate permissions