🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Cron & Scheduling

by @gitgoodordietrying

Schedule and manage recurring tasks with cron and systemd timers. Use when setting up cron jobs, writing systemd timer units, handling timezone-aware scheduling, monitoring failed jobs, implementing retry patterns, or debugging why a scheduled task didn't run.

Versionv1.0.0
Downloads9,485
Installs76
Stars⭐ 7
TERMINAL
clawhub install cron-scheduling

πŸ“– About This Skill


name: cron-scheduling description: Schedule and manage recurring tasks with cron and systemd timers. Use when setting up cron jobs, writing systemd timer units, handling timezone-aware scheduling, monitoring failed jobs, implementing retry patterns, or debugging why a scheduled task didn't run. metadata: {"clawdbot":{"emoji":"⏰","requires":{"anyBins":["crontab","systemctl","at"]},"os":["linux","darwin"]}}

Cron & Scheduling

Schedule and manage recurring tasks. Covers cron syntax, crontab management, systemd timers, one-off scheduling, timezone handling, monitoring, and common failure patterns.

When to Use

  • Running scripts on a schedule (backups, reports, cleanup)
  • Setting up systemd timers (modern cron alternative)
  • Debugging why a scheduled job didn't run
  • Handling timezones in scheduled tasks
  • Monitoring and alerting on job failures
  • Running one-off delayed commands
  • Cron Syntax

    The five fields

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ minute (0-59)
    β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€ hour (0-23)
    β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€ day of month (1-31)
    β”‚ β”‚ β”‚ β”Œβ”€β”€β”€ month (1-12 or JAN-DEC)
    β”‚ β”‚ β”‚ β”‚ β”Œβ”€ day of week (0-7, 0 and 7 = Sunday, or SUN-SAT)
    β”‚ β”‚ β”‚ β”‚ β”‚
    * * * * * command
    

    Common schedules

    # Every minute
    * * * * * /path/to/script.sh

    Every 5 minutes

    */5 * * * * /path/to/script.sh

    Every hour at :00

    0 * * * * /path/to/script.sh

    Every day at 2:30 AM

    30 2 * * * /path/to/script.sh

    Every Monday at 9:00 AM

    0 9 * * 1 /path/to/script.sh

    Every weekday at 8:00 AM

    0 8 * * 1-5 /path/to/script.sh

    First day of every month at midnight

    0 0 1 * * /path/to/script.sh

    Every 15 minutes during business hours (Mon-Fri 9-17)

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

    Twice a day (9 AM and 5 PM)

    0 9,17 * * * /path/to/script.sh

    Every quarter (Jan, Apr, Jul, Oct) on the 1st at midnight

    0 0 1 1,4,7,10 * /path/to/script.sh

    Every Sunday at 3 AM

    0 3 * * 0 /path/to/script.sh

    Special strings (shorthand)

    @reboot    /path/to/script.sh   # Run once at startup
    @yearly    /path/to/script.sh   # 0 0 1 1 *
    @monthly   /path/to/script.sh   # 0 0 1 * *
    @weekly    /path/to/script.sh   # 0 0 * * 0
    @daily     /path/to/script.sh   # 0 0 * * *
    @hourly    /path/to/script.sh   # 0 * * * *
    

    Crontab Management

    # Edit current user's crontab
    crontab -e

    List current crontab

    crontab -l

    Edit another user's crontab (root)

    sudo crontab -u www-data -e

    Remove all cron jobs (be careful!)

    crontab -r

    Install crontab from file

    crontab mycrontab.txt

    Backup crontab

    crontab -l > crontab-backup-$(date +%Y%m%d).txt

    Crontab best practices

    # Set PATH explicitly (cron has minimal PATH)
    PATH=/usr/local/bin:/usr/bin:/bin

    Set MAILTO for error notifications

    MAILTO=admin@example.com

    Set shell explicitly

    SHELL=/bin/bash

    Full crontab example

    PATH=/usr/local/bin:/usr/bin:/bin MAILTO=admin@example.com SHELL=/bin/bash

    Backups

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

    Cleanup old logs

    0 3 * * 0 find /var/log/myapp -name "*.log" -mtime +30 -delete

    Health check

    */5 * * * * /opt/scripts/healthcheck.sh || /opt/scripts/alert.sh "Health check failed"

    Systemd Timers

    Create a timer (modern cron replacement)

    # /etc/systemd/system/backup.service
    [Unit]
    Description=Daily backup

    [Service] Type=oneshot ExecStart=/opt/scripts/backup.sh User=backup StandardOutput=journal StandardError=journal

    # /etc/systemd/system/backup.timer
    [Unit]
    Description=Run backup daily at 2 AM

    [Timer] OnCalendar=*-*-* 02:00:00 Persistent=true RandomizedDelaySec=300

    [Install] WantedBy=timers.target

    # Enable and start the timer
    sudo systemctl daemon-reload
    sudo systemctl enable --now backup.timer

    Check timer status

    systemctl list-timers systemctl list-timers --all

    Check last run

    systemctl status backup.service journalctl -u backup.service --since today

    Run manually (for testing)

    sudo systemctl start backup.service

    Disable timer

    sudo systemctl disable --now backup.timer

    OnCalendar syntax

    # Systemd calendar expressions

    Daily at midnight

    OnCalendar=daily

    or: OnCalendar=*-*-* 00:00:00

    Every Monday at 9 AM

    OnCalendar=Mon *-*-* 09:00:00

    Every 15 minutes

    OnCalendar=*:0/15

    Weekdays at 8 AM

    OnCalendar=Mon..Fri *-*-* 08:00:00

    First of every month

    OnCalendar=*-*-01 00:00:00

    Every 6 hours

    OnCalendar=0/6:00:00

    Specific dates

    OnCalendar=2026-02-03 12:00:00

    Test calendar expressions

    systemd-analyze calendar "Mon *-*-* 09:00:00" systemd-analyze calendar "*:0/15" systemd-analyze calendar --iterations=5 "Mon..Fri *-*-* 08:00:00"

    Advantages over cron

    Systemd timers vs cron:
    + Logs in journald (journalctl -u service-name)
    + Persistent: catches up on missed runs after reboot
    + RandomizedDelaySec: prevents thundering herd
    + Dependencies: can depend on network, mounts, etc.
    + Resource limits: CPUQuota, MemoryMax, etc.
    + No lost-email problem (MAILTO often misconfigured)
    
  • More files to create (service + timer)
  • More verbose configuration
  • One-Off Scheduling

    at (run once at a specific time)

    # Schedule a command
    echo "/opt/scripts/deploy.sh" | at 2:00 AM tomorrow
    echo "reboot" | at now + 30 minutes
    echo "/opt/scripts/report.sh" | at 5:00 PM Friday

    Interactive (type commands, Ctrl+D to finish)

    at 10:00 AM > /opt/scripts/task.sh > echo "Done" | mail -s "Task complete" admin@example.com >

    List pending jobs

    atq

    View job details

    at -c

    Remove a job

    atrm

    sleep-based (simplest)

    # Run something after a delay
    (sleep 3600 && /opt/scripts/task.sh) &

    With nohup (survives logout)

    nohup bash -c "sleep 7200 && /opt/scripts/task.sh" &

    Timezone Handling

    # Cron runs in the system timezone by default
    

    Check system timezone

    timedatectl date +%Z

    Set timezone for a specific cron job

    Method 1: TZ variable in crontab

    TZ=America/New_York 0 9 * * * /opt/scripts/report.sh

    Method 2: In the script itself

    #!/bin/bash export TZ=UTC

    All date operations now use UTC

    Method 3: Wrapper

    TZ=Europe/London date '+%Y-%m-%d %H:%M:%S'

    List available timezones

    timedatectl list-timezones timedatectl list-timezones | grep America

    DST pitfalls

    Problem: A job scheduled for 2:30 AM may run twice or not at all
    during DST transitions.

    "Spring forward": 2:30 AM doesn't exist (clock jumps 2:00 β†’ 3:00) "Fall back": 2:30 AM happens twice

    Mitigation: 1. Schedule critical jobs outside 1:00-3:00 AM 2. Use UTC for the schedule: TZ=UTC in crontab 3. Make jobs idempotent (safe to run twice) 4. Systemd timers handle DST correctly

    Monitoring and Debugging

    Why didn't my cron job run?

    # 1. Check cron daemon is running
    systemctl status cron    # Debian/Ubuntu
    systemctl status crond   # CentOS/RHEL

    2. Check cron logs

    grep CRON /var/log/syslog # Debian/Ubuntu grep CRON /var/log/cron # CentOS/RHEL journalctl -u cron --since today # systemd

    3. Check crontab actually exists

    crontab -l

    4. Test the command manually (with cron's environment)

    env -i HOME=$HOME SHELL=/bin/sh PATH=/usr/bin:/bin /opt/scripts/backup.sh

    If it fails here but works normally β†’ PATH or env issue

    5. Check permissions

    ls -la /opt/scripts/backup.sh # Must be executable ls -la /var/spool/cron/ # Crontab file permissions

    6. Check for syntax errors in crontab

    cron silently ignores lines with errors

    7. Check if output is being discarded

    By default, cron emails output. If no MTA, output is lost.

    Always redirect: >> /var/log/myjob.log 2>&1

    Job wrapper with logging and alerting

    #!/bin/bash
    

    cron-wrapper.sh β€” Run a command with logging, timing, and error alerting

    Usage: cron-wrapper.sh [args...]

    set -euo pipefail

    JOB_NAME="${1:?Usage: cron-wrapper.sh [args...]}" shift COMMAND=("$@")

    LOG_DIR="/var/log/cron-jobs" mkdir -p "$LOG_DIR" LOG_FILE="$LOG_DIR/$JOB_NAME.log"

    log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" >> "$LOG_FILE"; }

    log "START: ${COMMAND[*]}" START_TIME=$(date +%s)

    if "${COMMAND[@]}" >> "$LOG_FILE" 2>&1; then ELAPSED=$(( $(date +%s) - START_TIME )) log "SUCCESS (${ELAPSED}s)" else EXIT_CODE=$? ELAPSED=$(( $(date +%s) - START_TIME )) log "FAILED with exit code $EXIT_CODE (${ELAPSED}s)" # Alert (customize as needed) echo "Cron job '$JOB_NAME' failed with exit $EXIT_CODE" | \ mail -s "CRON FAIL: $JOB_NAME" admin@example.com 2>/dev/null || true exit $EXIT_CODE fi

    # Use in crontab:
    0 2 * * * /opt/scripts/cron-wrapper.sh daily-backup /opt/scripts/backup.sh
    */5 * * * * /opt/scripts/cron-wrapper.sh health-check /opt/scripts/healthcheck.sh
    

    Lock to prevent overlap

    # Prevent concurrent runs (job takes longer than interval)
    

    Method 1: flock

    * * * * * flock -n /tmp/myjob.lock /opt/scripts/slow-job.sh

    Method 2: In the script

    LOCKFILE="/tmp/myjob.lock" exec 200>"$LOCKFILE" flock -n 200 || { echo "Already running"; exit 0; }

    ... do work ...

    Idempotent Job Patterns

    # Idempotent backup (only creates if newer than last backup)
    #!/bin/bash
    BACKUP_DIR="/backups/$(date +%Y%m%d)"
    [[ -d "$BACKUP_DIR" ]] && { echo "Backup already exists"; exit 0; }
    mkdir -p "$BACKUP_DIR"
    pg_dump mydb > "$BACKUP_DIR/mydb.sql"

    Idempotent cleanup (safe to run multiple times)

    find /tmp/uploads -mtime +7 -type f -delete 2>/dev/null || true

    Idempotent sync (rsync only transfers changes)

    rsync -az /data/ backup-server:/backups/data/

    Tips

  • Always redirect output in cron jobs: >> /var/log/job.log 2>&1. Without this, output goes to mail (if configured) or is silently lost.
  • Test cron jobs by running them with env -i to simulate cron's minimal environment. Most failures are caused by missing PATH or environment variables.
  • Use flock to prevent overlapping runs when a job might take longer than its schedule interval.
  • Make all scheduled jobs idempotent. If a job runs twice (DST, manual trigger, crash recovery), it should produce the same result.
  • systemd-analyze calendar is invaluable for verifying timer schedules before deploying.
  • Never schedule critical jobs between 1:00 AM and 3:00 AM if DST applies. Use UTC schedules instead.
  • Log the start time, end time, and exit code of every cron job. Without this, debugging failures after the fact is guesswork.
  • Prefer systemd timers over cron for production services: you get journald logging, missed-run catchup (Persistent=true), and resource limits for free.
  • ⚑ When to Use

    TriggerAction
    - Setting up systemd timers (modern cron alternative)
    - Debugging why a scheduled job didn't run
    - Handling timezones in scheduled tasks
    - Monitoring and alerting on job failures
    - Running one-off delayed commands

    πŸ“‹ Tips & Best Practices

  • Always redirect output in cron jobs: >> /var/log/job.log 2>&1. Without this, output goes to mail (if configured) or is silently lost.
  • Test cron jobs by running them with env -i to simulate cron's minimal environment. Most failures are caused by missing PATH or environment variables.
  • Use flock to prevent overlapping runs when a job might take longer than its schedule interval.
  • Make all scheduled jobs idempotent. If a job runs twice (DST, manual trigger, crash recovery), it should produce the same result.
  • systemd-analyze calendar is invaluable for verifying timer schedules before deploying.
  • Never schedule critical jobs between 1:00 AM and 3:00 AM if DST applies. Use UTC schedules instead.
  • Log the start time, end time, and exit code of every cron job. Without this, debugging failures after the fact is guesswork.
  • Prefer systemd timers over cron for production services: you get journald logging, missed-run catchup (Persistent=true), and resource limits for free.