A cron job is a command or script that the cron daemon, a background scheduler on Unix-like systems, runs automatically at fixed times or intervals you define. You specify the schedule with a five-field time expression stored in a crontab, and cron executes the job on its own, with no manual trigger required.
Cron is one of the oldest and most dependable pieces of Unix automation, and it is still everywhere: on Linux servers, macOS machines, containers, and virtual machines. If you have ever needed a backup to run every night, a cache to clear every hour, or a report to be emailed every Monday morning, cron is the tool that has traditionally done that work. This guide explains what a cron job actually is, how the syntax works field by field, how to write and install a crontab entry, the mistakes that trip people up most often, and when a different scheduler is the better tool.
What is cron and what is a cron job?
Cron is the scheduler: a long-running daemon (commonly cron or crond) that wakes up roughly once a minute, checks its schedule tables, and runs any commands that are due. A cron job is a single scheduled entry, one line pairing a time specification with a command. The file that holds those lines is called a crontab, short for cron table.
Each user on a system can have their own crontab, and there are system-wide crontabs too (typically /etc/crontab and files under /etc/cron.d/, plus convenience directories such as /etc/cron.daily/). A user crontab runs commands as that user, so permissions and file ownership follow whoever owns the crontab. This is worth remembering: a job in root’s crontab and a job in your own crontab run with different privileges.
How does cron syntax work?
A standard cron entry has five time-and-date fields followed by the command to run. The fields are always in the same order:
* * * * * command-to-run
| | | | |
| | | | +-- day of week (0-7, 0 and 7 = Sunday)
| | | +---- month (1-12)
| | +------ day of month (1-31)
| +-------- hour (0-23)
+---------- minute (0-59)
The table below lists each field, its valid range, and what it controls.
| Position | Field | Allowed values | Notes |
|---|---|---|---|
| 1 | Minute | 0-59 | Minute of the hour |
| 2 | Hour | 0-23 | 24-hour clock, 0 = midnight |
| 3 | Day of month | 1-31 | Calendar day |
| 4 | Month | 1-12 | Names like jan, feb also accepted in many implementations |
| 5 | Day of week | 0-7 | 0 and 7 both mean Sunday; names like sun, mon often accepted |
Within each field you can use four operators to build more than a single fixed value:
*(asterisk) means every valid value for that field. An asterisk in the minute field means every minute.,(comma) lists several discrete values.0,15,30,45in the minute field means the job runs four times an hour.-(hyphen) defines an inclusive range.1-5in the day-of-week field means Monday through Friday./(slash) is a step.*/5in the minute field means every fifth minute;0-30/10means minutes 0, 10, 20, and 30.
These operators combine. 0 9-17/2 * * 1-5 reads as: at minute 0, every second hour between 9 and 17, on every day of the month, Monday through Friday. In plain English, that is 9:00, 11:00, 13:00, 15:00, and 17:00 on weekdays.
What do the special @ strings mean?
Most modern cron implementations accept shorthand strings that replace the whole five-field expression. They are easier to read and less error-prone for common schedules:
| Shortcut | Equivalent | Meaning |
|---|---|---|
@yearly / @annually |
0 0 1 1 * |
Once a year, at midnight on 1 January |
@monthly |
0 0 1 * * |
Once a month, at midnight on the 1st |
@weekly |
0 0 * * 0 |
Once a week, at midnight on Sunday |
@daily / @midnight |
0 0 * * * |
Once a day, at midnight |
@hourly |
0 * * * * |
Once an hour, at minute 0 |
@reboot |
n/a | Once, when the cron daemon starts after boot |
@reboot is the odd one out: it is not tied to a clock time at all. It runs the job a single time when cron starts up, which is useful for launching a helper process at boot. Note that it runs when the daemon starts, which is not always the exact moment of a full system boot.
How do you write and install a crontab entry?
You edit your personal crontab with:
crontab -e
This opens the crontab in your default editor. Add one job per line. For example, to run a backup script every day at 2:30 AM:
30 2 * * * /usr/local/bin/backup.sh
To run a cleanup command every 15 minutes:
*/15 * * * * /usr/bin/php /var/www/app/cleanup.php
A few more useful commands:
crontab -llists the current user’s crontab.crontab -rremoves it entirely (there is no confirmation prompt, so be careful).crontab -u username -eedits another user’s crontab, if you have permission.
Because cron does not source your shell profile, it is common practice to set a couple of environment variables at the top of the crontab so every job inherits them:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=alerts@example.com
30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
That last line redirects both standard output and standard error into a log file, which turns silent failures into something you can actually read. If you set MAILTO and a local mail transport exists, cron also emails any output the job produces.
What are the most common cron mistakes?
Cron is reliable, but the environment it runs jobs in surprises almost everyone at least once. These are the failures that come up again and again.
The minimal environment
Cron does not load your .bashrc, .profile, aliases, or your interactive PATH. A command that works perfectly in your terminal can fail under cron because the binary is not on the stripped-down path cron provides. The fixes are to call binaries by absolute path (/usr/bin/python3, not python3) and to set PATH explicitly in the crontab.
Timezone assumptions
By default cron uses the system timezone, which on servers is often UTC. If you expect a job to fire at 9 AM local time, verify what the machine’s clock is set to. Vixie-style cron supports a CRON_TZ variable at the top of the crontab to change when jobs fire, but that only affects scheduling; it does not set TZ inside the running process, so your script may still see the system timezone unless you export TZ yourself. Daylight saving transitions add further edge cases around the skipped and repeated hour.
Day-of-month and day-of-week together
This one is genuinely counterintuitive. If you restrict both the day-of-month field and the day-of-week field (neither is *), most cron implementations run the job when either condition matches, not both. So 0 0 13 * 5 runs on the 13th of every month and on every Friday, not only on Friday the 13th. Keep one of the two fields as * unless you truly want the OR behavior.
The percent sign
In a crontab, an unescaped % is treated as a newline in the command. Any command using it, such as a date +%Y-%m-%d format, must escape each percent as % or the command will break in confusing ways.
Editing the wrong crontab
Running crontab -e as root edits root’s table; as a regular user it edits yours. Jobs quietly go missing when they were added to a different account than the one you are inspecting. Always confirm which user owns the crontab you are looking at.
Overlapping runs
Cron starts a job on schedule whether or not the previous run has finished. A task that occasionally takes longer than its interval can pile up and exhaust resources. Guard long jobs with a lock, for example flock:
*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /usr/local/bin/sync.sh
When should you not use cron?
Cron is excellent for simple, recurring commands on a single machine, but it is not always the right tool.
- You need dependency ordering, catch-up, or rich logging. On modern Linux, systemd timers pair a timer unit with a service unit, log to journald, can run missed jobs after downtime with
Persistent=true, and support explicit timezones inOnCalendar. They are more verbose to set up but more capable and observable. - The work must survive a server being replaced. If a machine is terminated, its crontab goes with it. Cloud schedulers such as managed cron services, serverless scheduled functions, or a Kubernetes CronJob decouple the schedule from any one host.
- You need sub-minute precision. Standard cron resolves down to one minute. For anything finer, use a dedicated scheduler or a long-running process with its own timing loop.
- You need visibility into whether jobs actually ran. Cron will not tell you when a job silently stops firing. Pair critical jobs with a dead-man’s-switch monitor that alerts when an expected run does not check in.
For most everyday automation, though, cron remains the pragmatic default: it is present on virtually every Unix-like system, needs no extra dependencies, and has decades of predictable behavior behind it. Learn the five fields, respect the minimal environment, log your output, and cron will run your jobs quietly for years.