Skip to main content
Linux·10 min read

Cron Jobs in Linux — 15 Examples That Run Reliably in Production

Practical cron job examples for backups, log rotation, monitoring, and maintenance tasks. Covers crontab syntax, common schedules, error handling, and debugging cron failures.

DT

DevOps Engineer & Technical Writer

The Problem

Cron Job Execution Flow ┌─min ┌─hour ┌─day ┌─month ┌─dow 30 2 * * * /opt/scripts/backup.sh crond Daemon PID 1 child process Crontab Files /var/spool/cron/ Schedule Check Matches time fields Execute Forks child process Log Output /var/log/syslog MAILTO Email on output Reads every minute User + system crontabs stdout/stderr captured

You need a database backup every night at 2 AM, log cleanup every Sunday, SSL certificate renewal checks twice daily, and a health check endpoint pinged every 5 minutes. Cron is the standard Linux scheduler, but its syntax is cryptic, its error handling is non-existent by default, and debugging silent failures wastes hours.

Crontab Syntax

┌───────────── minute (0-59)

│ ┌───────────── hour (0-23)

│ │ ┌───────────── day of month (1-31)

│ │ │ ┌───────────── month (1-12)

│ │ │ │ ┌───────────── day of week (0-7, 0 and 7 = Sunday)

│ │ │ │ │

* command_to_execute

Special characters

CharacterMeaningExample
<code class="inline-code"><em></code>Every value<code class="inline-code"></em> <em> </em> <em> </em></code> = every minute
<code class="inline-code">,</code>List<code class="inline-code">1,15,30</code> = at minutes 1, 15, and 30
<code class="inline-code">-</code>Range<code class="inline-code">9-17</code> = hours 9 through 17
<code class="inline-code">/</code>Step<code class="inline-code">*/5</code> = every 5 units

Managing crontab

crontab -e                      # Edit your crontab

crontab -l # List your cron jobs

sudo crontab -e -u deploy # Edit for specific user

crontab -r # Remove all cron jobs

crontab /path/to/crontab-file # Load from file

Common Schedule Patterns

    * /opt/scripts/health-check.sh               # Every minute

/5 * /opt/scripts/check-queue.sh # Every 5 minutes

0 /opt/scripts/sync-data.sh # Every hour

30 2 * /opt/scripts/backup.sh # Daily at 2:30 AM

0 9 1 /opt/scripts/weekly-report.sh # Monday 9 AM

0 18 1-5 /opt/scripts/daily-summary.sh # Weekdays 6 PM

0 0 1 /opt/scripts/monthly-cleanup.sh # First of month

/15 9-17 * 1-5 /opt/scripts/monitor.sh # Every 15 min, business hours

0 6,18 * /opt/scripts/cert-check.sh # Twice daily

0 3 0 /opt/scripts/weekly-maintenance.sh # Sunday 3 AM

0 0 1 1,4,7,10 * /opt/scripts/quarterly-audit.sh # Quarterly

Special strings

@reboot     /opt/scripts/startup.sh      # Run once at startup

@hourly /opt/scripts/hourly.sh # 0

@daily /opt/scripts/daily.sh # 0 0 *

@weekly /opt/scripts/weekly.sh # 0 0 0

@monthly /opt/scripts/monthly.sh # 0 0 1

Production Cron Job Examples

Database backup with retention

#!/bin/bash

set -euo pipefail

BACKUP_DIR="/backups/postgres"

RETENTION_DAYS=14

TIMESTAMP=$(date +%Y%m%d_%H%M%S)

DB_NAME="production_app"

pg_dump -Fc "$DB_NAME" > "${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.dump"

if [ ! -s "${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.dump" ]; then

echo "ERROR: Backup file is empty" >&2

exit 1

fi

find "$BACKUP_DIR" -name "*.dump" -mtime +${RETENTION_DAYS} -delete

echo "Backup completed: ${DB_NAME}_${TIMESTAMP}.dump"

30 2   * /opt/scripts/backup-postgres.sh >> /var/log/cron/backup.log 2>&1

Log rotation and cleanup

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

0 2 find /var/log/myapp -name ".log" -mtime +1 ! -name "*.gz" -exec gzip {} \;

SSL certificate expiry check

#!/bin/bash

set -euo pipefail

DOMAINS=("app.example.com" "api.example.com" "admin.example.com")

ALERT_DAYS=14

for domain in "${DOMAINS[@]}"; do

expiry=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | \

openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)

if [ -z "$expiry" ]; then

echo "WARNING: Could not check $domain"

continue

fi

expiry_epoch=$(date -d "$expiry" +%s)

now_epoch=$(date +%s)

days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

if [ "$days_left" -lt "$ALERT_DAYS" ]; then

echo "ALERT: $domain SSL expires in $days_left days ($expiry)"

fi

done

0 6,18   * /opt/scripts/check-ssl.sh | mail -s "SSL Check" ops@company.com

Docker cleanup

0 4   * docker system prune -f >> /var/log/cron/docker-cleanup.log 2>&1

0 4 0 docker system prune -af --volumes >> /var/log/cron/docker-cleanup.log 2>&1

Disk space monitoring

/10    * df -h | awk '$5+0 > 85 {print "DISK ALERT: "$6" at "$5}' | mail -s "Disk Space Alert $(hostname)" ops@company.com

Error Handling and Logging

Redirect output

# Both stdout and stderr to log

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

# With timestamp

30 2 * /opt/scripts/backup.sh 2>&1 | ts '[%Y-%m-%d %H:%M:%S]' >> /var/log/cron/backup.log

Prevent overlapping runs with flock

/5    * /usr/bin/flock -n /tmp/queue-worker.lock /opt/scripts/process-queue.sh

/5 * /usr/bin/flock -w 300 /tmp/sync.lock /opt/scripts/sync-data.sh

Set environment variables

# Set at top of crontab

SHELL=/bin/bash

PATH=/usr/local/bin:/usr/bin:/bin

MAILTO=ops@company.com

HOME=/home/deploy

# Or source profile in command

30 2 * source /home/deploy/.profile && /opt/scripts/backup.sh

Error notification pattern

#!/bin/bash

set -euo pipefail

LOG_FILE="/var/log/cron/backup-$(date +%Y%m%d).log"

run_backup() {

echo "[$(date)] Starting backup..."

pg_dump -Fc production > /backups/production_$(date +%Y%m%d).dump

echo "[$(date)] Backup completed successfully"

}

if ! run_backup >> "$LOG_FILE" 2>&1; then

echo "Backup FAILED at $(date). Check $LOG_FILE" | \

mail -s "CRITICAL: Backup Failed on $(hostname)" ops@company.com

exit 1

fi

Debugging Cron Failures

# Check if cron is running

systemctl status cron # Debian/Ubuntu

systemctl status crond # RHEL/CentOS

# Check cron logs

grep CRON /var/log/syslog

grep CRON /var/log/cron

journalctl -u cron --since today

# Test with cron's environment

sudo -u deploy env -i /bin/bash -c '/opt/scripts/backup.sh'

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

# Common fixes

chmod +x /opt/scripts/backup.sh # Script not executable

# Use full paths: /usr/bin/pg_dump not pg_dump

# Escape percent: date +\%Y\%m\%d not date +%Y%m%d

Common Mistakes

  • Not using full paths — Cron has a minimal PATH. Use /usr/bin/python3 not python3.
  • Forgetting to redirect output — Unredirected output goes to local mail. If mail is not configured, output is lost.
  • Not escaping % in crontab — The % character is interpreted as a newline. Escape with \% or put the command in a script.
  • Assuming your shell environment — Cron does not source .bashrc or .profile. Set variables explicitly.
  • No overlap prevention — A slow job can stack up, running multiple instances. Use flock.
  • Not logging the exit code — You cannot debug a failed job if you do not know whether it ran or what error it produced.
  • Quick Reference

    ScheduleCrontab Expression
    Every minute<code class="inline-code"><em> </em> <em> </em> *</code>
    Every 5 minutes<code class="inline-code"><em>/5 </em> <em> </em> *</code>
    Every hour<code class="inline-code">0 <em> </em> <em> </em></code>
    Daily at 2 AM<code class="inline-code">0 2 <em> </em> *</code>
    Weekdays at 9 AM<code class="inline-code">0 9 <em> </em> 1-5</code>
    Every Sunday 3 AM<code class="inline-code">0 3 <em> </em> 0</code>
    First of month<code class="inline-code">0 0 1 <em> </em></code>
    Every 15 min, business hours<code class="inline-code"><em>/15 9-17 </em> * 1-5</code>
    Twice daily<code class="inline-code">0 6,18 <em> </em> *</code>
    On reboot<code class="inline-code">@reboot</code>

    Summary

    Cron is reliable but unforgiving. Always use full paths, redirect output to log files, prevent overlapping runs with flock, and test commands manually with cron's minimal environment before adding them to crontab. For critical jobs, add alerting so you know immediately when something fails.

    ---

    Frequently Asked Questions

    How do I edit the crontab in Linux?

    Run crontab -e to open your user's crontab file in the default editor. Each line follows the format minute hour day month weekday command. Use crontab -l to list existing cron jobs and crontab -r to remove all entries. For system-wide jobs, edit files in /etc/cron.d/ instead.

    What does the cron expression "0 /2 " mean?

    This runs the command at minute 0 of every 2nd hour (00:00, 02:00, 04:00, etc.). The /2 means "every 2 units" in the hour field. Common patterns: /5 (every 5 minutes), 0 0 (daily at midnight), 0 0 * 0 (weekly on Sunday at midnight).

    Why is my cron job not running?

    Common causes include: the script lacks a full path to commands (cron has a minimal PATH), file permissions aren't set to executable, environment variables aren't loaded (cron doesn't source .bashrc), or the cron service isn't running. Check /var/log/syslog or /var/log/cron for execution logs and errors.

    How do I redirect cron job output to a log file?

    Append >> /var/log/myjob.log 2>&1 to your crontab entry to capture both stdout and stderr. Use > instead of >> to overwrite rather than append. To suppress all output, redirect to /dev/null: * /path/script.sh > /dev/null 2>&1. For emailed output, ensure a local MTA is configured.

    What is the difference between crontab and systemd timers?

    Crontab is simpler with a one-line-per-job format, while systemd timers offer more features like random delays, dependency management, and resource controls through service units. Systemd timers integrate with journald for logging and can trigger on events beyond time (boot, socket activation). Use crontab for simple scheduled tasks and systemd timers for complex service management.

    ---