Cron Jobs in Linux: Schedule Tasks with crontab

Cron Jobs in Linux: Schedule Tasks with crontab

Job automation is the process of scheduling commands and scripts to run automatically without manual intervention. Linux provides three tools: cron for recurring scheduled tasks, at for one-time future tasks, and anacron for systems that may not always be running. Mastering these tools is essential for automating backups, log rotation, monitoring checks, and maintenance windows.

Why Automate?

Manual execution of repetitive tasks introduces human error, requires staff availability at inconvenient times (3 AM backups), and creates operational risk when staff are unavailable. Automation ensures:

  • Consistency — same task runs identically every time
  • Reliability — no "forgot to run the backup" incidents
  • Efficiency — tasks run during off-peak hours without human monitoring
  • Auditability — cron logs provide execution records

cron — The Linux Task Scheduler

cron is a daemon (crond) that wakes up every minute, reads all crontab files, and executes any commands whose scheduled time matches the current time.

cron Architecture

  • /etc/crontab: System-wide crontab (has extra USER field)
  • /var/spool/cron/: Per-user crontab files (edited with crontab -e)
  • /etc/cron.d/: Package-installed crontab files
  • /etc/cron.hourly/, daily/, weekly/, monthly/: Directories for scripts
  • /var/log/cron: Execution log

Crontab Format — Complete Breakdown

# Field positions:
# MIN   HOUR   DOM    MON    DOW    COMMAND
# 0-59  0-23   1-31  1-12   0-6

# DOM = Day of Month (1-31)
# MON = Month (1-12 or jan,feb,...,dec)
# DOW = Day of Week (0-6, 0=Sunday; or sun,mon,...,sat)

# Special characters:
# *      = any value (wildcard)
# ,      = list of values (1,3,5)
# -      = range of values (1-5)
# /      = step values (*/5 = every 5)

# @reboot  = run once at startup
# @yearly  = 0 0 1 1 *
# @monthly = 0 0 1 * *
# @weekly  = 0 0 * * 0
# @daily   = 0 0 * * *
# @hourly  = 0 * * * *

Crontab Examples — With Explanations

# Every minute:
* * * * * /scripts/check.sh

# Every 5 minutes:
*/5 * * * * /scripts/monitor.sh

# Every hour at minute 30:
30 * * * * /scripts/hourly.sh

# Every day at 2:30 AM:
30 2 * * * /backup/daily.sh

# Monday through Friday at 9 AM:
0 9 * * 1-5 /scripts/workday_start.sh

# First of every month at midnight:
0 0 1 * * /scripts/monthly_report.sh

# Every Sunday at 3 AM:
0 3 * * 0 /scripts/weekly_cleanup.sh

# At 2:00 AM and 2:00 PM:
0 2,14 * * * /scripts/twice_daily.sh

# Every 15 minutes during business hours (9-17, Mon-Fri):
*/15 9-17 * * 1-5 /scripts/business_check.sh

# On January 1st at midnight (New Year):
0 0 1 1 * /scripts/new_year.sh

# Reboot task:
@reboot /scripts/startup.sh

# Complex: 9:30 PM on the 1st and 15th of every month:
30 21 1,15 * * /scripts/bimonthly.sh

Managing Crontabs

# Edit your crontab (opens in $EDITOR):
# crontab -e

# Edit another user's crontab (root only):
# crontab -e -u raju

# List your crontab:
# crontab -l

# List another user's crontab:
# crontab -l -u raju

# Remove your crontab entirely:
# crontab -r

# The system crontab (/etc/crontab) has an extra USER field:
# MIN HOUR DOM MON DOW USER COMMAND
# 0   2    *   *   *   root /backup/run.sh

Environment Variables in Crontab

# cron runs with a minimal environment (PATH is limited)
# Always use full paths in cron scripts

# Set environment in crontab:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@example.com           # send output to this email
HOME=/root

# Redirect output to log file instead of email:
*/5 * * * * /scripts/check.sh >> /var/log/check.log 2>&1
# >> = append stdout; 2>&1 = redirect stderr to stdout

Cron Access Control

# /etc/cron.allow — whitelist (ONLY listed users can use cron):
# If this file exists, only users in it can use crontab
# If this file doesn't exist, /etc/cron.deny is checked

# /etc/cron.deny — blacklist (listed users CANNOT use cron):
# If a user is in this file, they cannot use crontab

# Logic:
# cron.allow exists: only listed users allowed (even root must be listed)
# cron.allow absent, cron.deny exists: anyone NOT in cron.deny can use cron
# Both absent: only root can use cron
# Both empty: anyone can use cron (cron.allow empty but exists = no one)

# Add user to blacklist:
# echo "baduser" >> /etc/cron.deny

# Allow only specific users:
# echo "raju" > /etc/cron.allow
# echo "sam" >> /etc/cron.allow

at — One-Time Task Scheduling

at schedules a command or script to run once at a specific future time. Unlike cron, at jobs are not recurring. The atd daemon processes the queue.

# Schedule an at job:
# at 10:30pm
at> /backup/run.sh
at> <Ctrl+D>                      # press Ctrl+D to save

# Time formats accepted by at:
# at 10:30pm                        # today at 10:30 PM
# at 22:30                          # 24-hour format
# at noon                           # noon today
# at midnight                       # midnight tonight
# at now + 2 hours                  # 2 hours from now
# at now + 30 minutes               # 30 minutes from now
# at 9:00 PM Jul 15                 # specific date
# at 2:00 AM tomorrow               # tomorrow
# at 5:00 AM Jul 15 2026            # far future

# Examples:
# at 3:00 AM tomorrow
at> tar -czvf /backup/db_$(date +%Y%m%d).tar.gz /var/lib/mysql

# One-liner:
# echo "/scripts/deploy.sh" | at 11:30pm

# Manage at jobs:
# atq                               # list pending jobs (same as at -l)
# at -l                             # list jobs
# at -r 3                           # remove job number 3
# atrm 3                            # same as at -r 3

# View job details:
# at -c 3                           # view commands in job 3

anacron — The Missing cron for Desktops

Regular cron assumes the system is always running. If a cron job is scheduled for 3 AM but the laptop is off at that time, the job never runs. anacron solves this by tracking when jobs last ran and executing missed jobs when the system starts.

# /etc/anacrontab format:
# PERIOD  DELAY  JOB-ID          COMMAND
1         5      cron.daily       run-parts /etc/cron.daily
7         10     cron.weekly      run-parts /etc/cron.weekly
@monthly  15     cron.monthly     run-parts /etc/cron.monthly

# Fields:
# PERIOD  = frequency in days (or @daily, @weekly, @monthly)
# DELAY   = minutes to wait after boot before running
# JOB-ID  = unique name (used for tracking in /var/spool/anacron/)
# COMMAND = what to run

# Timestamp files (when job last ran):
# ls /var/spool/anacron/            # cron.daily, cron.weekly, cron.monthly

# Run all pending anacron jobs now:
# anacron -n

# Force run (ignore timestamps):
# anacron -f

# Test without running:
# anacron -T                        # test config syntax

Practical Examples for Production Use

# Daily database backup at 1:00 AM:
0 1 * * * /usr/bin/mysqldump -u root -pSECRET --all-databases | \
    /usr/bin/gzip > /backup/db_$(date +\%Y\%m\%d).sql.gz

# Note: % must be escaped as \% in crontab

# Weekly log cleanup:
0 3 * * 0 /usr/bin/find /var/log -name "*.gz" -mtime +30 -delete

# Check disk space every 30 minutes and alert if over 80%:
*/30 * * * * /usr/bin/df -h | /usr/bin/awk '$5 > 80 {print}' | \
    /usr/bin/mail -s "Disk Alert $(hostname)" admin@example.com

# Sync time every hour:
0 * * * * /usr/sbin/ntpdate -s pool.ntp.org

# Clear temp files daily at 4 AM:
0 4 * * * /usr/bin/find /tmp -maxdepth 1 -mtime +2 -delete

# Rotate a custom log file daily:
59 23 * * * /usr/bin/mv /app/logs/app.log /app/logs/app_$(date +\%Y\%m\%d).log \
    && /bin/systemctl reload myapp

# Certificate expiry check weekly:
0 9 * * 1 /usr/bin/openssl s_client -connect example.com:443 2>/dev/null | \
    /usr/bin/openssl x509 -noout -enddate | \
    /usr/bin/mail -s "Cert expiry check" admin@example.com

Troubleshooting Cron

# Check if crond is running:
# systemctl status crond

# View cron execution log:
# grep CRON /var/log/cron | tail -20

# Why a cron job didn't run — checklist:
# 1. Syntax error in crontab (test with: crontab -l)
# 2. Wrong path — use full paths in cron scripts
#    Bad:  backup.sh → not found (PATH is minimal in cron)
#    Good: /home/raju/scripts/backup.sh
# 3. Script not executable: chmod +x /scripts/backup.sh
# 4. User not in cron.allow or is in cron.deny
# 5. Script has errors — check the output email or redirect to log
# 6. % not escaped: date +%Y%m%d must be date +\%Y\%m\%d in crontab
# 7. crond not running

# Test a cron command manually first:
# Run exactly as cron would:
# sudo -u raju /bin/bash -c '/path/to/script.sh'

# Debug by logging:
* * * * * /scripts/test.sh >> /tmp/cron_test.log 2>&1