How to Use cron on Linux: Complete Scheduling Guide

Tested on: Ubuntu 24.04 LTS · Debian 12 · Fedora 40 — Last updated: June 2026
Cron is Linux's built-in task scheduler — a daemon that wakes every minute, checks its configuration, and fires commands whose schedule matches the current time. If you need to run a backup at 2 AM, rotate logs daily, or poll an API every five minutes, cron handles it reliably without any third-party tooling. This guide covers everything from raw syntax to environment pitfalls, logging strategies, and when to reach for systemd timers instead.
Prerequisites
- A Linux system running Ubuntu 24.04, Debian 12, Fedora 40, or any modern distribution
- A non-root user with
sudoprivileges (required for system-wide jobs) - Basic terminal familiarity and a text editor (nano or vim)
- The cron daemon installed and active — most distros ship it by default
Verify cron is installed and running before proceeding:
# Ubuntu/Debian — service is named 'cron'
systemctl status cron
# Fedora/RHEL/Arch — service is named 'crond'
systemctl status crondIf it is not installed:
# Ubuntu/Debian
sudo apt install cron
sudo systemctl enable --now cron
# Fedora/RHEL
sudo dnf install cronie
sudo systemctl enable --now crondHow Cron Works
Cron is a daemon — it starts at boot and runs continuously in the background. Every minute it wakes, reads all crontab files, and executes any job whose time fields match the current minute. It does not accumulate missed runs; if the system is off at the scheduled time, the job simply does not execute. (If you need missed-run handling, look at anacron, covered at the end of this guide.)
Cron reads from three distinct configuration sources:
- User crontabs: Each user can maintain a personal crontab via
crontab -e. Jobs run as that user with that user's environment and permissions. Stored under/var/spool/cron/crontabs/. - System crontab:
/etc/crontab— a global file that adds a username field so jobs can run as any system user. Managed by root. - Drop-in directories:
/etc/cron.d/for per-application crontab fragments (same format as/etc/crontab), and/etc/cron.hourly/,/etc/cron.daily/,/etc/cron.weekly/,/etc/cron.monthly/for executable scripts run byrun-parts.
crontab Basics
Always use the crontab command to edit your schedule — never edit spool files directly. The command validates your syntax before saving and signals the daemon immediately.
# Open your crontab in your default editor
crontab -e
# List all current entries
crontab -l
# Delete your entire crontab — no confirmation prompt
crontab -r
# Edit or list another user's crontab (as root)
sudo crontab -u www-data -e
sudo crontab -u deploy -lThe first time you run crontab -e, you will be prompted to choose an editor. Your selection is saved to ~/.selected_editor. To change it later:
select-editorCron Syntax Explained
Every user crontab job entry has this structure — five time fields followed by the command:
# ┌───────────── minute (0-59)
# │ ┌─────────── hour (0-23)
# │ │ ┌───────── day of month (1-31)
# │ │ │ ┌─────── month (1-12 or Jan-Dec)
# │ │ │ │ ┌───── day of week (0-7, 0 and 7 both = Sunday, or Sun-Sat)
# │ │ │ │ │
# * * * * * /path/to/command --argsOperators
* # wildcard — every valid value
5 # exact value — the 5th minute, or 5th hour, etc.
1-5 # inclusive range — 1, 2, 3, 4, 5
1,3,5 # list — runs on each of these values
*/15 # step — every 15 units: 0, 15, 30, 45
0-23/2 # step with range — every 2 hours from 0: 0, 2, 4 … 22Reading Time Fields Together
0 2 * * * # 2:00 AM every day
30 8 * * 1-5 # 8:30 AM every weekday (Mon-Fri)
0 */2 * * * # top of every 2nd hour: 00:00, 02:00, 04:00 …
15 3 1 * * # 3:15 AM on the 1st of every month
*/15 9-17 * * 1-5 # every 15 min during business hours, weekdays only
0 0 * * 0 # midnight every SundayCritical quirk — day-of-month vs. day-of-week: If you specify a non-wildcard value in both the day-of-month and day-of-week fields, cron uses OR logic, not AND. The entry 0 0 1 * 5 fires at midnight on the 1st of every month and at midnight every Friday — not only on Fridays that happen to fall on the 1st. This surprises nearly everyone the first time.
Special Time Strings
These shortcuts replace the five-field block entirely. They are clearer to read and safe to use in any crontab:
| String | Equivalent | Meaning |
|---|---|---|
@reboot | — | Once at system startup |
@yearly / @annually | 0 0 1 1 * | Jan 1st at midnight |
@monthly | 0 0 1 * * | First of every month at midnight |
@weekly | 0 0 * * 0 | Every Sunday at midnight |
@daily / @midnight | 0 0 * * * | Every day at midnight |
@hourly | 0 * * * * | Top of every hour |
@reboot /home/deploy/start-agent.sh
@daily /home/deploy/scripts/cleanup.sh >> /var/log/cleanup.log 2>&1
@hourly /usr/local/bin/health-check@reboot is especially useful for launching user-space services or mounting FUSE filesystems without writing a full systemd unit file.
Practical Examples
Nightly Backup at 2 AM
0 2 * * * /home/deploy/scripts/backup.sh >> /var/log/backup.log 2>&1Disk Space Alert Every Hour
0 * * * * df -h | mail -s "Disk Usage $(hostname) $(date +%F)" ops@example.comClean Up Temp Files Weekly
0 0 * * 0 find /tmp -type f -mtime +7 -deletePostgreSQL Dump — Weekdays at 6 PM
0 18 * * 1-5 /usr/bin/pg_dump mydb > /backup/db_$(date +%Y%m%d).sqlNote the escaped %. The percent sign (%) is special in crontab — everything after an unescaped % is treated as a newline and passed to the command on stdin. Always escape % characters in crontab entries.
Every 15 Minutes During Business Hours
*/15 9-17 * * 1-5 /opt/myapp/bin/sync-queue >> /var/log/sync.log 2>&1Run at Boot, Then Daily
@reboot /opt/myapp/bin/worker --daemon
0 4 * * * /opt/myapp/bin/daily-reportSystem-Wide Cron Jobs
/etc/crontab
The system crontab inserts a username field between the time specification and the command:
cat /etc/crontab# min hour dom month 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/ — Per-Application Jobs
This is the correct place for application or package cron jobs. Each file follows the same format as /etc/crontab. Keeping jobs in separate files makes them easy to audit, disable, or remove without touching the system crontab.
sudo nano /etc/cron.d/myappSHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=ops@example.com
# Run as the 'deploy' user every night at 1 AM
0 1 * * * deploy /opt/myapp/scripts/maintenance.sh >> /var/log/myapp-maintenance.log 2>&1Filename rules for /etc/cron.d/: Files must consist only of alphanumerics, hyphens, and underscores. Names containing dots (e.g., myapp.conf) are silently ignored by run-parts on Debian-based systems. Name your file myapp or myapp-maintenance, not myapp.cron.
Environment Variables in Crontab
This is where most cron debugging time goes. Cron runs jobs with a minimal environment — nothing like your interactive shell session. The PATH is typically only /usr/bin:/bin. Variables like HOME, DISPLAY, NVM_DIR, VIRTUAL_ENV, and any exports from your .bashrc or .profile are not present.
You can set variables at the top of your crontab — they apply to all jobs below them:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/deploy/.local/bin
MAILTO=ops@example.com
HOME=/home/deploy
0 2 * * * /home/deploy/scripts/backup.shSetting MAILTO to an email address causes cron to mail the output of every job to that address. Set MAILTO="" to suppress all email. Setting it per-job using redirection gives you the most control.
The safest approach is to always use absolute paths for both commands and any files they reference, and to source your environment explicitly inside scripts that need it:
#!/bin/bash
# At the top of your cron-invoked script:
source /home/deploy/.profile
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Rest of your script...
node /opt/myapp/index.jsLogging Cron Output
By default, cron emails any output to the local system user. In practice, configure explicit logging in each job entry:
# Append stdout and stderr to a log file
0 2 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>&1
# Separate stdout and stderr into different files
0 2 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>>/var/log/backup-errors.log
# Discard all output (silent jobs)
*/5 * * * * /usr/local/bin/check.sh > /dev/null 2>&1
# Add timestamps to log output — useful for multi-run scripts
0 * * * * /usr/local/bin/health.sh 2>&
