Cron and Scheduling
Schedule scripts with crontab -e, understand the 5-field cron syntax, use @reboot and @daily shortcuts, and account for cron's minimal environment.
- Write crontab entries with the correct 5-field syntax
- Use @reboot, @daily, and other schedule shortcuts
- Explain why scripts that work interactively can fail in cron
- Schedule one-off jobs with the at command
- Redirect cron output to logs to capture errors
Scripts that work interactively are only half the value. The other half is automation: running them on a schedule without a human present. cron is the Unix standard for scheduled jobs — battle-tested, available everywhere, and simple once you know its quirks. Understanding cron also helps you understand why CI/CD pipelines and cloud schedulers work the way they do: they are, at some level, cron's spiritual descendants.
crontab basics
Each user has a crontab (cron table) — a file listing scheduled jobs. Edit it with:
crontab -e # open the editor
crontab -l # list current entries
crontab -r # remove all entries (careful — no confirmation)Each line in the crontab is a scheduled job with this format:
# min hour day month weekday command
* * * * * /path/to/script.shThe 5-field syntax
| Field | Range | Special values |
|---|---|---|
| minute | 0–59 | * = every minute |
| hour | 0–23 | */2 = every 2 hours |
| day of month | 1–31 | 1 = first of the month |
| month | 1–12 (or jan–dec) | * = every month |
| day of week | 0–7 (0 and 7 = Sunday) | 1-5 = weekdays |
Common patterns:
# Every day at 2:30 AM
# 30 2 * * * /home/user/backup.sh
# Every 15 minutes
# */15 * * * * /usr/local/bin/health-check.sh
# Weekdays at 9 AM
# 0 9 * * 1-5 /home/user/daily-report.sh
# First day of every month at midnight
# 0 0 1 * * /home/user/monthly-cleanup.sh
# Every Sunday at 6 AM
# 0 6 * * 0 /home/user/weekly-snapshot.shUse crontab.guru to verify your syntax interactively. Paste any cron expression and it shows you exactly when it will fire, which eliminates the classic "I thought * meant every hour" mistakes.
Schedule shortcuts
For common intervals, cron supports shorthand:
# @reboot /home/user/start-service.sh # runs once at system boot
# @yearly /home/user/annual-archive.sh # once per year (0 0 1 1 *)
# @monthly /home/user/monthly-cleanup.sh # once per month (0 0 1 * *)
# @weekly /home/user/weekly-report.sh # once per week (0 0 * * 0)
# @daily /home/user/daily-backup.sh # once per day (0 0 * * *)
# @hourly /home/user/health-check.sh # once per hour (0 * * * *)Cron's minimal environment
The most common cron pitfall: cron runs with a minimal environment. Your interactive shell has ~/.bashrc, ~/.profile, and a full PATH. Cron has almost none of that.
# BAD — 'python3' may not be found because PATH is minimal in cron
# 0 2 * * * python3 /home/user/backup.py
# GOOD — use absolute paths for everything
# 0 2 * * * /usr/bin/python3 /home/user/backup.py
# ALSO GOOD — set PATH at the top of the crontab
# PATH=/usr/local/bin:/usr/bin:/bin
# 0 2 * * * python3 /home/user/backup.pyOther environment differences to be aware of:
HOMEis set, but~expansion in arguments may not work as expectedDISPLAYis not set (no GUI operations)- Mail is sent to the user for any output (stdout or stderr) — silence output you don't need
Capturing output
Cron emails stdout/stderr to the user (if a mail agent is configured) or silently discards them. Always redirect to a log file:
# Redirect both stdout and stderr to a log file
# 0 2 * * * /home/user/backup.sh >> /home/user/logs/backup.log 2>&1
# Discard output entirely (not recommended for production)
# 0 2 * * * /home/user/backup.sh > /dev/null 2>&1The at command: one-off scheduling
at schedules a command to run once at a specified time:
at 2:30 PM
> /home/user/report.sh
> Ctrl-D # submit the job
at now + 1 hour
> echo "An hour has passed" | mail -s "Reminder" user@example.com
> Ctrl-D
atq # list pending at jobs
atrm 3 # remove job number 3at may not be installed by default on all systems. Install with apt-get install at (Debian/Ubuntu) or yum install at (RHEL/CentOS). On systemd systems, systemd-run is a modern alternative with better logging and dependency management.
When the agent's away
For automation that runs without a human, add these safeguards to your scheduled scripts:
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="/var/log/myscript.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOGFILE"; }
log "Starting"
# ... your work ...
log "Done"This way, even when cron runs silently, you have a timestamped audit trail.
Check your understanding
- 1.Which cron entry runs a script every 30 minutes, every day?
- 2.A script runs fine interactively but fails silently in cron. What is the most likely cause?
- 3.@reboot in a crontab runs the job once when the cron daemon itself is restarted, not when the system boots.
Do it yourself
# View your current crontab (may be empty)
crontab -l
# Add a test job: write a timestamp every minute for testing
# (REMOVE IT AFTER TESTING)
# Edit with: crontab -e
# Add: * * * * * echo "$(date)" >> /tmp/cron-test.log
# After adding, wait a couple of minutes then check:
# cat /tmp/cron-test.log
# Check cron system logs (may need sudo)
grep CRON /var/log/syslog 2>/dev/null | tail -10
# or on systemd:
journalctl -u cron --since "1 hour ago" 2>/dev/null | tail -10Where to go next
Cron handles time-based scheduling. The next lesson — Makefiles — shows how make works as a general-purpose task runner for any project, not just C compilation.