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.
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.
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.
Mental model
A kitchen with a timer and a stack of alarm cards
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.
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.
Key commands
crontab -lList your current cron jobs.
# m h dom mon dow command 30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
crontab -eEdit your crontab in the editor set by EDITOR (vim, nano).
Opens your crontab in the editor; saving installs the new schedule.
crontab -rRemove 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/crontabShow 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.dList system cron drop-in files, each with a user column, used by packages like certbot.
certbot logrotate unattended-upgrades
grep CRON /var/log/syslogSearch 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 cronVerify the cron daemon is running on a systemd distro (crond on RHEL-family).
● cron.service - Regular background program processing daemon Active: active (running)
Examples
Schedule a daily backup with logging and an overlap lock
# 1. Write the backup scriptcat > /usr/local/bin/backup.sh <<'EOF'#!/bin/bashexec 9>/var/lock/backup.lockflock -n 9 || exit 1tar czf /backup/$(date +%F).tar.gz /srv/dataecho "backup done: $(date)"EOFchmod +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 scheduledcrontab -lflock -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.
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.
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.
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.
Common issues
Job 'runs' but does nothing / produces no logThe command is not found because cron's minimal PATH omits its directory.
Use absolute paths (/usr/local/bin/script.sh) and set PATH explicitly inside the script, then redirect output to a log.
crontab: changes not saved / cannot edit crontabNo usable editor is configured or the EDITOR variable points at a missing binary.
export EDITOR=vim (or nano), then crontab -e again.
Job never runs at allThe cron daemon is not running — common in containers and minimal images.
service cron start or systemctl start cron, then confirm with pgrep cron.
Job runs twice or produces corrupt archivesA previous run is still executing when the next trigger fires.
Guard the job with flock -n <lockfile> so a second run exits immediately instead of overlapping.
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.
Interview checkpoint
Hands-on lab
- 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
Quick quiz
In the crontab line '30 2 * * 1 /usr/local/bin/backup.sh', what does the final field 1 mean?
What time does '*/15 * * * *' run a job?
Which command edits your user's crontab?
Which special string runs a job once at boot?
Cron automatically runs jobs with your full login environment, including PATH, aliases and exported variables.
A daily job 'appears to run' but produces no logs and you cannot tell whether it succeeded. Which change makes it verifiable?
Cheat sheet
* * * * * cmdEvery minute (min hour dom month dow)*/15 * * * * cmdEvery 15 minutes0 2 * * * cmdDaily at 02:000 2 * * 1-5 cmd02:00 on weekdays only0 0 * * 1 cmdMidnight every Monday30 3 1 * * cmd03:30 on the 1st of every month0 2 * * 0,6 cmd02:00 at weekends@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 -lList your jobscrontab -eEdit your jobs (uses $EDITOR)crontab -rRemove your crontab(crontab -l; echo 'line') | crontab -Append a job non-interactivelycrontab -u alice -lList another user's jobs (root)cat /etc/crontabSystem schedule — has a user fieldls /etc/cron.dPackage drop-in files (certbot, logrotate)/etc/cron.dailyScripts run once a day via run-partsgrep CRON /var/log/syslogCron execution log (Debian/Ubuntu)grep CROND /var/log/cronCron log on RHEL-family distros>> /var/log/job.log 2>&1Capture stdout and stderrMAILTO=oncall@example.comRoute cron's failure emailsflock -n /var/lock/job.lock cmdExit if the previous run is still goingPATH=/usr/local/bin:/usr/bin:/binSet PATH explicitly in the job/usr/local/bin/script.shAlways absolute pathsecho 'job done: $(date)'Timestamped proof the job ran