Cron is Linux’s task scheduler for running commands automatically at specified times. To schedule a task, run crontab -e to open your personal crontab file, then add a line using the format: minute hour day month weekday command. For example, 30 2 * * * /home/sarah/backup.sh runs a backup script every day at 2:30 AM, and 0 9 * * 1 /usr/bin/python3 /home/sarah/weekly_report.py runs a Python script every Monday at 9:00 AM. Use crontab -l to view your current scheduled tasks.
Automating Linux with Time
Every Linux system has tasks that need to happen on a schedule: backups at 3 AM, log files that need rotating weekly, monitoring scripts that check disk space every hour, database maintenance that runs at midnight, reports generated every Monday morning. Doing these tasks manually — remembering to run them, being at the computer at the right time — is fragile and unsustainable.
Cron is Linux’s answer to this problem. Cron is a time-based job scheduler that has been part of Unix systems since 1975. It runs in the background as a daemon, waking up every minute to check whether any scheduled tasks need to run. If a task is scheduled for the current time, cron executes it. If not, cron sleeps for another minute. This simple loop has powered Unix and Linux automation for five decades.
Understanding cron means understanding the crontab file format — a terse but logical syntax that specifies exactly when a command should run. Once you grasp the five time fields (minute, hour, day of month, month, day of week) and a few special characters, you can express virtually any schedule imaginable: every minute, every day at midnight, every weekday at 9 AM, the first day of every month, every 15 minutes between 9 AM and 5 PM.
This article covers cron completely: the daemon that runs it, the crontab syntax in depth, special scheduling shortcuts, system-wide cron configuration, environment considerations that trip up many first-time users, how to test and debug cron jobs, and when to consider systemd timers as an alternative.
How Cron Works: The Daemon
Cron runs as a background service (crond on Fedora, managed by systemd on most modern distributions):
$ systemctl status cron
● cron.service - Regular background program processing daemon
Loaded: loaded (/lib/systemd/system/cron.service; enabled)
Active: active (running) since Mon 2026-02-18 09:00:12 UTC
Every minute, the cron daemon:
- Reads all crontab files (per-user and system-wide)
- Checks each scheduled task against the current time
- Runs any tasks whose schedule matches the current minute
- Captures output and (if configured) mails it to the task owner
The cron daemon reads crontab files from:
/var/spool/cron/crontabs/— per-user crontab files (one file per user)/etc/crontab— the system-wide crontab/etc/cron.d/— additional system-wide crontab snippets/etc/cron.hourly/,/etc/cron.daily/,/etc/cron.weekly/,/etc/cron.monthly/— directories of scripts run at those intervals
The crontab Command: Managing Your Schedule
Each user has their own crontab (cron table) — a personal list of scheduled tasks that run as that user.
Opening and Editing Your Crontab
$ crontab -e
This opens your personal crontab in the editor defined by the EDITOR or VISUAL environment variable (usually nano or vi). If this is your first crontab, the file is empty with some explanatory comments.
Never edit crontab files directly at /var/spool/cron/crontabs/username — always use crontab -e. The crontab command validates the syntax before saving, and it notifies the cron daemon that the file has changed.
Viewing Your Current Crontab
$ crontab -l
# List all scheduled tasks for the current user
30 2 * * * /home/sarah/scripts/backup.sh
0 9 * * 1 /usr/bin/python3 /home/sarah/weekly_report.py
*/15 * * * * /home/sarah/scripts/check_disk.sh >> /var/log/disk_check.log 2>&1
Removing Your Crontab
$ crontab -r # Remove ALL scheduled tasks — use with caution!
This deletes your entire crontab. There is no confirmation prompt by default. If you want to remove just one task, edit with crontab -e and delete that line.
Managing Another User’s Crontab (as root)
$ sudo crontab -u sarah -l # List sarah's crontab
$ sudo crontab -u sarah -e # Edit sarah's crontab
The Crontab Syntax: Five Time Fields
Each line in a crontab follows this structure:
┌─────────────── minute (0-59)
│ ┌──────────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌────── month (1-12 or JAN-DEC)
│ │ │ │ ┌─── day of week (0-7 or SUN-SAT, both 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command to execute
Each field can contain:
- A specific number (
5,14,30) - An asterisk
*meaning “every value” (every minute, every hour, etc.) - A comma-separated list of values (
1,15,30= at minute 1, 15, and 30) - A range with a hyphen (
9-17= hours 9 through 17) - A step value with a slash (
*/5= every 5,*/15= every 15) - Combinations (
1-5,10,15= 1 through 5, plus 10, plus 15)
Reading Crontab Entries: Examples
Every day at midnight:
0 0 * * * command
Minute 0, Hour 0, every day, every month, every weekday.
Every day at 2:30 AM:
30 2 * * * command
Minute 30, Hour 2, every day, every month, every weekday.
Every hour (at minute 0):
0 * * * * command
Minute 0, every hour, every day.
Every 15 minutes:
*/15 * * * * command
At minutes 0, 15, 30, and 45 of every hour.
Every 5 minutes:
*/5 * * * * command
At minutes 0, 5, 10, 15… 55 of every hour.
Every minute:
* * * * * command
All fields are * — runs at every minute.
Every weekday (Monday–Friday) at 9 AM:
0 9 * * 1-5 command
Minute 0, Hour 9, every day of month, every month, days 1 (Monday) through 5 (Friday).
Every Monday at 9 AM:
0 9 * * 1 command
Minute 0, Hour 9, every day, every month, only Monday (day 1).
First day of every month at midnight:
0 0 1 * * command
Minute 0, Hour 0, day 1 only, every month, every weekday.
Every 6 hours:
0 */6 * * * command
Minute 0, every 6th hour (0:00, 6:00, 12:00, 18:00).
Specific times: 9 AM and 5 PM, Monday through Friday:
0 9,17 * * 1-5 command
Minute 0, Hours 9 and 17, every day, every month, Monday through Friday.
Last day nuance: Cron cannot directly express “last day of month.” A workaround:
0 0 28-31 * * [ "$(date +\%d -d tomorrow)" = "01" ] && command
This runs on days 28–31 but only executes the command when tomorrow is the 1st (i.e., today is the last day of the month).
Day of Week Numbering
Both 0 and 7 represent Sunday. Monday is 1, Tuesday is 2, through Saturday is 6:
| Number | Day |
|---|---|
| 0 or 7 | Sunday |
| 1 | Monday |
| 2 | Tuesday |
| 3 | Wednesday |
| 4 | Thursday |
| 5 | Friday |
| 6 | Saturday |
You can also use three-letter abbreviations: SUN, MON, TUE, WED, THU, FRI, SAT.
Month Numbering and Names
Months 1–12 (January = 1, December = 12), or three-letter abbreviations: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC.
The Day-of-Month and Day-of-Week Interaction
An important, often-misunderstood behavior: when both day-of-month AND day-of-week are specified (not *), the job runs when EITHER condition is true — not when both are true simultaneously.
0 0 1 * 1 command
This does NOT mean “first Monday of the month.” It means “midnight on the 1st of every month OR midnight on every Monday” — whichever comes first.
To schedule for “the first Monday of the month,” you need a workaround:
0 0 * * 1 [ $(date +\%d) -le 7 ] && command
This runs every Monday but only executes the command if the day of the month is ≤ 7 (meaning it is in the first week).
Special Schedule Strings
Cron supports several special shorthand strings as alternatives to the five numeric fields:
| String | Meaning | Equivalent |
|---|---|---|
@reboot |
Run once at system startup | — |
@yearly or @annually |
Run once per year | 0 0 1 1 * |
@monthly |
Run once per month | 0 0 1 * * |
@weekly |
Run once per week | 0 0 * * 0 |
@daily or @midnight |
Run once per day | 0 0 * * * |
@hourly |
Run once per hour | 0 * * * * |
These are much more readable for common schedules:
@daily /home/sarah/scripts/daily_backup.sh
@weekly /usr/local/bin/weekly_report.sh
@reboot /home/sarah/scripts/startup_tasks.sh
@hourly /home/sarah/scripts/check_services.sh
@reboot is particularly useful for tasks that should run every time the system starts — starting a background process, mounting a network drive, or running a one-time initialization.
Writing Effective Cron Jobs
Use Absolute Paths for Everything
Cron runs with a minimal environment that does not include your normal PATH. Commands that work perfectly in your terminal may fail in cron because cron cannot find them.
This may fail:
30 2 * * * backup.sh
This will work:
30 2 * * * /home/sarah/scripts/backup.sh
Inside your scripts, also use absolute paths for any commands:
#!/bin/bash
# In a script called by cron, use full paths:
/usr/bin/rsync -av /home/ /backup/
/bin/tar -czf /backup/archive.tar.gz /home/
/usr/bin/find /backup/ -mtime +30 -delete
Or explicitly set PATH at the top of your crontab file:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 2 * * * backup.sh # Now works because PATH is set
Capture Output to a Log File
By default, cron emails command output to the user (using the local mail system). If no mail system is configured, output is discarded silently — making it impossible to know if your job ran or failed.
Always redirect output explicitly:
# Redirect both stdout and stderr to a log file
30 2 * * * /home/sarah/scripts/backup.sh >> /var/log/backup.log 2>&1
# Append to log (>>): keeps history
# Overwrite log (>): always shows just the latest run
30 2 * * * /home/sarah/scripts/check.sh > /tmp/check.log 2>&1
# Discard all output (use only for commands you know work correctly)
*/5 * * * * /home/sarah/scripts/heartbeat.sh > /dev/null 2>&1
The 2>&1 redirects stderr (file descriptor 2) to the same place as stdout (file descriptor 1), capturing error messages along with normal output.
Timestamping Log Output
For scripts called by cron, add timestamps to log entries so you can see exactly when each run happened:
#!/bin/bash
echo "=== Backup started: $(date) ==="
rsync -av /home/ /backup/
echo "=== Backup completed: $(date) ==="
MAILTO: Control Output Destination
Set MAILTO at the top of your crontab to control where cron sends job output:
MAILTO="" # Discard all output (no emails)
MAILTO="sarah@example.com" # Send to this email address
MAILTO="sarah" # Send to local user "sarah"
Setting MAILTO="" silences all cron email notifications system-wide for your crontab.
Cron’s Environment
Cron runs jobs in a stripped-down environment very different from your interactive shell:
What Cron’s Environment Looks Like
HOME=/home/sarah # Set to user's home directory
LOGNAME=sarah # Username
USER=sarah # Username
SHELL=/bin/sh # sh, not bash, unless specified
PATH=/usr/bin:/bin # Minimal PATH — not your full interactive PATH
MAILTO=sarah # Default mail destination
Notice SHELL=/bin/sh — cron uses sh by default, not bash. If your script uses Bash-specific syntax (arrays, [[ ]] tests, process substitution), it may fail when run by cron even if it works fine in your terminal.
Solutions:
Use the shebang in your scripts to explicitly specify bash:
#!/bin/bash
# The shebang ensures bash is used regardless of SHELL
Or set SHELL in the crontab:
SHELL=/bin/bash
30 2 * * * backup.sh
Testing with Cron’s Environment
To test a command under conditions similar to cron’s minimal environment:
$ env -i HOME=/home/sarah PATH=/usr/bin:/bin SHELL=/bin/sh /home/sarah/scripts/backup.sh
env -i starts with an empty environment, then only the specified variables are set — approximating what cron sees.
System-Wide Cron Configuration
/etc/crontab: The System Crontab
The system crontab at /etc/crontab has an extra field compared to user crontabs — the username to run the command as:
$ cat /etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# 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 )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
The format: minute hour day month weekday user command
The user field specifies which user account the command runs as. System crontab entries typically run as root.
/etc/cron.d/: Structured System Cron Files
Package managers use /etc/cron.d/ to install cron jobs alongside their packages. Each file follows the same format as /etc/crontab (with a user field):
$ ls /etc/cron.d/
anacron e2scrub_all popularity-contest sysstat
$ cat /etc/cron.d/sysstat
# The first element of the path is a directory where the debian-sa1
# script is located
PATH=/usr/lib/sysstat:/usr/sbin:/usr/sbin:/usr/bin:/sbin:/bin
# Activity reports every 10 minutes everyday
5-55/10 * * * * root command -v debian-sa1 > /dev/null && debian-sa1 1 1
/etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.monthly/
These directories contain executable scripts that run at the indicated interval. The cron daemon (or anacron) runs all scripts in these directories at the appropriate time:
$ ls /etc/cron.daily/
apport apt-compat aptitude dpkg logrotate man-db samba
To add a task that runs daily for all users (system-wide), drop an executable script in /etc/cron.daily/:
$ sudo nano /etc/cron.daily/my_maintenance_task
#!/bin/bash
# This script runs daily as root
find /tmp -type f -mtime +7 -delete
find /var/log -name "*.log.gz" -mtime +30 -delete
$ sudo chmod +x /etc/cron.daily/my_maintenance_task
anacron: For Systems Not Always Running
Standard cron requires the system to be running at the exact scheduled time. If the system is off at 2 AM when the daily backup is scheduled, the backup never runs.
anacron (irregular cron) solves this: it runs tasks based on how long ago they last ran rather than a specific clock time. When the system boots and anacron finds a daily task that has not run in more than 24 hours, it runs it shortly after boot.
$ cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# These replace cron's daily/weekly/monthly
1 5 cron.daily run-parts --report /etc/cron.daily
7 10 cron.weekly run-parts --report /etc/cron.weekly
@monthly 15 cron.monthly run-parts --report /etc/cron.monthly
The fields are: period delay identifier command
period— days between runs (1 = daily, 7 = weekly)delay— minutes to wait after boot before runningidentifier— a name for the job (used to track last run time)
On modern Ubuntu, the /etc/cron.daily/, /etc/cron.weekly/, and /etc/cron.monthly/ scripts are handled by anacron rather than cron directly, ensuring they run even if the system was off at the scheduled time.
Troubleshooting Cron Jobs
Cron jobs failing silently is a common frustration. Systematic debugging usually finds the problem quickly.
Step 1: Check if Cron is Running
$ systemctl status cron
$ ps aux | grep cron
Step 2: Check the System Log
Cron logs its activity to the system log:
$ grep CRON /var/log/syslog | tail -20
Feb 18 02:30:01 myserver CRON[1234]: (sarah) CMD (/home/sarah/scripts/backup.sh)
Feb 18 02:30:03 myserver CRON[1235]: (sarah) END CMD (/home/sarah/scripts/backup.sh)
If you see “CMD” entries, cron is triggering the job. If you see errors, the log may show what went wrong.
Step 3: Check Output Logs
If you are capturing output to a log file:
$ tail -f /var/log/backup.log
If there are no logs, the command may be failing silently. Temporarily redirect output:
*/5 * * * * /home/sarah/test.sh >> /tmp/cron_debug.log 2>&1
Step 4: Test the Command Manually
Run the exact command from the crontab manually to confirm it works:
$ /home/sarah/scripts/backup.sh
Step 5: Test with Cron’s Environment
The most common reason cron jobs fail: they work manually but fail in cron’s stripped environment. Test with minimal environment:
$ env -i HOME=/home/sarah LOGNAME=sarah PATH=/usr/bin:/bin SHELL=/bin/sh /home/sarah/scripts/backup.sh
If this fails but the normal manual run works, the problem is PATH or environment. Fix by using absolute paths in the script or setting PATH in the crontab.
Step 6: Check File Permissions
The script must be executable:
$ ls -la /home/sarah/scripts/backup.sh
-rwxr-xr-x 1 sarah sarah 1234 Feb 18 09:00 backup.sh
If not executable: chmod +x /home/sarah/scripts/backup.sh
Step 7: Check the Script’s Shebang
Make sure the first line of the script correctly specifies the interpreter:
#!/bin/bash # Correct for bash scripts
Without a shebang, /bin/sh is used — which may not support all bash features.
Practical Cron Examples
Daily Database Backup at 3 AM
0 3 * * * /usr/bin/pg_dump mydb > /backup/mydb_$(date +\%Y\%m\%d).sql 2>> /var/log/db_backup.log
Note: % characters in crontab must be escaped as \% — unescaped % is treated as a newline in crontab syntax.
Hourly Disk Space Check with Alert
0 * * * * /home/sarah/scripts/check_disk.sh
#!/bin/bash
THRESHOLD=90
USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "WARNING: Disk usage at ${USAGE}% on $(hostname)" | mail -s "Disk Alert" admin@example.com
fi
Weekly Log Rotation and Cleanup
0 0 * * 0 find /var/log/myapp/ -name "*.log" -mtime +30 -exec gzip {} \;
Runs every Sunday at midnight, compressing log files older than 30 days.
Backup Before System Updates (Every Tuesday at 1 AM)
0 1 * * 2 /home/sarah/scripts/pre_update_backup.sh && sudo apt update && sudo apt upgrade -y
Sync Files Every 30 Minutes During Business Hours
*/30 9-17 * * 1-5 /usr/bin/rsync -aq /home/sarah/projects/ /nas/projects/
Runs every 30 minutes from 9 AM to 5 PM, Monday through Friday.
Run at System Startup
@reboot sleep 30 && /home/sarah/scripts/startup_services.sh
sleep 30 gives the system time to fully initialize before the script runs.
cron vs. systemd Timers
Modern Linux systems also offer systemd timers as an alternative to cron. Systemd timers are more feature-rich but more complex to set up:
| Feature | cron | systemd timers |
|---|---|---|
| Setup complexity | Low (one crontab line) | Higher (two files: .service + .timer) |
| Logging | Syslog | journald (queryable with journalctl) |
| Missed jobs (if off) | Not run (unless anacron) | Can run on next boot |
| Per-job dependencies | No | Yes (run after network, etc.) |
| Randomized delay | No | Yes (RandomizedDelaySec=) |
| Persistent | Via /etc/crontab | systemctl enable timer_name.timer |
| On-calendar scheduling | Yes | Yes (more flexible expressions) |
Use cron for: simple recurring tasks, quick setup, widespread compatibility. Use systemd timers for: tasks that need service dependencies, better logging with journalctl, or when you are already managing complex systemd services.
Quick Reference: Cron Syntax
Time Field Values
| Field | Range | Special Values |
|---|---|---|
| Minute | 0–59 | *, */5, 0,30, 0-30 |
| Hour | 0–23 | *, */6, 9-17 |
| Day of Month | 1–31 | *, 1, 15, 1,15 |
| Month | 1–12 | *, JAN–DEC, 1-6 |
| Day of Week | 0–7 (0,7=Sun) | *, SUN–SAT, 1-5 |
Common Crontab Commands
| Task | Command |
|---|---|
| Edit crontab | crontab -e |
| List crontab | crontab -l |
| Remove all tasks | crontab -r |
| Edit root crontab | sudo crontab -e |
| Edit another user’s | sudo crontab -u username -e |
| Check cron logs | grep CRON /var/log/syslog |
Quick Schedule Reference
| Schedule | Crontab Line |
|---|---|
| Every minute | * * * * * |
| Every 5 minutes | */5 * * * * |
| Every hour | 0 * * * * |
| Every day at midnight | 0 0 * * * |
| Every day at 2:30 AM | 30 2 * * * |
| Every Monday 9 AM | 0 9 * * 1 |
| Weekdays 9 AM | 0 9 * * 1-5 |
| First of month midnight | 0 0 1 * * |
| At system startup | @reboot |
Conclusion: Automate Everything That Repeats
Cron is one of the most practically valuable tools in Linux. The tasks that run automatically — backups, log management, monitoring, report generation, maintenance — free you from the cognitive burden of remembering to do them manually and eliminate the failures that come from human forgetfulness.
The core skill is understanding the five time fields well enough to express any schedule you can imagine. Once you can read */15 9-17 * * 1-5 command as “every 15 minutes during business hours on weekdays,” you have mastered the most important part of cron.
Pair that with two operational habits — always use absolute paths, and always capture output to a log file — and your cron jobs will run reliably and debuggably. The combination of a correctly specified schedule, a well-written script with a proper shebang and absolute paths, and output captured to a log file is the recipe for cron jobs that run without supervision indefinitely.




