Naar hoofdinhoud springen

Guides / fix

Cron expressions: the five fields, with examples

A cron expression is five space-separated fields that say when a job runs: minute, hour, day of month, month, day of week. Read left to right, 30 2 * * 1 means at minute 30 of hour 2 on any day of any month, if that day is a Monday.

The five fields

Every standard Unix cron line begins with the same five fields, followed by the command to run.

* * * * *  command to run
| | | | |
| | | | +-- day of week  (0-7, 0 and 7 both mean Sunday, or SUN-SAT)
| | | +---- month        (1-12, or JAN-DEC)
| | +------ day of month (1-31)
| +-------- hour         (0-23)
+---------- minute       (0-59)

A field holds one of four things: a star meaning every value, a single number, a list, or a range. Hours use a 24 hour clock with no AM or PM, so 6 PM is 18. Sunday is both 0 and 7 because different systems historically disagreed, and cron accepts either.

Some schedulers add fields. Quartz, used by many Java frameworks, puts a seconds field first and adds an optional year at the end, so a six or seven field expression copied from a Quartz reference will not work in a Linux crontab. Check which flavor you are writing for before copying an expression from anywhere.

The special characters

  • * matches every value in the field. * * * * * runs every minute.
  • , lists values. 0 8,13,20 * * * runs at 08:00, 13:00 and 20:00.
  • - gives a range. 0 9-17 * * * runs on the hour from 09:00 through 17:00.
  • / takes a step through a range. */15 * * * * runs at minute 0, 15, 30 and 45. You can combine it with a range: 0 9-17/2 * * * runs at 9, 11, 13, 15 and 17.
  • Names. MON through SUN and JAN through DEC work in the last two fields and read better than numbers. They cannot be used inside a step.

Four more characters appear constantly in tutorials and do not exist in standard cron. L for last day, W for nearest weekday, # for the nth weekday of the month, and ? in place of a star are Quartz extensions. An expression such as 0 0 L * * will be rejected by a Linux crontab. To run something on the last day of the month there, run it daily and let the command decide:

0 0 * * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh

Note the backslashes in that line. A percent sign has a special meaning in a crontab, which is the fifth mistake below.

Cron also accepts six shorthand strings in place of all five fields: @yearly (also written @annually), @monthly, @weekly, @daily (also @midnight), @hourly, and @reboot, which runs once when cron starts rather than on a clock. @reboot is not supported everywhere.

Common schedules

When you want it to runExpression
Every minute* * * * *
Every 5 minutes*/5 * * * *
Every 30 minutes0,30 * * * *
Every hour, on the hour0 * * * *
Every 2 hours0 */2 * * *
Every day at midnight0 0 * * *
Every day at 02:3030 2 * * *
Twice a day, 08:00 and 20:000 8,20 * * *
Weekdays at 09:000 9 * * MON-FRI
Every hour during business hours, weekdays0 9-17 * * MON-FRI
Every Monday at 07:000 7 * * MON
Every Sunday at 03:1515 3 * * SUN
First of the month at 06:000 6 1 * *
Quarterly, first of Jan, Apr, Jul, Oct0 0 1 1,4,7,10 *

Mistakes that put the job at the wrong time

  1. Filling in both day fields. This is the one that catches everyone. When day of month and day of week are both set to something other than a star, cron runs the job when either matches, not both. 0 0 13 * FRI runs on every 13th and on every Friday, not on Friday the 13th.
  2. A step that does not divide the range. */7 * * * * fires at minutes 0, 7, 14, 21, 28, 35, 42, 49 and 56, then restarts at 0. The gap across the hour boundary is four minutes, not seven. The same trap applies to */40 in the minute field and any step that is not a factor of 60.
  3. Putting the step in the wrong field. */2 * * * * is every two minutes. 0 */2 * * * is every two hours. Dropping the leading 0 turns an hourly job into a job that runs 30 times as often.
  4. Assuming the environment. Cron gives the job a minimal environment, usually little more than PATH=/usr/bin:/bin and SHELL=/bin/sh. A script that works in your shell fails in cron because it calls something in /usr/local/bin. Use absolute paths for the command and inside it.
  5. An unescaped percent sign. In a crontab, % is turned into a newline and everything after the first one is fed to the command as standard input. A date +%Y-%m-%d inside a crontab line has to be written date +\%Y-\%m-\%d.
  6. Ignoring the output. Anything the job prints is mailed to the crontab owner, and on a machine with no mail configured it is discarded. Send it somewhere you will read: 0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
  7. Letting runs overlap. Cron starts the next run on schedule whether or not the last one finished. Wrap long jobs in a lock: */5 * * * * flock -n /tmp/sync.lock /usr/local/bin/sync.sh
  8. Forgetting the clock changes. Cron uses the machine's local time. On the days clocks shift, the hour around 02:00 either does not exist or happens twice, so a job scheduled inside it can be skipped or run twice depending on the cron implementation. Set servers to UTC, or schedule outside that window.
  9. Typing crontab -r. It deletes the entire crontab with no confirmation, and it sits one key away from crontab -e. Keep the crontab in version control and install it from a file.

Check it from outside

An expression that parses is not the same as a job that ran. Verify both, on the machine and then from off it.

On the machine, confirm what is installed, what the clock says, and whether cron started the job:

crontab -l
timedatectl
grep CRON /var/log/syslog | tail -20
journalctl -u cron -u crond --since "2 hours ago"

The log line proves cron launched the command. It says nothing about whether the command succeeded, which is why the redirect to a log file in the mistakes above matters.

Then check the result the way a user would, from outside the server. Most scheduled jobs exist to change something observable: a feed that should carry today's date, a sitemap that should have been rewritten, an export that should have appeared. Request it and read the headers:

curl -sI https://example.com/exports/daily.json
curl -s https://example.com/health/last-run

The Last-Modified header tells you whether the file is fresh, and it does that without shell access. Running the same request from several locations with the HTTP check separates a job that did not run from a page that is cached at an edge in one region and stale only there. That distinction is invisible from a single curl on the server itself.

Knowing when a scheduled job stops running

A cron job that stops running produces nothing to notice. There is no error page and no failed request, so nothing raises an alert unless something is watching for the absence. What you monitor is the job's effect rather than the job: a status file it rewrites, a timestamp it publishes, an endpoint that reports its last successful run. A scheduled check against that endpoint turns a silent failure into a notification, and HostTracker sends those by email, SMS, voice call, Slack, Telegram and more across 13 monitor types. The mechanics of setting one up are in the HTTP check guide, the rest of the troubleshooting guides are in the fix cluster, and the full list of check types is on the features page.

Check it now

Run the free check against your own site - no account needed.

Features

Monitor this permanently

Get alerted the moment it breaks: HostTracker checks from 300+ locations and notifies you by email, SMS, Slack, Telegram and more.

HostTracker features