Cron Scheduling

Cron scheduling syntax, environment pitfalls, and practical patterns for scheduled automation.

Cron Syntax

The five-field schedule — minute hour day-of-month month day-of-week
# ┌───────── minute (0-59)
# │ ┌───────── hour (0-23)
# │ │ ┌───────── day of month (1-31)
# │ │ │ ┌───────── month (1-12)
# │ │ │ │ ┌───────── day of week (0-7, 0 and 7 = Sunday)
# │ │ │ │ │
# * * * * * command
Common schedules — the patterns you will actually use
# Every 5 minutes
*/5 * * * * /usr/local/bin/health-check.sh

# Daily at 2:30 AM
30 2 * * * /usr/local/bin/backup.sh

# Every Monday at 8:00 AM
0 8 * * 1 /usr/local/bin/weekly-report.sh

# First day of every month at midnight
0 0 1 * * /usr/local/bin/monthly-audit.sh

# Every weekday (Mon-Fri) at 6:00 PM
0 18 * * 1-5 /usr/local/bin/eod-summary.sh

# Every 15 minutes during business hours
*/15 8-17 * * 1-5 /usr/local/bin/monitor.sh

Crontab Management

Edit, list, and backup the current user’s crontab
# Edit
crontab -e

# List
crontab -l

# List another user's (requires root)
sudo crontab -l -u www-data

# Backup before editing — always
crontab -l > /tmp/crontab-backup-$(date +%Y%m%d)

Environment and Paths

Set environment in crontab — cron runs with a minimal environment
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=evan@example.com

*/10 * * * * /usr/local/bin/check-services.sh

Cron’s default PATH is /usr/bin:/bin. If your script calls node, python3, or anything in /usr/local/bin, it will fail silently unless you set PATH explicitly.

Use absolute paths everywhere — cron does not source your profile
#!/bin/bash
# WRONG — relies on PATH from .bashrc
npm run build

# CORRECT — absolute paths
/usr/bin/npm run build

Logging and Debugging

Redirect output — cron emails stdout/stderr by default
# Log both stdout and stderr
*/5 * * * * /usr/local/bin/task.sh >> /var/log/task.log 2>&1

# Discard output entirely
*/5 * * * * /usr/local/bin/task.sh >/dev/null 2>&1

# Separate error log
*/5 * * * * /usr/local/bin/task.sh >> /var/log/task.log 2>> /var/log/task-err.log
Debug a failing cron job — simulate the cron environment
env -i SHELL=/bin/bash PATH=/usr/bin:/bin HOME="$HOME" /usr/local/bin/task.sh

env -i clears the environment. If your script works interactively but fails in cron, this reveals the difference.

Check cron daemon logs
# systemd journal
journalctl -u cronie --since "1 hour ago"

# Traditional syslog
grep CRON /var/log/syslog

Practical Patterns

Flock — prevent overlapping runs of slow jobs
*/5 * * * * flock -n /tmp/backup.lock /usr/local/bin/backup.sh

flock -n fails immediately if the lock is held. No stale lockfile cleanup needed. Prefer this over manual PID-file locking.

Conditional execution — only run if a precondition is met
# Only back up if the NFS mount is available
0 3 * * * mountpoint -q /mnt/backup && /usr/local/bin/backup.sh

# Only run on the primary node
0 * * * * [ "$(hostname)" = "primary" ] && /usr/local/bin/replicate.sh
Cron vs systemd timers — when to choose which
Use cron when:
  - Simple schedule, simple command
  - Portability across Unix systems matters
  - No dependency on other services

Use systemd timers when:
  - You need logging integration (journalctl)
  - The job depends on other units (After=network.target)
  - You want OnCalendar= expressions (more readable)
  - You need RandomizedDelaySec= to avoid thundering herd

See Also

  • systemd Timers — modern alternative with journalctl integration

  • Linux Cron — cron from the Linux administration perspective