Somewhere on nearly every Linux server, a small daemon is quietly checking the clock once a minute and running whatever it finds scheduled. That daemon is cron, and despite being more than forty years old, it still runs the world's backups, reports, cleanups, and reminders. If you can read its five-field schedule format, you can automate almost anything.
What cron is and how it works
Cron is a time-based job scheduler built into Unix-like systems. You describe jobs in a file called a crontab, and the cron daemon wakes up every minute, compares the current time against every line, and runs the commands whose schedule matches. Each user can have their own crontab, and the system itself keeps one for maintenance tasks.
The beauty of cron is its simplicity: there is no queue to manage, no service to keep alive, and no programming required. A single line in a crontab is a complete automation. The tradeoff is that cron is deliberately dumb — it does not retry failed jobs, it does not prevent overlapping runs, and if the machine is off when a job is due, that run is simply skipped.
Reading the five fields
Every cron line starts with five time fields, followed by the command to run:
- Minute (0–59)
- Hour (0–23)
- Day of month (1–31)
- Month (1–12)
- Day of week (0–7, where both 0 and 7 mean Sunday)
An asterisk means "every value." So 30 2 * * * /home/user/backup.sh runs at 2:30 AM every day. The fields support lists (1,15), ranges (1-5 for Monday through Friday), steps (*/15 for every 15 minutes), and a few friendly shorthands like @daily and @reboot. The most common mistake is reading the fields in the wrong order — it is minute first, then hour, the reverse of how most people say a time.
Common schedules, decoded
0 9 * * 1-5— 9:00 AM, Monday through Friday*/15 * * * *— every 15 minutes, around the clock0 0 1 * *— midnight on the first day of every month0 2 * * 0— 2:00 AM every Sunday30 4 1,15 * *— 4:30 AM on the 1st and 15th of each month
A subtle trap: when both day-of-month and day-of-week are restricted, cron treats them as "either," not "both." The line 0 0 15 * 5 runs on the 15th of the month and every Friday, not only on Fridays that fall on the 15th. If you need both conditions, put the extra check inside the command or script itself.
Worked example: a nightly backup job
Say you want a backup every night at 2:30 AM, with a log file you can check in the morning. The crontab line looks like this:
30 2 * * * /home/user/bin/backup.sh >> /home/user/logs/backup.log 2>&1
The redirection matters: cron captures a job's output and, by default, emails it to the job owner. Redirecting both standard output and errors to a log file keeps your inbox quiet and gives you a record to inspect. Running backups at 2:30 AM rather than on the hour also avoids the stampede of every other job scheduled at midnight, which is a real source of slowdowns on shared servers.
The gotchas that silently break jobs
Most cron failures are silent, because a job that never runs produces no error you will notice. The classic causes:
- A minimal PATH. Cron runs with a stripped-down environment, so a command that works in your terminal fails under cron because it cannot find a binary. Use absolute paths for everything, or set PATH at the top of the crontab.
- Missing environment variables. Your shell profile is not loaded by cron, so variables like API keys or language settings must be set explicitly in the crontab or the script.
- Percent signs. In a crontab line, an unescaped
%is converted to a newline, which mangles commands containing it. Escape them or, better, put the logic in a script and call the script. - Overlapping runs. If a job takes longer than its interval, cron happily starts a second copy. For long jobs, use a lock file (for example, with
flock) so only one instance runs at a time. - Daylight saving time. Jobs scheduled during the repeated hour in autumn may run twice, and jobs in the skipped hour in spring may not run at all. Avoid scheduling critical jobs between 2:00 and 3:00 AM.
When a job fails, check three things in order: the system cron log (usually under /var/log/cron or via journalctl), the job's own log file, and whether the command works when run with cron's minimal environment. Nine times out of ten, the answer is a path or an environment variable.
Cron vs. modern alternatives
On modern Linux systems, systemd timers are the official alternative: they offer dependency handling, logging through journald, and calendar expressions that read almost like English. Cloud schedulers and managed job queues go further, with retries and alerting built in. But cron's advantage is that it is everywhere, it has no dependencies, and its format has not changed in decades — a crontab written in 1995 still runs today. For straightforward "run this at that time" jobs, that stability is hard to beat. Reach for the fancier tools when you need retries, dependencies, or guaranteed execution on machines that sleep.
Frequently asked questions
What is the difference between cron and at?
Cron runs jobs on a repeating schedule, while at runs a job once at a specified time. Use cron for anything recurring — backups, reports, cleanups — and at for one-off tasks like restarting a service tonight at midnight. Both are built into most Unix-like systems.
Why did my cron job not run?
The most common causes are a wrong PATH, a missing environment variable, a relative path in the command, or a schedule written in the wrong field order. Check the system cron log first, then run the exact command with a minimal environment to reproduce the failure. Also confirm the cron daemon itself is running.
Does cron run jobs while the computer is off or asleep?
No. Cron only runs while the machine is powered on and the daemon is active, and a missed run is not made up later. For laptops and desktops that sleep, use a tool designed for that case, such as anacron or a systemd timer with persistent scheduling, which runs the missed job at the next wake.
How do I stop cron from emailing me job output?
Redirect the job's output to a log file or discard it entirely, instead of letting cron capture it. You can also set MAILTO to an empty value at the top of the crontab to disable mail for all jobs in that file.