Cron Jobs Explained with Examples

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.

The five fields, explained

A large clock with gears, a blank calendar and a small server illustrating cron jobs that schedule recurring computer tasks automatically.

Every cron schedule line has five time fields followed by the command to run:

minute hour day-of-month month day-of-week   command

  • Minute (0 to 59): the minute past the hour.
  • Hour (0 to 23): the hour of the day in 24 hour time.
  • Day of month (1 to 31): the calendar date.
  • Month (1 to 12): the month, where names like jandec also work.
  • Day of week (0 to 7): the weekday, where both 0 and 7 mean Sunday, and names like 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.

Practical examples

  • 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 day of month vs day of week trap

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.

Common mistakes (and how to avoid them)

  • Assuming your shell environment. Cron runs commands with a minimal environment, your usual 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.
  • Unescaped percent signs. In a crontab entry, % 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.
  • Overlapping runs. If a job takes longer than its interval (an hourly job that sometimes runs 90 minutes), two copies run simultaneously and can corrupt shared data. Guard with a lock such as flock, or make the script exit if a previous instance is still running.
  • Silent failures. Cron emails output to the job owner, but on many modern systems that mail goes nowhere. Redirect output to a log file (>> /var/log/myjob.log 2>&1) so failures are visible.
  • Timezone surprises. Cron uses the system timezone. After a server move or daylight saving change, “2:30 AM” may no longer mean what you think, verify with date.
  • Editing the wrong crontab. 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.

Testing a cron job safely

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.

A worked example: building a small backup routine from scratch

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.

Common schedules compared

ExpressionPlain English meaningHow often it firesTypical job
* * * * *Every minute1,440 times a dayTesting a new job before relaxing its schedule
*/15 * * * *Every 15 minutes96 times a dayHealth checks, queue workers, cache warming
30 2 * * *2:30 AM every dayOnce a dayNightly backups in the quiet hours
0 9 * * 1-59:00 AM, Monday to FridayAbout 22 times a monthWeekday reports to a team
15 3 * * 03:15 AM every Sunday4 to 5 times a monthWeekly archives and maintenance
0 0 1 * *Midnight on the first of the monthOnce a monthMonthly billing runs and log rotation
@rebootOnce when the machine startsOnly at startupStarting a service that must survive restarts

Common mistakes to avoid when a job must not fail

  • Testing as yourself, scheduling as another user. A script that runs perfectly from your login can fail under cron because the job belongs to a different user with a different home directory and different permissions. Test under the same account that will run the schedule.
  • Forgetting the executable bit or the interpreter line. A script file that is not marked executable, or that lacks a first line naming its interpreter, works when you call it through the shell and fails silently when cron calls it directly. Cron is not your shell and will not guess.
  • Piling every job onto midnight. Midnight, the top of the hour, and Monday 9:00 AM are where everyone’s jobs already live. Stagger heavy work by a few minutes, 2:30 and 3:15 instead of 2:00 and 3:00, and the machine stops stamping on itself.
  • Letting the log grow forever. The redirect that saves you from silent failures creates a file that never stops growing. Pair every logged job with rotation or a cleanup line, or the disk fills up months later and takes the job down with it.
  • Scheduling a job that depends on a network mount or an external service being up. Cron fires on time whether or not the drive mounted or the remote service answered. Robust jobs check their preconditions first and log a clear message when one is missing, instead of writing a backup into the wrong place.
  • Editing a crontab file directly with a text editor. Hand edited files can carry invisible formatting errors, and some systems ignore files whose permissions look wrong. The 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.

Frequently asked questions

What does * * * * * mean in cron?

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.

Why didn’t my cron job run?

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.

How do I run a job every 90 minutes?

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 * * *.

Do I need to restart cron after editing the crontab?

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 vs systemd timers, which should I use?

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.

Related guides