Code of the Day
AdvancedAutomation

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.

BashAdvanced10 min read
Recommended first
By the end of this lesson you will be able to:
  • 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. 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.sh

The 5-field syntax

FieldRangeSpecial values
minute0–59* = every minute
hour0–23*/2 = every 2 hours
day of month1–311 = first of the month
month1–12 (or jan–dec)* = every month
day of week0–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.sh

Use 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.py

Other environment differences to be aware of:

  • HOME is set, but ~ expansion in arguments may not work as expected
  • DISPLAY is 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>&1

The 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 3

at 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. 1.
    Which cron entry runs a script every 30 minutes, every day?
  2. 2.
    A script runs fine interactively but fails silently in cron. What is the most likely cause?
  3. 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 -10

Where 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.

Finished reading? Mark it complete to track your progress.

On this page