Linux
Intermediate 12 min read Linux

Cron & Scheduled Tasks

Backups, certificate renewal, log rotation and health checks all run on cron. Learn what cron is, master the five-field crontab syntax, manage your jobs with crontab -l/-e/-r, understand system cron (/etc/crontab, /etc/cron.d) and debug with the cron log — then apply the production habits of logging, PATH and locking that separate reliable jobs from silent failures.

croncrontabscheduled tasksjob schedulingcron.dcrontab -ecron logflockautomationbackupslinux
Prerequisites: Shell Basics
Introduction

What is this?

Cron is the classic Unix job scheduler: a daemon that wakes up every minute and runs commands on a schedule you define. The crontab — 'cron table' — is a small file describing when each job runs, and it remains the default tool for backups, log rotation, certificate renewal and health checks on millions of production servers.

Motivation

Why do you need it?

  • Nightly backups, certificate renewal and log rotation are all cron jobs — understanding cron is mandatory on any production box.
  • Cron runs jobs in a stripped-down environment with no PATH and no aliases, which surprises people and is the number one cause of 'it worked in my terminal'.
  • Reading the five-field syntax lets you audit any server's schedule in seconds.
  • Knowing cron's limits tells you exactly when to reach for systemd timers instead.
Think about it

Mental model

Imagine

A kitchen with a timer and a stack of alarm cards

Think of

Cron

The kitchen timer (crond) checks the clock every minute. Each alarm card says 'at minute 0, hour 2, any day — run the backup recipe'. When the clock matches, the chef (a shell) runs the recipe with only the ingredients listed on the card — never the full pantry of your login shell.

Mechanics

How it works

  • A cron daemon (crond or cronie) starts at boot and reads the crontabs of every user plus the system files /etc/crontab and /etc/cron.d.
  • Each job line has five time fields — minute, hour, day of month, month, day of week — followed by the command to run.
  • Every minute crond checks whether the current time matches any job and launches the match through a shell.
  • Cron executes jobs with a minimal environment: /bin/sh, a bare PATH and the user's cron environment — not your interactive shell's variables or aliases.
  • Anything a job writes to stdout or stderr is emailed to the job's owner (or MAILTO) unless redirected, and jobs missed during downtime are never 'caught up'.

The life of a cron job, from crontab file to execution.

crond starts at boot
Job entry
Time match?
Shell executes
Output handled
Next minute
Reference

Key commands

crontab -l

List your current cron jobs.

# m h dom mon dow  command
30 2 * * *   /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
crontab -e

Edit your crontab in the editor set by EDITOR (vim, nano).

Opens your crontab in the editor; saving installs the new schedule.
crontab -r

Remove your crontab and all its jobs.

(crontab -l; echo '0 2 * * * /usr/local/bin/backup.sh') | crontab -

Append a job non-interactively — perfect for scripts and provisioning tools.

cat /etc/crontab

Show the system crontab — note it has an extra user field before the command.

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
ls /etc/cron.d

List system cron drop-in files, each with a user column, used by packages like certbot.

certbot   logrotate   unattended-upgrades
grep CRON /var/log/syslog

Search the cron log for the exact commands crond launched and any start errors.

Aug  8 02:00:01 web-01 CRON[12345]: (backup) CMD (/usr/local/bin/backup.sh)
systemctl status cron

Verify the cron daemon is running on a systemd distro (crond on RHEL-family).

● cron.service - Regular background program processing daemon
   Active: active (running)
Hands-on

Examples

Schedule a daily backup with logging and an overlap lock

Schedule a daily backup with logging and an overlap lock
# 1. Write the backup script
cat > /usr/local/bin/backup.sh <<'EOF'
#!/bin/bash
exec 9>/var/lock/backup.lock
flock -n 9 || exit 1
tar czf /backup/$(date +%F).tar.gz /srv/data
echo "backup done: $(date)"
EOF
chmod +x /usr/local/bin/backup.sh
# 2. Install the job non-interactively, with output redirected
(crontab -l; echo "30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1") | crontab -
# 3. Verify it is scheduled
crontab -l

flock -n (non-blocking) makes the script exit immediately if the previous run is still going — no overlapping tar archives. Redirecting >> /var/log/backup.log 2>&1 captures every message instead of mailing root, and a timestamped 'backup done' line proves the job ran. Installing with (crontab -l; echo ...) | crontab - appends without touching a full-screen editor.

Real world

In production

  • Nightly backups and database dumps with a retention policy are the canonical cron workload.
  • logrotate is driven from /etc/cron.daily to keep log growth bounded.
  • Certificate renewal (certbot) and periodic TLS checks run on cron drop-ins.
  • Health checks, report emails and metric collection run every few minutes across production fleets.
Guidelines

Best practices

  • Redirect everything: >> /var/log/<job>.log 2>&1 — stdout and stderr, always.
  • Use absolute paths and set PATH at the top of the script so cron never guesses.
  • Prevent overlap with flock or a lockfile — a slow job that re-runs can corrupt data.
  • Set MAILTO to an on-call address (or blank it) so cron does not silently flood root's mailbox.
  • Put system jobs in /etc/cron.d or run-parts directories and version the scripts.
  • Remember cron uses server-local time — and most cloud VMs run UTC.
  • Test the command manually first; only then schedule it.
Watch out

Common mistakes

  • Assuming cron has your interactive environment — it does not: no PATH, no aliases, no exported variables.
  • Leaving out the minute field — '*' is required; a bare '2 * * *' is not 'hour 2'.
  • Not redirecting output, so errors vanish into /var/mail instead of a log.
  • Editing /etc/crontab without the user column, which system crontabs require.
  • Using % unescaped in the command field — cron truncates the command at the first unescaped %.
  • Expecting a missed job to run late after downtime — cron never catches up.
Root cause analysis

Common issues

Error
Job 'runs' but does nothing / produces no log
Root cause

The command is not found because cron's minimal PATH omits its directory.

Fix

Use absolute paths (/usr/local/bin/script.sh) and set PATH explicitly inside the script, then redirect output to a log.

Error
crontab: changes not saved / cannot edit crontab
Root cause

No usable editor is configured or the EDITOR variable points at a missing binary.

Fix

export EDITOR=vim (or nano), then crontab -e again.

Error
Job never runs at all
Root cause

The cron daemon is not running — common in containers and minimal images.

Fix

service cron start or systemctl start cron, then confirm with pgrep cron.

Error
Job runs twice or produces corrupt archives
Root cause

A previous run is still executing when the next trigger fires.

Fix

Guard the job with flock -n <lockfile> so a second run exits immediately instead of overlapping.

When things break

Debugging tips

  • Check the log first: grep CRON /var/log/syslog shows the exact CMD line crond executed.
  • Run the job's command manually and check echo $? — if it fails by hand, it fails under cron.
  • Add an echo with a timestamp at the top and bottom of the job to prove it started and finished.
  • Confirm the daemon is alive with systemctl status cron or pgrep cron before blaming the schedule.
Interviews

Interview checkpoint

Practice

Hands-on lab

Schedule, log and verify a cron job inside a container
15 min · intermediate
In progress
Objectives
  • Install and start the cron daemon in a container
  • Write an executable script that logs its runs
  • Install a crontab entry that runs every minute
  • Verify execution via the job's log file
  • Clean up with crontab -r

Schedule, log and verify a cron job inside a container

15 min · intermediate

0/6 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/6
1

In the crontab line '30 2 * * 1 /usr/local/bin/backup.sh', what does the final field 1 mean?

2

What time does '*/15 * * * *' run a job?

3

Which command edits your user's crontab?

4

Which special string runs a job once at boot?

5

Cron automatically runs jobs with your full login environment, including PATH, aliases and exported variables.

6

A daily job 'appears to run' but produces no logs and you cannot tell whether it succeeded. Which change makes it verifiable?

Quick reference

Cheat sheet

The five-field schedule
* * * * * cmdEvery minute (min hour dom month dow)
*/15 * * * * cmdEvery 15 minutes
0 2 * * * cmdDaily at 02:00
0 2 * * 1-5 cmd02:00 on weekdays only
0 0 * * 1 cmdMidnight every Monday
30 3 1 * * cmd03:30 on the 1st of every month
0 2 * * 0,6 cmd02:00 at weekends
Special strings
@reboot cmdOnce at boot
@daily / @midnight cmdOnce per day (00:00)
@hourly cmdOnce per hour
@weekly cmdOnce per week (Sunday)
@monthly cmdOnce per month (1st)
Crontab management
crontab -lList your jobs
crontab -eEdit your jobs (uses $EDITOR)
crontab -rRemove your crontab
(crontab -l; echo 'line') | crontab -Append a job non-interactively
crontab -u alice -lList another user's jobs (root)
System cron
cat /etc/crontabSystem schedule — has a user field
ls /etc/cron.dPackage drop-in files (certbot, logrotate)
/etc/cron.dailyScripts run once a day via run-parts
grep CRON /var/log/syslogCron execution log (Debian/Ubuntu)
grep CROND /var/log/cronCron log on RHEL-family distros
Production job hygiene
>> /var/log/job.log 2>&1Capture stdout and stderr
MAILTO=oncall@example.comRoute cron's failure emails
flock -n /var/lock/job.lock cmdExit if the previous run is still going
PATH=/usr/local/bin:/usr/bin:/binSet PATH explicitly in the job
/usr/local/bin/script.shAlways absolute paths
echo 'job done: $(date)'Timestamped proof the job ran
Go deeper

Official documentation