Scheduling Tasks — crontab
Not everything needs to run continuously as a service, or only in response to something happening — plenty of real tasks need to run on a fixed schedule instead: a nightly backup, a weekly report, a cleanup script that runs every hour. cron is the traditional Linux tool for exactly that, and despite its age, it remains genuinely standard practice.
The crontab Syntax
A scheduled job is defined by a line with six fields — five specifying the schedule, followed by the command itself:
minute hour day-of-month month day-of-week command| Field | Range |
|---|---|
| Minute | 0–59 |
| Hour | 0–23 |
| Day of month | 1–31 |
| Month | 1–12 |
| Day of week | 0–7 (both 0 and 7 mean Sunday) |
A few special characters make these fields expressive rather than requiring one line per exact time:
| Symbol | Meaning | Example |
|---|---|---|
* | Every value | * in the hour field means every hour |
, | A list of specific values | 1,15 means the 1st and 15th |
- | A range | 1-5 means Monday through Friday in the day-of-week field |
/ | A step | */15 in the minute field means every 15 minutes |
Common Schedules, Read In Plain English
| Schedule | Meaning |
|---|---|
* * * * * | Every minute |
0 * * * * | Every hour, on the hour |
0 2 * * * | Every day at 2:00 AM |
0 2 * * 1 | Every Monday at 2:00 AM |
*/15 * * * * | Every 15 minutes |
0 0 1 * * | Midnight on the first day of every month |
Reading any cron schedule left to right — minute, then hour, then day-of-month, then month, then day-of-week — becomes fairly natural once you’ve seen a handful of real examples.
Editing Your Own Schedule: crontab -e
crontab -eThis opens your personal crontab in your configured editor (nano, per the convention used throughout this course). Add a line:
0 2 * * * /home/you/scripts/backup.shSave and exit, and cron picks up the new schedule automatically — no restart or reload step needed, unlike systemd unit files.
crontab -lLists your current crontab without opening an editor — useful for a quick check.
crontab -rRemoves your entire crontab. There’s no per-line removal command — to delete just one job, use crontab -e and delete that specific line, saving the rest intact.
Each user’s crontab is stored separately under /var/spool/cron/crontabs/, though you should always manage it through crontab -e rather than editing that location directly.
System-Wide Scheduling
Beyond individual users’ own crontabs, /etc/crontab and files under /etc/cron.d/ serve the same purpose system-wide — with one syntax difference worth noting: these files include an extra username field between the schedule and the command, since there’s no single implicit “owner” the way a personal crontab has:
0 2 * * * root /usr/local/bin/system-backup.shFor the common case of “just run this script daily/hourly/weekly,” there’s an even simpler option: drop an executable script directly into /etc/cron.daily/, /etc/cron.hourly/, /etc/cron.weekly/, or /etc/cron.monthly/, with no schedule syntax needed at all — cron runs everything in these directories on the corresponding built-in schedule automatically, using a helper called run-parts.
The Gotcha: cron’s Minimal Environment
This trips up nearly everyone the first time: a script that works perfectly when you run it yourself can fail silently when cron runs it, because cron jobs execute with a genuinely minimal environment — no interactive shell configuration loaded, and critically, a much shorter PATH than your normal login session has.
A script calling a command by name alone — python3 myscript.py, relying on python3 being found via PATH — can fail under cron even though it works fine when you type it yourself. Two reliable fixes: use absolute paths for every command inside scripts run by cron, or set PATH explicitly at the top of the crontab itself:
PATH=/usr/local/bin:/usr/bin:/bin
0 2 * * * /home/you/scripts/backup.shHandling Output
By default, cron emails any output a job produces (standard output and standard error both) to the crontab owner, via the local mail system — which, on a typical server with no mail setup configured, effectively goes nowhere useful. Two common approaches: redirect output explicitly to a log file, or discard it deliberately if it’s genuinely not needed, using the redirection syntax from earlier in this course:
0 2 * * * /home/you/scripts/backup.sh >> /var/log/backup.log 2>&1This appends both standard output and error into a persistent log file — generally the better default over silently discarding everything with /dev/null, since a failing scheduled job with no output anywhere is far harder to diagnose than one with a log trail to check.
Where cron Logs
cron activity itself — not the output of the jobs it runs, but the fact that it ran them — is logged through syslog, using the cron facility from the last chapter’s facility table:
grep CRON /var/log/syslogThis confirms a scheduled job actually fired at the time you expected, independent of whatever that job’s own script logged on its own.
systemd Timers: The Modern Alternative
systemd offers its own scheduling mechanism — .timer units, briefly mentioned as a unit type back in the Units & Targets chapter — pairing a timer with a corresponding .service unit it triggers. Full timer authoring is genuinely deeper territory than this course covers, but the tradeoff is worth knowing at a glance:
| Scope | cron | systemd timers |
|---|---|---|
| Ubiquity | Available on essentially every Unix-like system | systemd-specific |
| Simplicity | One-line schedule, very fast to write | Requires a separate .timer and .service unit pair |
| Missed-run handling | No built-in catch-up if the system was off | Can catch up missed runs on next boot (Persistent=true) |
| Integration with other units | None | Can depend on and be ordered against other systemd units |
For straightforward, simple scheduling needs, cron remains the faster and more universally recognizable choice — which is exactly why it’s the one covered hands-on in this chapter.
What’s Next
With scheduled automation covered alongside continuous services, this section has now walked through nearly everything a running system does on its own. The final chapter pulls it together with real-world debugging — working through actual failure scenarios using the tools from across this entire section.