← Back

2026

cron: Scheduling Jobs in Linux

Cron has been scheduling tasks on Unix systems since the 1970s. Here is how the daemon works, how to read and write crontab expressions and the pitfalls that catch everyone at least once.

Most things on a Linux system happen in response to something. A request comes in. A user runs a command. A file changes. Cron is different. It runs jobs on a schedule, regardless of what else is going on, regardless of whether anyone is logged in. Set it up once and it keeps running indefinitely.

The cron daemon has been part of Unix systems since the 1970s. It is one of those tools that has barely needed to change because the problem it solves is simple and well-defined. You want a command to run at a specific time on a regular interval. Cron does that reliably without any ceremony.

Understanding cron properly means understanding three things: the cron daemon itself, the crontab file format and the places on the filesystem where scheduled jobs can live. Once those three pieces click together, everything else follows naturally.

The cron daemon

Cron runs as a background daemon, typically called crond on Red Hat-based systems. On Debian-based systems the package is usually called cron. Either way the daemon starts at boot, stays running in the background and wakes up once per minute to check whether any scheduled jobs are due.

The checking mechanism is straightforward. Every minute the daemon reads all active crontab files, compares each entry against the current time and launches any jobs whose schedule matches. The job runs as a subprocess, inherits a minimal environment and produces output that gets mailed to the owning user unless you redirect it somewhere else.

Check that the daemon is running

systemctl status cron

# Debian / Ubuntu

systemctl status crond

# RHEL / CentOS / Fedora

The crontab syntax

Each line in a crontab file that describes a scheduled job follows the same format: five time fields followed by the command to run. The fields are positional. Getting them in the wrong order is the most common mistake people make when writing cron entries for the first time.

Format

MIN  HOUR  DOM  MON  DOW  COMMAND

DOM = day of month    MON = month    DOW = day of week

Minute

0 – 59

The minute within the hour when the job runs

Hour

0 – 23

The hour of the day in 24-hour format

Day of month

1 – 31

A specific day in the month. Use * to mean every day

Month

1 – 12

The month number. Named abbreviations like jan and feb are also accepted

Day of week

0 – 7

0 and 7 both mean Sunday. Named abbreviations like mon and fri are accepted

Each field also accepts special characters. An asterisk means every valid value. A comma separates a list of values. A forward slash after an asterisk expresses a step interval. The expression */5 in the minutes field means every five minutes. A hyphen defines a range, so 1-5 in the day-of-week field covers Monday through Friday.

Reading cron expressions

The best way to get comfortable with the syntax is to read real examples. Work through each one from left to right: minute, hour, day of month, month, day of week.

0 2 * * *  /opt/scripts/backup.sh

Every day at 02:00

*/15 * * * *  /opt/scripts/health-check.sh

Every 15 minutes

0 9 * * 1  /opt/scripts/weekly-report.sh

Every Monday at 09:00

30 4 1 * *  /opt/scripts/monthly-cleanup.sh

First day of each month at 04:30

0 8-18 * * 1-5  /opt/scripts/sync.sh

Every hour from 08:00 to 18:00 on weekdays

Special strings

Most cron implementations support a set of named shortcuts that replace the five-field syntax for common intervals. They are easier to read at a glance and less likely to be mistyped.

@reboot

Run once at startup

@hourly

0 * * * *

@daily

0 0 * * *

@weekly

0 0 * * 0

@monthly

0 0 1 * *

@yearly

0 0 1 1 *

The @reboot entry is worth knowing. It runs the command once each time the system boots, which makes it useful for things that need to start at boot but do not warrant a full systemd service unit.

Managing crontabs

Each user on the system can have their own crontab. The crontab command is how you create, view and edit it. User crontabs are stored under /var/spool/cron/ but you should never edit those files directly. Always go through the crontab command so the daemon is properly notified.

crontab -e

Open your crontab in the default editor. Creates one if it does not exist yet

crontab -l

Print your current crontab to the terminal

crontab -r

Delete your crontab entirely. No confirmation prompt. Be careful

crontab -u lars -l

View another user's crontab. Requires root

crontab -u lars -e

Edit another user's crontab. Requires root

Worth knowing

The EDITOR environment variable controls which editor opens when you run crontab -e. If you prefer vim over nano, set export EDITOR=vim in your shell profile before running the command.

System-wide cron locations

User crontabs handle per-user scheduled tasks. System-level scheduled work lives in a different set of locations under /etc/. Some of these are directories where you drop scripts. Others are files with full crontab syntax that packages maintain directly.

/etc/cron.hourly/

Drop an executable script here to run it every hour. No schedule needed in the file itself.

/etc/cron.daily/

Scripts that run once per day. Exact time depends on the distribution, usually somewhere in the early morning.

/etc/cron.weekly/

Once per week. Good for tasks like rotating old archives.

/etc/cron.monthly/

Once per month. Anything that needs to happen on a slower cadence without custom timing.

/etc/cron.d/

Individual crontab files dropped by packages. Same syntax as a user crontab but with an extra username field. These are managed alongside the software that needs them.

The main system crontab lives at /etc/crontab. It uses the same five time fields but adds a username column after them so the daemon knows which user to run each command as. This is the file that ties together the drop-in directories.

/etc/crontab — notice the USERNAME field

# MIN HOUR DOM MON DOW USER COMMAND

17 * * * * root cd / && run-parts --report /etc/cron.hourly

25 6 * * * root test -x /usr/sbin/anacron || run-parts /etc/cron.daily

47 6 * * 7 root test -x /usr/sbin/anacron || run-parts /etc/cron.weekly

52 6 1 * * root test -x /usr/sbin/anacron || run-parts /etc/cron.monthly

Environment variables and output

Jobs run by cron inherit a minimal environment. The PATH variable is much shorter than what you get in an interactive shell, which means commands that work fine when you type them manually can fail silently inside cron because the binary is not in the restricted path.

The safest approach is to use absolute paths for every binary in your cron scripts. Alternatively, set PATH explicitly at the top of your crontab file. Any variable assignments at the top of the file apply to all jobs below them.

Setting PATH in a crontab

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

MAILTO=""

# jobs follow below

0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

By default, any output from a cron job gets emailed to the user who owns the crontab. On most servers there is no mail daemon configured, so that output disappears. Setting MAILTO="" suppresses it entirely. A better approach is to redirect output explicitly in the job definition using >> /path/to/logfile 2>&1 so you have something to read when a job misbehaves.

Common pitfalls

Missing PATH

A script works fine when you run it by hand but fails in cron with a command not found error. Fix it by using absolute paths in scripts or setting PATH at the top of the crontab.

Percent signs in commands

The percent sign % is special in crontab syntax. It gets interpreted as a newline character. If your command includes a date format string like %Y-%m-%d, escape each percent sign with a backslash.

No output, no clues

Jobs fail silently when output is not captured anywhere. Always redirect stdout and stderr to a log file. Without that, there is no record of what went wrong.

Missed jobs after downtime

Standard cron does not run missed jobs after the system comes back up from an outage. If the machine was off at 02:00 when the backup was scheduled, that backup does not run. anacron exists specifically to solve this for daily and weekly jobs.

Concurrent job accumulation

If a job takes longer than its interval, cron will start another instance on the next trigger. Two concurrent instances of the same job writing to the same files or database is usually not what you want. Use a lock file or a wrapper like flock to prevent it.

Where it fits

Cron sits in the automation layer of a Linux system. It is what runs your backups at 02:00, rotates your logs each morning and cleans up temporary files on a weekly schedule. It requires no special configuration beyond a crontab entry and it has been doing this reliably for decades.

For simple scheduled scripts it remains the most straightforward option available. The syntax is terse but readable once you know the field order. The lack of built-in logging is the main friction point and redirecting output to a file is the standard way to work around it.

When your needs grow beyond what cron handles well, systemd timers are the natural next step. They integrate with the journal, handle missed jobs through the Persistent=true option and give you proper dependency management. Many production systems use both: cron for the small operational scripts that have always lived there and systemd timers for anything new that benefits from better observability. Neither replaces the other entirely.