Guide Bash-Scripting Advanced

Cron & Scheduling

Writing crontab schedules, and the environment/PATH pitfalls that make scripts behave differently under cron than in an interactive shell.

3 min read

The Crontab Format

* * * * * command
│ │ │ │ │
│ │ │ │ └── day of week (0-6, Sunday=0)
│ │ │ └──── month (1-12)
│ │ └────── day of month (1-31)
│ └──────── hour (0-23)
└────────── minute (0-59)
crontab -e          # edit YOUR crontab
crontab -l            # list your current crontab entries
crontab -r              # remove your entire crontab (careful — no confirmation)
sudo crontab -u deploy -e   # edit another user's crontab

Common Schedule Examples

0 2 * * *        /opt/scripts/backup.sh          # daily at 2:00 AM
*/15 * * * *      /opt/scripts/healthcheck.sh       # every 15 minutes
0 0 * * 0          /opt/scripts/weekly_report.sh      # every Sunday at midnight
0 9-17 * * 1-5      /opt/scripts/business_hours.sh       # hourly, 9 AM-5 PM, weekdays only
0 0 1 * *            /opt/scripts/monthly_cleanup.sh        # 1st of every month
@reboot                /opt/scripts/startup.sh                  # once, at system boot

Why Cron Scripts “Work in the Terminal But Not in Cron”

This is the single most common cron issue. Cron runs jobs with a minimal, non-interactive, non-login shell environment — it does NOT source .bashrc/.bash_profile, and $PATH is typically just /usr/bin:/bin.

# BAD: relies on PATH having picked up nvm/pyenv/custom tool locations
0 2 * * * backup.sh

# GOOD: use absolute paths for the interpreter and every tool the script calls
0 2 * * * /usr/bin/bash /opt/scripts/backup.sh

# GOOD: explicitly set PATH and other environment inside the script itself
#!/usr/bin/env bash
export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
source /home/deploy/.env

Always Redirect Output — Cron Emails or Silently Drops It

0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

Without explicit redirection, cron either emails output to the crontab owner (if mail is configured, which is often not the case on modern servers) or silently discards it — leaving you with no record of failures.

Preventing Overlapping Runs

*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /opt/scripts/sync.sh

flock -n (non-blocking) ensures a new invocation exits immediately if the previous one is still running, rather than stacking up overlapping jobs — critical for tasks that might occasionally run longer than the schedule interval.

systemd Timers: The Modern Alternative

# /etc/systemd/system/backup.timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
systemctl list-timers                 # see all scheduled timers and next run
sudo systemctl enable --now backup.timer
journalctl -u backup.service            # logs integrated with the systemd journal, unlike plain cron

Persistent=true catches up a missed run after downtime (e.g., the server was off at 2 AM) — plain cron has no equivalent.

Testing a Cron Script Properly

# Simulate cron's minimal environment before trusting a script will work when scheduled
env -i /usr/bin/bash /opt/scripts/backup.sh

Production Considerations

  • Always use absolute paths (for both the interpreter and any tools/binaries the script calls) in cron jobs — never rely on an assumed $PATH.
  • Always redirect cron job output explicitly to a log file (>> log 2>&1) — don’t rely on cron’s mail delivery, which is frequently unconfigured on modern servers.
  • Prefer systemd timers over cron for new work when the target systems use systemd — better logging integration, dependency support, and Persistent=true catch-up semantics.

Quick Interview Answer

“Cron’s five fields are minute, hour, day-of-month, month, day-of-week. The classic cron gotcha is that jobs run with a minimal, non-interactive environment — no .bashrc, and a bare-bones $PATH — so scripts that work fine interactively often fail under cron unless they use absolute paths and set up their own environment explicitly. Always redirect output to a log file, and use flock -n to prevent overlapping runs of a job that might occasionally run longer than its schedule interval.”

Common Mistakes

  • Assuming a cron job has the same $PATH and environment variables as an interactive shell.
  • Not redirecting cron job output, leaving no record when something silently fails.
  • Scheduling a job frequently enough that overlapping runs are possible, without a lock (flock) to prevent it.

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form