Cron jobs are the standard way to schedule recurring tasks on Linux and Unix systems, running a backup every night, clearing caches hourly, or sending a report each Monday morning. A cron job is simply a command paired with a schedule written in cron’s compact five field syntax, stored in a crontab file that the cron daemon checks every minute. This guide explains the syntax field by field, walks through practical examples, and covers the mistakes that trip up nearly everyone at first. For more everyday utilities, browse the FaridiaTech tools collection.
Every cron schedule line has five time fields followed by the command to run:
minute hour day-of-month month day-of-week command
jandec also work.monsun also work.Each field accepts a single value (30), a list (0,30), a range (1-5), a step (*/15 means every 15), or an asterisk (*) meaning “every.” So */15 in the minute field means “every 15 minutes,” and 1-5 in the day of week field means Monday through Friday.
0 9 * * 1-5 run at 9:00 AM, Monday through Friday (the classic weekday job).*/15 * * * * run every 15 minutes, all day, every day.30 2 * * * run daily at 2:30 AM (a popular window for backups, when systems are quiet).0 0 1 * * run at midnight on the first day of every month (monthly reports, log rotation).0 18 * * 5 run at 6:00 PM every Friday.45 6 * * 1 run at 6:45 AM every Monday.Many cron implementations also support special strings as a shorthand for common schedules: @daily (midnight daily), @hourly, @weekly, @monthly, and @reboot (run once when the machine starts). These are convenient, but the five field form is universal and worth learning.
The single most misunderstood cron behavior: when both the day of month and day of week fields are restricted (not *), the job runs when either condition matches, it is an OR, not an AND. So 0 9 15 * 3 runs at 9:00 AM on the 15th of the month and every Wednesday, not “on Wednesdays that are the 15th.” If you truly need “the 15th only when it is a Wednesday,” cron alone cannot express it; put the weekday test inside your script instead.
PATH, aliases, and shell profile are not loaded. Use absolute paths (/usr/bin/python3, not python3) or set PATH explicitly at the top of the crontab.% is special: it is converted to a newline, and everything after the first % is fed to the command’s standard input. Escape it as % this famously breaks date +%Y-%m-%d in cron lines.flock, or make the script exit if a previous instance is still running.>> /var/log/myjob.log 2>&1) so failures are visible.date.crontab -e edits your user’s crontab; system jobs live in /etc/crontab and /etc/cron.d/, which include an extra username field. Mixing up the formats is a classic error.Before committing to a schedule, run the exact command yourself in a minimal environment (env -i) to simulate what cron sees. Start with a frequent schedule like */5 * * * * during testing, watch the log file to confirm it behaves, then relax it to the real interval. And always double check the five fields, a job meant for 9:00 AM weekdays written as 0 9 1-5 * * would instead run on the 1st through 5th of every month.
Cron has scheduled Unix tasks since the 1970s, and it pairs naturally with understanding how Unix tracks time itself including the 2038 problem awaiting 32 bit systems.
Suppose you run a small shop website and want three things to happen without you: a database backup every night, a compressed archive of the site files every week, and an old log cleanup once a month. Here is how those wishes become three crontab lines, field by field, using only the syntax covered above.
The nightly backup should run when the shop is quiet, at 2:30 AM. In five fields that is minute 30, hour 2, and asterisks everywhere else: 30 2 * * *, followed by the command with an absolute path and output sent to a log, for example /usr/bin/mysqldump shopdb > /home/shop/backups/db.sql 2>> /home/shop/backups/backup.log. The weekly archive belongs at 3:15 AM on Sundays, day of week 0: 15 3 * * 0 running a tar command that packs the site folder into one dated file. The monthly cleanup runs at 4:00 AM on the first of the month: 0 4 1 * * with a find command that deletes backup files older than the retention window you chose.
It is worth pausing on what this little system does over time, because cron schedules are really promises about frequency. The nightly line fires about 30 times a month, the weekly line 4 or 5 times, and the monthly line once, roughly 35 automatic runs a month replacing a chore that is very easy to forget on exactly the night it matters. Add a health check at */15 * * * * and that one line alone runs 96 times a day (4 times an hour, 24 hours). Before you save a schedule like this, translate it into your own time zone with a timezone converter if the server lives elsewhere, a 2:30 AM backup that is really 2:30 PM in shop hours is a slow, busy, miserable backup. And if a job needs a long random token to upload its archive somewhere, generate that token properly with a password generator rather than typing something memorable; memorable is exactly what a credential should not be.
| Expression | Plain English meaning | How often it fires | Typical job |
|---|---|---|---|
* * * * * | Every minute | 1,440 times a day | Testing a new job before relaxing its schedule |
*/15 * * * * | Every 15 minutes | 96 times a day | Health checks, queue workers, cache warming |
30 2 * * * | 2:30 AM every day | Once a day | Nightly backups in the quiet hours |
0 9 * * 1-5 | 9:00 AM, Monday to Friday | About 22 times a month | Weekday reports to a team |
15 3 * * 0 | 3:15 AM every Sunday | 4 to 5 times a month | Weekly archives and maintenance |
0 0 1 * * | Midnight on the first of the month | Once a month | Monthly billing runs and log rotation |
@reboot | Once when the machine starts | Only at startup | Starting a service that must survive restarts |
crontab -e command exists precisely because it validates and installs the file the way the daemon expects.The thread running through every item above is the same: cron itself is almost never the broken part. The schedule fires faithfully; what fails is an assumption the job carried with it, about paths, permissions, timing, or the state of the machine. Make the job prove its assumptions in its log, and a schedule you set once can be trusted for years.
It means “every minute of every hour of every day”, the job runs once per minute. It is useful for testing but rarely what you want in production.
The most common causes: the cron daemon isn’t running, the schedule fields are wrong (check AM/PM in 24 hour time), the command relies on environment variables or a PATH that cron doesn’t provide, or output/errors went to an unread mail spool instead of a log file. Check the system cron log first.
Cron steps divide the hour evenly, so “every 90 minutes” can’t be written as a single step expression. The usual approach is to run hourly and have the script check whether 90 minutes have elapsed since its last run (via a timestamp file), or to schedule explicit times like 0 1,4,7,10,13,16,19,22 * * *.
No. When you save via crontab -e, the daemon picks up the change automatically (it checks file modification times every minute). Only hand edited files under /etc/cron.d/ on some systems need a daemon reload.
Cron remains the simplest, most portable choice for “run this command on a schedule,” and it works the same almost everywhere. Systemd timers offer more features (dependency handling, randomized delays, persistent catch up after downtime) on systemd based systems. For straightforward scheduling, cron is still the default answer.