| name | cron |
| category | system |
| description | Schedule recurring and one-time tasks using cron, crontab, at, and launchd. Use when the user asks to set up scheduled jobs, configure crontab entries, write cron expressions, schedule one-time tasks with at, create launchd plist files on macOS, debug why a cron job is not running, or manage periodic automated tasks on Unix/Linux/macOS systems.
|
| license | MIT |
| compatibility | Requires cron/crontab (Linux/macOS) or launchd (macOS) |
| metadata | {"author":"zeph","version":"1.0"} |
Task Scheduling with Cron, At, and Launchd
Cron Expression Format
┌─────────── minute (0-59)
│ ┌───────── hour (0-23)
│ │ ┌─────── day of month (1-31)
│ │ │ ┌───── month (1-12 or JAN-DEC)
│ │ │ │ ┌─── day of week (0-6, SUN-SAT; 7 = SUN on some systems)
│ │ │ │ │
* * * * * command
Special Characters
| Character | Meaning | Example |
|---|
* | Any value | * * * * * (every minute) |
, | List | 1,15,30 (at 1, 15, 30) |
- | Range | 1-5 (1 through 5) |
/ | Step | */5 (every 5 units) |
Common Expressions
| Expression | Schedule |
|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
*/15 * * * * | Every 15 minutes |
0 * * * * | Every hour (at :00) |
0 */2 * * * | Every 2 hours |
30 * * * * | Every hour at :30 |
0 0 * * * | Daily at midnight |
0 6 * * * | Daily at 6:00 AM |
0 8,12,18 * * * | At 8 AM, noon, 6 PM |
0 9-17 * * * | Every hour from 9 AM to 5 PM |
0 0 * * 0 | Weekly on Sunday at midnight |
0 0 * * 1-5 | Weekdays at midnight |
0 0 * * 6,0 | Weekends at midnight |
0 0 1 * * | First day of every month |
0 0 1 1 * | Yearly on January 1 |
0 0 1 */3 * | Every 3 months (quarterly) |
0 0 15 * * | 15th of every month |
5 4 * * 0 | Sunday at 4:05 AM |
0 22 * * 1-5 | Weekdays at 10 PM |
Predefined Shortcuts
| Shortcut | Equivalent | Description |
|---|
@reboot | — | Run once at startup |
@yearly / @annually | 0 0 1 1 * | Once a year (Jan 1) |
@monthly | 0 0 1 * * | Once a month (1st) |
@weekly | 0 0 * * 0 | Once a week (Sunday) |
@daily / @midnight | 0 0 * * * | Once a day |
@hourly | 0 * * * * | Once an hour |
Crontab Management
crontab -e
crontab -l
crontab -r
sudo crontab -u username -e
sudo crontab -u username -l
crontab mycrontab.txt
crontab -l > crontab-backup.txt
crontab crontab-backup.txt
Crontab File Format
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
HOME=/home/user
30 2 * * * /usr/local/bin/backup.sh
*/15 * * * * /usr/local/bin/healthcheck.sh
0 9 * * 1-5 /usr/local/bin/send-report.sh
0 3 1 * * /usr/local/bin/cleanup.sh
@reboot /usr/local/bin/start-services.sh
Output Handling
* * * * * /path/to/script.sh > /var/log/cron-output.log 2>&1
* * * * * /path/to/script.sh >> /var/log/cron-output.log 2>&1
* * * * * /path/to/script.sh > /dev/null 2>&1
* * * * * /path/to/script.sh > /var/log/cron.log 2> /var/log/cron-error.log
MAILTO=user@example.com
0 * * * * /path/to/script.sh
MAILTO=""
0 * * * * /path/to/script.sh
0 * * * * /path/to/script.sh > /dev/null
Environment in Cron
Cron jobs run with a minimal environment. The PATH is usually just /usr/bin:/bin.
PATH=/usr/local/bin:/usr/bin:/bin:/home/user/.local/bin
0 * * * * /usr/local/bin/python3 /home/user/scripts/task.py
0 * * * * . /home/user/.profile && /home/user/scripts/task.sh
0 * * * * /bin/bash -lc '/home/user/scripts/task.sh'
0 * * * * LANG=en_US.UTF-8 /home/user/scripts/task.sh
LANG=en_US.UTF-8
NODE_ENV=production
Locking (Preventing Overlap)
*/5 * * * * flock -n /tmp/myjob.lock /path/to/script.sh
*/5 * * * * flock -w 60 /tmp/myjob.lock /path/to/script.sh
*/5 * * * * /path/to/script-with-pidcheck.sh
System Cron Directories
/etc/crontab
/etc/cron.d/
/etc/cron.hourly/
/etc/cron.daily/
/etc/cron.weekly/
/etc/cron.monthly/
*/5 * * * * root /usr/local/bin/check.sh
at — One-Time Scheduled Tasks
echo "/path/to/script.sh" | at 14:30
echo "/path/to/script.sh" | at 14:30 2024-12-25
echo "/path/to/script.sh" | at now + 5 minutes
echo "/path/to/script.sh" | at now + 2 hours
echo "/path/to/script.sh" | at now + 1 day
echo "/path/to/script.sh" | at now + 1 week
at 15:00
echo "cmd" | at midnight
echo "cmd" | at noon
echo "cmd" | at teatime
echo "cmd" | at tomorrow
echo "cmd" | at "10:00 AM Jul 4"
atq
at -l
at -c JOB_NUMBER
atrm JOB_NUMBER
at -d JOB_NUMBER
echo "/path/to/heavy-task.sh" | batch
Launchd (macOS)
Launchd is the macOS replacement for cron. Jobs are configured via XML property list (plist) files.
Plist File Template
<?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>/usr/local/bin/script.sh</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>StandardOutPath</key>
<string>/tmp/myjob.stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/myjob.stderr.log</string>
<key>RunAtLoad</key>
<true/>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>
Calendar Schedule Keys
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>30</integer>
</dict>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Weekday</key><integer>1</integer>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
<dict>
<key>Weekday</key><integer>2</integer>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
<dict>
<key>Weekday</key><integer>3</integer>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
<dict>
<key>Weekday</key><integer>4</integer>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
<dict>
<key>Weekday</key><integer>5</integer>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>0</integer>
</dict>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Day</key>
<integer>1</integer>
<key>Hour</key>
<integer>0</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
Launchd Management
launchctl load ~/Library/LaunchAgents/com.example.myjob.plist
launchctl unload ~/Library/LaunchAgents/com.example.myjob.plist
sudo launchctl load /Library/LaunchDaemons/com.example.myjob.plist
launchctl list | grep com.example.myjob
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.myjob.plist
launchctl bootout gui/$(id -u)/com.example.myjob
launchctl start com.example.myjob
plutil -lint ~/Library/LaunchAgents/com.example.myjob.plist
Launchd Directories
| Directory | Scope | Runs as |
|---|
~/Library/LaunchAgents/ | Current user only | Current user |
/Library/LaunchAgents/ | All users | Logged-in user |
/Library/LaunchDaemons/ | System-wide | root |
Debugging Cron Jobs
Common Issues
- Wrong PATH: cron uses a minimal PATH; use full paths or set PATH in crontab
- No output captured: redirect both stdout and stderr (
> log 2>&1)
- Permission denied: check script has execute permission (
chmod +x script.sh)
- Wrong user: verify which user's crontab the job is in
- Environment missing: cron does not source
.bashrc or .profile
Diagnostic Steps
systemctl status cron
systemctl status crond
launchctl list | grep cron
grep CRON /var/log/syslog
journalctl -u cron
journalctl -u cron --since "1 hour ago"
log show --predicate 'process == "cron"' --last 1h
crontab -l
env -i SHELL=/bin/bash PATH=/usr/bin:/bin HOME=$HOME /path/to/script.sh
ls /var/mail/
Test Script for Cron Environment
#!/bin/bash
echo "Date: $(date)"
echo "User: $(whoami)"
echo "Shell: $SHELL"
echo "PATH: $PATH"
echo "HOME: $HOME"
echo "PWD: $(pwd)"
env | sort
Important Notes
- Cron uses the system timezone; be explicit about timezone if it matters
- The
% character in crontab has special meaning (newline); escape it as \%
- Both day-of-month and day-of-week fields are OR-ed:
0 0 1 * 5 runs on the 1st AND every Friday
- Crontab files must end with a newline; the last line may be silently ignored without one
- On macOS, launchd is preferred over cron; some macOS versions restrict cron
at may not be installed by default on minimal Linux installations; install via at package
- Use
flock to prevent overlapping job execution for long-running tasks
- Always test cron commands by running them manually first with a reduced environment
- Log rotation: if cron jobs produce logs, set up logrotate to prevent disk filling
- For complex scheduling (business days, holidays), consider dedicated schedulers over cron