Skip to content
C.W.K.
Stream
Lesson 07 of 10 · published

Cron and launchd

~12 min · cron, launchd, schedule

Level 0Window Tourist
0 XP0/95 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Two ways to schedule things

On Linux servers, cron is a common time-based scheduler. On macOS, launchd is the default lifecycle system and the recommended owner for new scheduled work. Existing cron jobs may still be present, so inspect both systems.

cron quick reference

crontab -e                 # edit your cron table
crontab -l                 # list it
*/5 * * * * /path/to/script.sh
0   3 * * 1 /path/to/weekly.sh

The five fields are minute, hour, day of month, month, and day of week. cron supplies a small environment and may use a different working directory from your terminal. Prefer absolute executable paths, explicit variables, and a controlled working directory. If a job intentionally sources a file, keep that file minimal and review it as part of the job contract.

launchd LaunchAgent

Place the plist in ~/Library/LaunchAgents/ and register it with launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.me.mytask.plist. Use StartInterval for a seconds-based interval or StartCalendarInterval for a calendar schedule. Loading at a user session and restarting after failure depend on the plist's lifecycle keys, such as RunAtLoad and KeepAlive.

<plist>
<dict>
  <key>Label</key>          <string>com.me.daily</string>
  <key>ProgramArguments</key> <array><string>/path/to/daily.sh</string></array>
  <key>StartCalendarInterval</key>
    <dict><key>Hour</key><integer>3</integer><key>Minute</key><integer>0</integer></dict>
  <key>StandardOutPath</key> <string>/tmp/daily.log</string>
</dict>
</plist>

An app-internal scheduler is a separate layer

An application may own its schedule while the operating-system service manager only keeps that application process alive. An empty crontab therefore does not prove that no scheduled work exists; inspect the running service's configuration and persistent state too.

  • Linux server → cron (or systemd timers).
  • macOS → launchd LaunchAgent (user) or LaunchDaemon (root).
  • App-internal schedules → embed a scheduler (APScheduler, node-cron) and let the OS own the process lifetime.

Give the schedule its environment

Cron supplies a small job environment; sourcing an interactive startup file imports unrelated aliases, prompts, and secrets. Use explicit paths, a controlled working directory, bounded logs, and a lock or idempotent design. On macOS, prefer launchd when it is the declared lifecycle owner.

Code

Bootstrap a launchd agent·bash
# Place plist
vim ~/Library/LaunchAgents/com.me.daily.plist
# Load (idempotent enough; bootout first if it was loaded)
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.me.daily.plist
# Inspect
launchctl list | grep com.me.daily
# Run now without waiting for schedule
launchctl kickstart gui/$UID/com.me.daily

External links

Exercise

Pick a tiny script (echo hello at $(date) >> /tmp/heartbeat.log). Schedule it: cron with */5 * * * *, or write a launchd plist with StartInterval 300. Verify by tailing /tmp/heartbeat.log.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.