Cron Jobs Explained
Cron is one of the oldest and most widely used tools on Unix and Linux. It runs in the background as a daemon, wakes up every minute, checks whether any scheduled job should run, and executes it. That simple mechanism underlies countless automated tasks: database backups, log rotation, certificate renewal, report generation, and system maintenance scripts.
The crontab format
A crontab entry has six parts: five time fields followed by the command.
* * * * * command to execute
| | | | |
| | | | +-- day of week (0-7, Sunday = 0 or 7)
| | | +---- month (1-12)
| | +------ day of month (1-31)
| +-------- hour (0-23)
+---------- minute (0-59)
Field values
Each field accepts:
* any value (every minute, every hour, etc.)
5 a specific value (at minute 5)
1,15,30 a list (at minutes 1, 15, and 30)
1-5 a range (minutes 1 through 5)
*/5 a step (every 5 minutes)
1-30/5 steps within a range (every 5 minutes from 1 to 30)
Common schedule examples
# Every minute
* * * * *
# Every hour, at minute 0
0 * * * *
# Every day at 2:30 AM
30 2 * * *
# Every Monday at midnight
0 0 * * 1
# Every weekday (Mon-Fri) at 9 AM
0 9 * * 1-5
# Every 15 minutes
*/15 * * * *
# Every 5 minutes during business hours (9 AM to 5 PM, Mon-Fri)
*/5 9-17 * * 1-5
# First day of every month at noon
0 12 1 * *
# Every January 1st at midnight
0 0 1 1 *
# Twice a day: at 8 AM and 8 PM
0 8,20 * * *
# Every 6 hours
0 */6 * * *
# Every Sunday at 3:15 AM
15 3 * * 0
# Every 2 hours between midnight and 6 AM
0 0-6/2 * * *
Named shortcuts
Most cron implementations support shortcuts for common schedules:
@reboot # run once, at startup
@yearly # once a year (0 0 1 1 *)
@annually # same as @yearly
@monthly # once a month (0 0 1 * *)
@weekly # once a week (0 0 * * 0)
@daily # once a day (0 0 * * *)
@midnight # same as @daily
@hourly # once an hour (0 * * * *)
# Run a script on every reboot
@reboot /home/alice/scripts/startup.sh
# Run a backup every day at midnight
@daily /usr/local/bin/backup.sh
Managing user crontabs
# Edit your crontab (opens in $EDITOR)
crontab -e
# List your current crontab entries
crontab -l
# Remove your entire crontab (no confirmation -- be careful)
crontab -r
# Edit another user's crontab (root only)
sudo crontab -u alice -e
# List another user's crontab
sudo crontab -u alice -l
# Remove another user's crontab
sudo crontab -u alice -r
User crontab files are stored in /var/spool/cron/crontabs/ (owned by root, not directly editable).
A complete crontab example
# This is alice's crontab
# View with: crontab -l
# Edit with: crontab -e
# Suppress email output for all jobs in this file
MAILTO=""
# Use a useful PATH (cron's default is minimal)
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Redirect output to a log file with a timestamp
LOGFILE=/home/alice/cron.log
# Run a backup script every day at 2 AM
0 2 * * * /home/alice/scripts/backup.sh >> /home/alice/logs/backup.log 2>&1
# Sync a directory every 15 minutes
*/15 * * * * rsync -a /home/alice/work/ /mnt/backup/work/ >> /home/alice/logs/sync.log 2>&1
# Check disk usage and send a warning if over 90% (weekdays only)
0 9 * * 1-5 /home/alice/scripts/diskcheck.sh
# Run a report on the first of every month
0 6 1 * * /home/alice/scripts/monthly-report.sh >> /home/alice/logs/reports.log 2>&1
# Restart a service that occasionally crashes (every hour)
0 * * * * systemctl is-active myapp || systemctl start myapp
System-wide cron configuration
/etc/crontab
The main system crontab. It has an extra username field between the time fields and the command:
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; }
/etc/cron.d/
Individual crontab files for packages and administrators. Format is the same as /etc/crontab (with the username field):
# List files in cron.d
ls /etc/cron.d/
# Example: /etc/cron.d/certbot
cat /etc/cron.d/certbot
# 0 */12 * * * root test -x /usr/bin/certbot && perl -e 'sleep int(rand(43200))' && certbot -q renew
/etc/cron.daily/, /etc/cron.weekly/, etc.
These directories contain scripts (not crontab entries) that are run by run-parts at the appropriate interval. Scripts placed here do not need crontab syntax — just make them executable:
# List daily scripts
ls /etc/cron.daily/
# Install a custom daily script
sudo cp myscript.sh /etc/cron.daily/myscript
sudo chmod 755 /etc/cron.daily/myscript
# Test run-parts manually
sudo run-parts --test /etc/cron.daily # shows which scripts would run
sudo run-parts /etc/cron.daily # actually runs them
Scripts in these directories must not have a file extension (no .sh) on Debian and Ubuntu — run-parts ignores files with dots in the name by default.
The cron environment problem
This is the most common reason cron jobs fail when the same command works in a shell. Cron runs commands with a minimal environment:
PATH=/usr/bin:/bin
HOME=/root (or the user's home)
SHELL=/bin/sh
MAILTO=username (for email output)
There is no .bashrc, no aliases, no nvm, no conda, no rbenv, and no custom PATH entries. Commands that rely on these will fail silently in cron.
Solutions:
# Option 1: Use absolute paths everywhere
0 2 * * * /usr/local/bin/node /home/alice/scripts/report.js
# Option 2: Set PATH at the top of the crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/alice/.local/bin
0 2 * * * node /home/alice/scripts/report.js
# Option 3: Source the user environment in the command
0 2 * * * bash -l -c 'node /home/alice/scripts/report.js'
# Option 4: Source the environment file at the start of the script
#!/bin/bash
source /home/alice/.profile
source /home/alice/.bashrc
# ... rest of script
Handling output and logging
Cron emails any output (stdout and stderr) to the crontab owner by default. On a server without a working mail system, this silently discards the output.
# Suppress all output (run silently)
0 2 * * * /path/to/script > /dev/null 2>&1
# Log stdout to a file, suppress stderr
0 2 * * * /path/to/script >> /var/log/myscript.log
# Log both stdout and stderr
0 2 * * * /path/to/script >> /var/log/myscript.log 2>&1
# Log with timestamps
0 2 * * * echo "$(date): starting" >> /var/log/myscript.log && /path/to/script >> /var/log/myscript.log 2>&1
# Suppress email but keep output for inspection
MAILTO=""
0 2 * * * /path/to/script >> /var/log/myscript.log 2>&1
# Email output only on error
0 2 * * * /path/to/script || mail -s "Script failed" alice@example.com < /tmp/script-error.log
Checking the cron log
When a cron job does not run or produces unexpected results, the cron log is the first place to look:
# Debian/Ubuntu: cron logs to syslog
grep CRON /var/log/syslog
grep CRON /var/log/syslog | tail -20
# Or via journalctl
journalctl -u cron
journalctl -u cron --since "1 hour ago"
# Fedora/RHEL
journalctl -u crond
# See that cron is running
systemctl status cron
systemctl status crond # Fedora/RHEL name
# Enable verbose cron logging (Debian/Ubuntu)
# Uncomment or add in /etc/rsyslog.conf or /etc/rsyslog.d/50-default.conf:
# cron.* /var/log/cron.log
Anacron: for machines that are not always on
cron assumes the machine is running 24/7. If a scheduled job time passes while the machine is off, cron does not run the missed job when the machine comes back on. anacron fixes this by tracking when jobs were last run and executing them if they are overdue:
# Anacron configuration
cat /etc/anacrontab
# The format: period delay job-id command
# 1 5 cron.daily run-parts /etc/cron.daily
# 7 10 cron.weekly run-parts /etc/cron.weekly
# @monthly 15 cron.monthly run-parts /etc/cron.monthly
# period: days between runs
# delay: minutes to wait after boot before running
# job-id: name used for tracking the last run time
# Check when anacron last ran a job
ls -la /var/spool/anacron/
cat /var/spool/anacron/cron.daily
On desktop Linux, the cron.daily, cron.weekly, and cron.monthly directories are run by anacron rather than cron directly. This ensures log rotation, package cleanup, and other maintenance tasks happen even on machines that are not always on.
Quick reference
# Edit your crontab
crontab -e
# List your crontab
crontab -l
# Common schedule patterns
*/5 * * * * # every 5 minutes
0 * * * * # every hour
0 2 * * * # every day at 2 AM
0 2 * * 0 # every Sunday at 2 AM
0 2 1 * * # first of every month at 2 AM
@reboot # on every boot
# Debug: run the exact command cron would run
env -i HOME=$HOME PATH=/usr/bin:/bin SHELL=/bin/sh /path/to/script
# Check cron logs
journalctl -u cron --since "1 hour ago"
grep CRON /var/log/syslog | tail -20
# Cron directories
/etc/cron.d/ # individual system crontab files
/etc/cron.daily/ # scripts run daily
/etc/cron.weekly/ # scripts run weekly
/etc/cron.monthly/ # scripts run monthly
/var/spool/cron/crontabs/ # stored user crontabs
Cron’s simplicity is its strength. The five-field time specification is easy to read once you know the field order, and the tool has been reliable on Unix systems for decades. The most important habits are using absolute paths, logging all output explicitly, and checking the cron log when something does not run.
Frequently Asked Questions
What is cron and how does it work?
Cron is a time-based job scheduler that runs commands automatically at specified times or intervals. The crond daemon runs continuously in the background, checking every minute whether any scheduled job should be executed. Jobs are defined in crontab files: per-user files managed with crontab -e, and system-wide files in /etc/cron.d/ and /etc/crontab. Each job entry specifies a schedule using five time fields (minute, hour, day, month, weekday) followed by the command to run.
What does * * * * * mean in cron?
-
-
-
-
- means “every minute of every hour of every day of every month on every day of the week” — so the command runs once per minute, continuously. Each asterisk represents one of the five time fields: 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). An asterisk in a field means “every valid value for this field.”
-
-
-
How do I edit my crontab?
Run crontab -e to open your user crontab in your default editor (usually nano or vi). Each line is either a comment (starting with #), an environment variable assignment (VAR=value), or a cron job (five time fields followed by a command). Save and exit the editor to install the new crontab. crontab -l lists your current jobs without editing. crontab -r removes your crontab entirely (be careful with this — it has no confirmation prompt).
Why is my cron job not running?
Common reasons: the schedule syntax is wrong (verify with crontab -l and a cron expression validator); the command works in a shell but fails in cron because cron runs with a minimal environment (no PATH beyond /usr/bin:/bin, no aliases, no .bashrc); the command requires a display or interactive terminal; the script is not executable (chmod +x); or cron is not running (systemctl status cron). Check the cron log (grep CRON /var/log/syslog on Ubuntu, or journalctl -u cron) for error messages. When debugging, redirect output: command >> /tmp/cron-test.log 2>&1.
What is the difference between /etc/crontab and /etc/cron.d/?
/etc/crontab is the main system crontab file. It has an extra field after the time fields specifying which user to run the command as (e.g., root or www-data). /etc/cron.d/ is a directory where packages and administrators drop individual crontab-format files, one per application. Files in /etc/cron.d/ also require the username field. /etc/cron.daily/, /etc/cron.weekly/, and /etc/cron.hourly/ are directories containing scripts that run at those intervals; the files are run by run-parts and do not need crontab syntax.