Kahibaro
Discord Login Register

Scheduled tasks (cron)

Why Use cron for Scheduled Tasks?

cron is the classic Unix tool for running commands on a schedule. For shell scripters, it’s what you use when you want:

This chapter focuses on how to use cron correctly with shell scripts, and how to avoid the common pitfalls.

cron vs at vs systemd timers (briefly)

In many modern systems, systemd timers are an alternative; another chapter may cover those. Here we focus on cron, but you should be aware:

On most distributions, cron still exists and is widely used, especially for per-user schedules and legacy scripts.

cron Basics: Daemon and Crontab Files

cron runs as a background service (crond, cronie, or similar). It reads configuration from:

As a script author, you mainly work with user crontabs and occasionally /etc/crontab.

crontab: Managing Per-User Schedules

Use the crontab command (note: not the same as the file /etc/crontab):

Each user can have their own crontab, and jobs run as that user.

Example to edit your crontab:

bash
crontab -e

The first time, you may be asked which editor to use.

The cron Time Format

Each line in a crontab (ignoring comments and environment lines) looks like:

text
MIN HOUR DOM MON DOW COMMAND

Where:

Example: run a script every day at 02:30:

text
30 2 * * * /home/alex/bin/backup.sh

Common syntax elements:

Examples:

text
  */5 * * * * /path/to/script.sh
text
  0 9 * * 1-5 /path/to/script.sh
text
  0 0 1 1 * /path/to/script.sh

Named months and weekdays

Instead of numbers, you can use short names (case-insensitive):

Example:

text
0 7 * * mon-fri /path/to/workday-report.sh

Special strings (@reboot, @daily, …)

Many cron implementations support shortcuts:

Example:

text
@reboot /home/alex/bin/startup-tasks.sh
@daily /home/alex/bin/rotate-logs.sh

Environment Differences in cron

A crucial point for shell scripts: cron does not run with your interactive shell environment. This is the source of many “it works in the terminal but not in cron” issues.

Key differences:

Setting SHELL, PATH, and other variables

You can define environment variables at the top of your crontab:

text
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=alex

Example crontab with environment and entry:

text
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
0 3 * * * /home/admin/bin/backup-databases.sh

You can also set custom variables that your scripts can read:

text
BACKUP_DIR=/srv/backups
0 3 * * * /home/admin/bin/backup.sh

Inside backup.sh, you can access $BACKUP_DIR.

Using absolute paths

Always use absolute paths to:

Relying on relative paths (./script.sh, logs/output.txt) usually breaks under cron.

Integrating cron with Shell Scripts

For advanced scripts, cron is just the scheduler. Your main work happens inside the script. To make scripts cron-friendly:

1. Use a proper shebang

At the top of your script:

bash
#!/usr/bin/env bash

or

bash
#!/bin/bash

Then make the script executable:

bash
chmod +x /home/alex/bin/backup.sh

Crontab entry:

text
0 2 * * * /home/alex/bin/backup.sh

2. Initialize environment in the script

If your script relies on environment variables or profiles, source them explicitly:

bash
#!/bin/bash
# Example: source a profile only if it exists
[ -f "$HOME/.profile" ] && . "$HOME/.profile"
# Or define required variables directly
export PATH=/usr/local/bin:/usr/bin:/bin
export APP_ENV=production

Don’t assume your interactive shell configuration is loaded.

3. Set working directory

If your script assumes a certain working directory:

bash
#!/bin/bash
cd /home/alex/myproject || exit 1
# Now relative paths are safe inside the script
./run-report.sh

Or always use absolute paths and avoid reliance on cd.

Redirecting Output and Logs

By default, cron sends stdout and stderr of each job via email to the user (or MAILTO recipient), if local mail is configured. On many desktops, local mail isn’t set up.

For reliable logs:

Examples:

Basic logging to a file

text
0 1 * * * /home/alex/bin/cleanup.sh >> /home/alex/logs/cleanup.log 2>&1

This:

Daily log overwrite

If you don’t want logs to grow forever:

text
0 1 * * * /home/alex/bin/cleanup.sh > /home/alex/logs/cleanup.log 2>&1

This overwrites the log each run.

Timestamped logs (from script)

In your script, prepend timestamps:

bash
#!/bin/bash
log() {
    printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
log "Starting cleanup"
# ...
log "Cleanup finished"

Run from cron with:

text
0 1 * * * /home/alex/bin/cleanup.sh >> /home/alex/logs/cleanup.log 2>&1

System-Wide cron: /etc/crontab and cron.* Directories

While user crontabs are common, as an admin or advanced user you may also touch:

/etc/crontab

System-wide crontab usually includes an extra field for the user:

text
MIN HOUR DOM MON DOW USER COMMAND

Example:

text
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Run a backup as root at 3 AM
0 3 * * * root /usr/local/sbin/system-backup.sh

Use this when you need jobs that must run as specific system users (e.g. root, postgres, www-data).

/etc/cron.hourly, /etc/cron.daily, etc.

These directories are for simple scripts run at fixed frequencies, managed by a system cron job or systemd timer.

Common directories:

Typical usage:

bash
  sudo nano /etc/cron.daily/logrotate-extra
  sudo chmod +x /etc/cron.daily/logrotate-extra

As a shell scripter, you might package maintenance scripts here for system-level tasks.

Access Control: cron.allow and cron.deny

On multi-user systems, cron usage may be restricted via:

These are plain text; one username per line.

Example /etc/cron.allow:

text
root
admin
backup

Check your system’s documentation (man 5 crontab) for exact behavior, as implementations vary.

Advanced Scheduling Patterns

For complex schedules, combine ranges, lists, and steps.

Examples

text
  */15 9-17 * * 1-5 /path/to/script.sh

There’s no single cron expression for this, but you can approximate with:

text
  0 9 1-7 * 1 /path/to/script.sh

This runs at 09:00 on days 1–7 if it’s Monday; only one of those days will be the first Monday.

text
  0,30 * * * * /path/to/script.sh
text
  5-55/10 * * * * /path/to/script.sh

Coordinating cron with Script Logic

Advanced shell scripts often need to be cron-safe:

1. Prevent overlapping runs (locking)

If your job takes longer than the interval, you can get overlaps. Use a lock:

bash
#!/bin/bash
LOCKFILE=/var/lock/myjob.lock
exec 9>"$LOCKFILE" || exit 1
if ! flock -n 9; then
    echo "Another instance is running, exiting."
    exit 1
fi
# Your job logic here

Crontab:

text
*/5 * * * * /usr/local/bin/myjob.sh >> /var/log/myjob.log 2>&1

Now, if one run is still working, the next run exits gracefully.

2. Conditional execution based on day/time

Sometimes it’s easier to run frequently and check conditions in the script:

bash
#!/bin/bash
hour=$(date +%H)
if [ "$hour" -ge 9 ] && [ "$hour" -lt 17 ]; then
    # Work-hours logic
    run_report
fi

Cron:

text
*/5 * * * * /home/alex/bin/report.sh

3. Exit codes for monitoring

Your script should use meaningful exit codes:

This allows monitoring systems or wrapper scripts to detect failures.

Troubleshooting cron Jobs

When a script doesn’t run as expected under cron:

  1. Check cron logs

Depending on distro:

Look for lines stating that the job was executed (or errors like “bad minute”).

  1. Log everything

Temporarily redirect output and debug info:

text
   * * * * * /home/alex/bin/test.sh >> /tmp/test-cron.log 2>&1
  1. Simulate cron environment

Run your script with a minimal environment:

bash
   env -i /bin/bash -c '/home/alex/bin/script.sh'

or explicitly set some minimal variables:

bash
   env -i PATH=/usr/bin:/bin HOME="$HOME" /bin/bash -c '/home/alex/bin/script.sh'
  1. Test commands with absolute paths

Inside your script, temporarily log:

bash
   which rsync
   echo "PATH is: $PATH"
   pwd
   env

Then inspect the log generated by cron.

  1. Validate your crontab syntax

A single invalid line can make cron ignore that job or more, depending on implementation. Use:

bash
   crontab -l

and check for typos like:

Security Considerations

When scheduling scripts with cron:

bash
    chmod 700 /home/alex/bin/backup.sh
    chmod 600 /home/alex/logs/backup.log

Explicitly set PATH and other variables in the script or crontab to prevent environment-based attacks (e.g., malicious binaries earlier in PATH).

If your script processes files or data from untrusted sources, don’t assume everything is safe just because it runs unattended.

Practical Examples

Example 1: Nightly backup via cron

Script: /home/backup/bin/backup-home.sh:

bash
#!/bin/bash
set -euo pipefail
SRC=/home
DEST=/srv/backups/home
LOG=/var/log/home-backup.log
mkdir -p "$DEST"
{
    echo "===== $(date '+%Y-%m-%d %H:%M:%S') Starting backup ====="
    rsync -a --delete "$SRC"/ "$DEST"/
    echo "Backup completed successfully."
} >> "$LOG" 2>&1

Make executable:

bash
chmod 700 /home/backup/bin/backup-home.sh

Crontab for user backup (crontab -e as that user):

text
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=backup-admin@example.com
0 2 * * * /home/backup/bin/backup-home.sh

Example 2: Run a monitoring script every 5 minutes

Script: /usr/local/bin/check-disk-usage.sh:

bash
#!/bin/bash
THRESHOLD=90
EMAIL=admin@example.com
HOST=$(hostname)
usage=$(df -h / | awk 'NR==2 {gsub("%","",$5); print $5}')
if [ "$usage" -ge "$THRESHOLD" ]; then
    echo "Disk usage on $HOST is ${usage}% (>= ${THRESHOLD}%)." \
      | mail -s "Disk usage warning on $HOST" "$EMAIL"
fi

Crontab for root (sudo crontab -e):

text
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /usr/local/bin/check-disk-usage.sh >> /var/log/check-disk-usage.log 2>&1

Summary

With these practices, your advanced shell scripts will run reliably and predictably under cron.

Views: 21

Comments

Please login to add a comment.

Don't have an account? Register now!