Scheduling Tasks with cron
Reviewed & published by Brayan K
By the end of this lesson you'll be able to schedule commands to run automatically — every night, every 15 minutes, or once at startup — using cron's five schedule fields, special shortcuts, and a crontab that actually runs reliably.
Part of the free Command Line course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Edit, list, and (carefully) remove your crontab with crontab -e / -l / -r
- Read and write the 5 schedule fields: minute, hour, day-of-month, month, day-of-week
- Use *, */n steps, ranges (1-5), and lists (1,15,30) to build any schedule
- Reach for shortcuts like @reboot, @daily, @hourly, @weekly, @monthly
- Control output with MAILTO and log files using >> /var/log/file.log 2>&1
- Avoid the #1 footgun: cron's minimal environment, by using absolute paths
1. The crontab: -e, -l and -r
Your personal schedule lives in a file called your crontab, but you never open that file by hand — you manage it with three commands. crontab -e edits it (this is how you add jobs), crontab -l lists it, and crontab -r removes it. Be careful: crontab -r deletes your entire crontab with no confirmation, and it's one key away from -e — a classic footgun. Back up first with crontab -l > backup.txt.
# cron is a time-based job scheduler on Unix/Linux.
# Your personal schedule lives in a file called your "crontab".
# You never edit that file directly — you use these three commands:
crontab -e # EDIT your crontab (opens it in an editor)
crontab -l # LIST (print) your crontab to the screen
crontab -r # REMOVE your crontab — DELETES THE WHOLE THING
# WARNING: crontab -r wipes your entire crontab with no confirmation.
# It sits one key away from crontab -e, so it is a classic footgun.
# Make a backup first: crontab -l > my-cron-backup.txt2. The Five Schedule Fields
Every cron line is five time fields followed by the command. The order, left to right, is always: minute (0-59), hour (0-23), day-of-month (1-31), month (1-12), and day-of-week (0-7, where both 0 and 7 mean Sunday). So 30 2 * * * means minute 30, hour 2, any day, any month, any weekday — i.e. 02:30 every day.
# Every cron line has 5 time fields, then the command to run.
# Read them LEFT to RIGHT, in THIS exact order:
#
# * * * * * command_to_run
# | | | | |
# | | | | +--- day-of-week (0-7) 0 and 7 BOTH mean Sunday
# | | | +----- month (1-12)
# | | +------- day-of-month (1-31)
# | +--------- hour (0-23) 0 = midnight, 23 = 11 PM
# +----------- minute (0-59)
#
# A literal example — run a backup at 02:30 every single day:
30 2 * * * /home/user/scripts/backup.sh
# minute=30, hour=2, day-of-month=any, month=any, day-of-week=any3. Field Syntax: *, Steps, Ranges & Lists
Each field accepts four building blocks. * means every value. */n is a step — */5 in the minute field means every 5 minutes. a-b is a range — 1-5 in day-of-week is Monday through Friday. And a,b,c is a list — 1,15,30. Combine them to express almost any schedule you need.
# The four building blocks you can put in ANY field:
#
# * every value (every minute / every hour / ...)
# */n a step: every n (*/5 in minutes = every 5 minutes)
# a-b a range (1-5 = Monday through Friday)
# a,b,c a list (1,15,30 = minutes 1, 15 and 30)
#
# Worked examples (each line = schedule + what it means):
0 * * * * /path/cmd # top of EVERY hour (minute 0)
*/15 * * * * /path/cmd # every 15 minutes
0 9 * * 1-5 /path/cmd # 09:00, Monday through Friday
0 0 1 * * /path/cmd # midnight on the 1st of each month
30 8 1,15 * * /path/cmd # 08:30 on the 1st and the 15th4. Special Shortcuts: @daily, @reboot & Friends
For common schedules, cron offers shortcuts that replace all five fields. @reboot runs a job once when the system boots; @hourly, @daily, @weekly, @monthly, and @yearly are readable aliases — for example @daily is exactly 0 0 * * *. You still add the command after the shortcut.
# Instead of five fields, cron accepts these handy shortcuts.
# They replace the WHOLE schedule (you still add a command after).
@reboot /path/startup.sh # run ONCE, each time the system boots
@hourly /path/cmd # = 0 * * * * (top of every hour)
@daily /path/cmd # = 0 0 * * * (every day at midnight)
@weekly /path/cmd # = 0 0 * * 0 (Sunday at midnight)
@monthly /path/cmd # = 0 0 1 * * (1st of month, midnight)
@yearly /path/cmd # = 0 0 1 1 * (Jan 1st at midnight)
# @midnight is also accepted as an alias for @daily on most systems.5. Output, Email & Logging
By default, cron emails whatever a job prints (stdout and stderr) to the local user. Put MAILTO="you@example.com" at the top of the crontab to choose the recipient, or MAILTO="" to switch that mail off. More commonly, people send output to a log file by appending >> /var/log/task.log 2>&1 — where 2>&1 routes errors into the same log so nothing is lost.
# By default, cron EMAILS any output (stdout + stderr) a job
# produces to the local user. Two settings (put them at the TOP
# of the crontab, before the job lines) control where mail goes:
MAILTO="you@example.com" # send job output to this address
# MAILTO="" # set it EMPTY to disable cron mail entirely
# Most people skip email and write output to a LOG file instead.
# Append both stdout and stderr to a log with >> file 2>&1 :
0 3 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1
# >> append (don't overwrite) the log file
# 2>&1 send errors (stderr) to the same place as output6. cron's Minimal Environment (use absolute paths!)
This is the single most common reason a cron job "works in my shell but not in cron." cron runs with a minimal environment: a sparse PATH (often just /usr/bin:/bin) and none of your ~/.bashrc or login settings. So always use absolute paths to your commands and scripts — /usr/bin/python3 /home/user/app/job.py — or set PATH= at the top of the crontab. As a fast contrast: at runs a command once at a future time, and systemd timers are a modern alternative on systemd-based Linux.
# THE #1 reason a cron job "works in my shell but not in cron":
# cron runs with a MINIMAL environment. Its PATH is sparse (often
# just /usr/bin:/bin) and it does NOT load your ~/.bashrc or profile.
#
# So a bare command name that works when YOU type it may not be found.
# RISKY — relies on PATH that cron may not have:
0 * * * * python myscript.py
# RELIABLE — use ABSOLUTE paths to the program AND the script:
0 * * * * /usr/bin/python3 /home/user/app/myscript.py
# Alternatively, set PATH once at the top of the crontab:
PATH=/usr/local/bin:/usr/bin:/bin
0 * * * * python3 /home/user/app/myscript.py🎯 Your Turn: schedule a daily 2:30 AM backup
Fill in the two blanks marked ___ using the # 👉 hints, then read the line back left to right and check it matches the Output panel.
# 🎯 YOUR TURN — schedule a backup at 2:30 AM EVERY day.
# Fill in the two blanks, then read it back left to right.
# Fields order: minute hour day-of-month month day-of-week command
___ ___ * * * /home/user/scripts/backup.sh
# | |
# | 👉 replace the 2nd ___ with the HOUR for 2 AM -> 2
# 👉 replace the 1st ___ with the MINUTE (thirty) -> 30
# ✅ Expected line:
# 30 2 * * * /home/user/scripts/backup.sh
# (runs at 02:30 every day)🎯 Your Turn: run a check every 15 minutes
Two blanks again: pick the step syntax for "every 15 minutes," and the redirect that appends output to a log. Then check the Output panel.
# 🎯 YOUR TURN — run a health check EVERY 15 minutes and log it.
# Fill in the two blanks.
# 1) Step syntax for "every 15 minutes" goes in the minute field.
___ * * * * /home/user/scripts/healthcheck.sh ___ /var/log/health.log 2>&1
# | |
# | 👉 replace 2nd ___ with >>
# 👉 replace 1st ___ with the step for every 15 min -> */15
# ✅ Expected line:
# */15 * * * * /home/user/scripts/healthcheck.sh >> /var/log/health.log 2>&1Pro Tips
- 💡 Always use absolute paths in cron jobs — to the program and the script. cron's PATH is not your shell's PATH.
- 💡 Log everything: append >> /var/log/task.log 2>&1 so you can read what happened instead of waiting on cron email.
- 💡 Back up before editing: run crontab -l > cron-backup.txt so a stray crontab -r can't wipe your schedule for good.
- 💡 Test the schedule, not just the script: set a job a minute or two out, confirm it fires, then move it to its real time.
Common Errors (and the fix)
- Job silently does nothing — usually cron's minimal environment. Use absolute paths to commands and scripts, and add >> /var/log/job.log 2>&1 to capture the real error.
- Accidentally wiped your crontab — you ran crontab -r (remove) instead of crontab -e (edit). They're one key apart; keep a backup from crontab -l.
- Wrong field order — the order is minute, hour, day-of-month, month, day-of-week. Writing the hour first is a common mix-up that schedules the job at the wrong time.
- Unexpected runs on a weekday-of-month rule — if you set both day-of-month and day-of-week to non-* values, cron runs when either matches, not both.
- Inbox full of cron mail — a noisy job is emailing its output. Set MAILTO="" at the top, or redirect output to a log file.
📋 Quick Reference
| Concept | Syntax | Notes |
|---|---|---|
| Edit crontab | crontab -e | Add or change jobs |
| List crontab | crontab -l | Print your jobs |
| Remove crontab | crontab -r | Deletes ALL of it |
| Field order | min hr dom mon dow | Then the command |
| Every value | * | Matches all |
| Step | */5 | Every 5 (e.g. minutes) |
| Range | 1-5 | Mon-Fri in dow |
| List | 1,15,30 | Specific values |
| Daily shortcut | @daily | = 0 0 * * * |
| At boot | @reboot | Runs once at startup |
| Email output | MAILTO="..." | Empty = no mail |
| Log output | >> file 2>&1 | Append out + errors |
Mini-Challenge: a weekday morning report
No blanks this time — just a brief and an outline. Build the full cron line that runs a report script at 8:00 AM on weekdays and logs its output. Plan each field in order, use the absolute path, and check your line against the target in the comments.
# 🎯 MINI-CHALLENGE: a weekday morning report job
#
# Goal: schedule /home/user/scripts/report.sh to run at 8:00 AM,
# Monday through Friday, sending all output to /var/log/report.log.
#
# Build the cron line yourself. Plan each field in order:
# 1. minute -> 0
# 2. hour -> 8
# 3. day-of-month -> * (any)
# 4. month -> * (any)
# 5. day-of-week -> the range that means Mon..Fri (hint: 1-5)
# 6. command -> the script, then >> /var/log/report.log 2>&1
#
# Remember: use the ABSOLUTE path to the script, because cron has a
# minimal PATH. Add it with crontab -e and confirm with crontab -l.
#
# ✅ Target line:
# 0 8 * * 1-5 /home/user/scripts/report.sh >> /var/log/report.log 2>&1
# your line here🎉 Lesson Complete!
- ✅ Manage jobs with crontab -e (edit), crontab -l (list), crontab -r (remove — careful!)
- ✅ The five fields are minute hour day-of-month month day-of-week, then the command
- ✅ Use *, steps */n, ranges 1-5, and lists 1,15,30 to build any schedule
- ✅ Shortcuts like @reboot and @daily cover the common cases
- ✅ Control output with MAILTO or >> /var/log/file.log 2>&1, and always use absolute paths
Practice quiz
What is the order of the 5 cron schedule fields?
- minute hour day-of-month month day-of-week
- minute hour month day-of-month day-of-week
- second minute hour day month
- hour minute day-of-month month day-of-week
Answer: minute hour day-of-month month day-of-week. The five fields, in order, are: minute, hour, day-of-month, month, day-of-week, then the command.
How do you edit your own crontab?
- crontab -r
- cron edit
- crontab -e
- crontab -l
Answer: crontab -e. crontab -e opens YOUR crontab in an editor; -l lists it and -r removes it.
What does */5 in the minute field mean?
- From minute 5 onward
- Every 5 minutes
- Only on the 5th
- At 5 minutes past the hour
Answer: Every 5 minutes. The step syntax */5 in the minute field means run every 5 minutes.
What does the line 0 9 * * 1-5 do?
- 9 PM every day
- Every 9 minutes on weekdays
- At minute 9 every hour
- At 9 AM Monday through Friday
Answer: At 9 AM Monday through Friday. minute 0, hour 9, any day-of-month, any month, days 1-5 (Mon-Fri) means 9:00 AM on weekdays.
What does @reboot do in a crontab?
- Runs the job once at system startup
- Runs the job every minute
- Disables the job
- Reboots the machine on a schedule
Answer: Runs the job once at system startup. @reboot runs the job a single time when the system boots, not on a clock schedule.
Which value range is correct for the hour field?
- 1-12
- 0-23
- 1-24
- 0-12
Answer: 0-23. Hours run 0-23 in 24-hour form; 0 is midnight and 23 is 11 PM.
Why do cron jobs often fail even though the same command works in your shell?
- cron only runs as root
- cron cannot run shell scripts
- cron caps jobs at one per hour
- cron has a minimal environment and PATH, so use absolute paths
Answer: cron has a minimal environment and PATH, so use absolute paths. cron runs with a sparse PATH and not your login environment, so commands and scripts should use absolute paths (or set PATH at the top of the crontab).
What does the MAILTO setting in a crontab do?
- Schedules an email job
- Logs output to a file
- Sets the email address that receives cron output
- Sends a test email immediately
Answer: Sets the email address that receives cron output. MAILTO=you@example.com sets who receives a job's stdout/stderr; MAILTO= (empty) disables that mail.
Which schedule runs at midnight on the 1st of every month?
- 0 0 * * 1
- 0 0 1 * *
- * * 1 * *
- 0 1 0 * *
Answer: 0 0 1 * *. minute 0, hour 0, day-of-month 1, any month, any weekday means 00:00 on the 1st of each month.
Which command schedules a one-time job to run once at a future time?
- at
- crontab -r
- cron once
- watch
Answer: at. at runs a command a single time at a future moment; cron is for jobs that repeat on a schedule.
Continue this course
- Previous: SSH and Remote Access
- Next: Networking Commands — Diagnose connectivity with ping, dig, ss, netstat and traceroute
- Quick reference: Command Line cheat sheet
Frequently asked questions
What is the difference between crontab -e, -l and -r?
crontab -e EDITS your crontab in an editor (this is how you add or change jobs). crontab -l LISTS (prints) it so you can see what is scheduled. crontab -r REMOVES it — and it deletes the ENTIRE crontab without asking, which is dangerous because -r sits right next to -e on the keyboard. Back up first with crontab -l > backup.txt.
What is the order of the five cron schedule fields?
Left to right: minute (0-59), hour (0-23), day-of-month (1-31), month (1-12), and day-of-week (0-7, where both 0 and 7 mean Sunday), followed by the command. So 30 2 * * * means minute 30, hour 2, any day, any month, any weekday — i.e. 2:30 AM every day.
Why does my cron job do nothing even though the command works when I type it?
Almost always the environment. cron runs with a minimal PATH (often just /usr/bin:/bin) and does NOT load your ~/.bashrc or login profile, so a bare command name may not be found. Fix it by using absolute paths (for example /usr/bin/python3 /home/user/app/job.py), or set PATH at the top of the crontab. Also redirect output to a log with >> file 2>&1 so you can actually see the error.
Where does the output of a cron job go?
By default cron emails a job's stdout and stderr to the local user. You can set MAILTO=you@example.com at the top of the crontab to choose the recipient, or MAILTO= (empty) to turn that mail off. Most people instead append output to a log file, for example: 0 3 * * * /path/job.sh >> /var/log/job.log 2>&1 — where 2>&1 sends errors to the same log.
When should I use cron versus at or systemd timers?
Use cron for jobs that REPEAT on a schedule (every day, every 15 minutes, every Monday). Use at to run a command ONCE at a single future time. On modern systemd-based Linux, systemd timers are an alternative to cron that add features like dependency ordering, logging through journald, and catch-up runs for missed jobs.