Developer ToolsLinux / Crontab

Cron Expression Guide: Fields, Special Characters, and Real-World Examples

Everything you need to read, write, and debug cron expressions โ€” from the five fields and their ranges to step values, lists, the DOM/DOW OR-behavior, and timezone pitfalls.

What is cron?

Cron is a time-based job scheduler built into virtually every Unix and Linux system. Its name comes from the Greek word for time, chronos. The cron daemon (crond) runs continuously in the background, waking up every minute to check whether any scheduled job needs to fire. When the current time matches a job's schedule, cron executes the associated command.

The schedule for each job is defined by a cron expression โ€” a concise, five-field string that encodes exactly when the job should run. Cron expressions are used far beyond traditional Linux servers: GitHub Actions, GitLab CI, Kubernetes CronJobs, AWS EventBridge, Google Cloud Scheduler, Heroku Scheduler, and countless application frameworks all accept the same (or a closely compatible) syntax. Understanding cron expressions is a foundational skill for any developer who works with automation, CI/CD, or server-side scheduling.

If you want to build or decode a cron expression right now, the Cron Expression Builder on sourcecodestack lets you edit the five fields visually, paste an existing expression to get a plain-English description, and copy the result โ€” all in your browser with no sign-up required.

The five fields of a cron expression

A standard cron expression consists of exactly five fields separated by spaces:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ minute (0โ€“59)
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ hour (0โ€“23)
โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ day of month (1โ€“31)
โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€ month (1โ€“12)
โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€ day of week (0โ€“6, 0 = Sunday)
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚
* * * * *

Each field is independent: the scheduler fires the job only when the current time satisfies all five fields at once (with a notable exception for the day fields discussed later). Here is a detailed breakdown of each field.

Field 1 โ€” Minute (0โ€“59)

The minute field controls which minute(s) within the hour the job fires. Valid values are integers from 0 to 59. A value of 0 means "on the hour"; 30 means "at the half-hour mark". You can use wildcards, ranges, lists, and step values (covered below) to express more complex patterns like "every 15 minutes" or "at minutes 5, 10, and 20".

Field 2 โ€” Hour (0โ€“23)

The hour field uses a 24-hour clock. Valid values are 0 to 23, where 0 is midnight and 23 is 11 PM. A job meant to run at 9:00 AM uses 9 here; 2:30 PM is 14. The hour field pairs with the minute field to express an exact time of day.

Field 3 โ€” Day of Month (1โ€“31)

This field specifies which day(s) of the calendar month the job runs. Valid values are 1 to 31. Common uses include billing jobs that run on the 1st of every month (1), or a mid-month report on the 15th (15). Note that not all months have 31 days; cron simply skips months where the specified day does not exist โ€” so 31 never fires in April, June, September, or November.

Field 4 โ€” Month (1โ€“12)

The month field restricts the job to specific calendar months. Valid values are 1 to 12, where 1 is January and 12 is December. Many cron implementations also accept three-letter abbreviations like JAN, FEB, MAR, and so on, though the numeric form is the most portable. A job with * in this field runs every month; one with 6 runs only in June.

Field 5 โ€” Day of Week (0โ€“6)

The day-of-week field specifies which day(s) of the week the job fires. Valid values are 0 to 6, where 0 is Sunday and 6 is Saturday. Many implementations also treat 7 as Sunday for compatibility. Like the month field, most cron daemons accept three-letter abbreviations: SUN, MON, TUE, WED, THU, FRI, SAT. A job with 1 here runs every Monday; 1-5 means Monday through Friday.

Special characters

Each field accepts the following special characters in addition to plain integers.

Asterisk (*) โ€” "every"

The asterisk is the wildcard. It means "every valid value for this field". * in the minute field means every minute; in the month field it means every month. An expression of five asterisks โ€” * * * * * โ€” runs every minute of every hour of every day.

Comma (,) โ€” list of values

A comma separates multiple discrete values in a single field. 1,3,5 in the day-of-week field means "Monday, Wednesday, and Friday". 0,30 in the minute field means "at minute 0 and minute 30". There is no limit to the number of values in a list, and you can mix ranges and individual values: 1,3,5-7.

Hyphen (-) โ€” range of values

A hyphen defines an inclusive range. 8-17 in the hour field means "every hour from 8 AM to 5 PM inclusive". 1-5 in the day-of-week field means "Monday through Friday". Ranges can be combined with commas: 1-5,0 in the day-of-week field means "Monday through Friday plus Sunday".

Slash (/) โ€” step values

The slash defines a step: */n means "every n units". */5 in the minute field fires at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, and 55. */2 in the hour field fires at hours 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, and 22 โ€” every two hours. You can also apply a step to a range: 0-30/10 in the minute field fires at 0, 10, 20, and 30.

The DOM/DOW OR-behavior โ€” an important nuance

In standard Vixie cron (the most common implementation on Linux), when both the day-of-month field and the day-of-week field are set to a non-wildcard value, the job fires when either condition is true โ€” it is a logical OR, not AND. This behavior surprises many developers.

For example, 0 6 1 * 5 does not mean "at 6:00 AM on the first day of the month, but only if that day is a Friday". It means "at 6:00 AM on the first of every month OR at 6:00 AM on every Friday". If you need truly both conditions to match, you must add an explicit check inside the script itself.

Worked examples

The table below maps cron expressions to their plain-English meaning. These cover the most common real-world scheduling patterns.

Cron expressionPlain-English meaning
* * * * *Every minute, all day, every day
0 * * * *At the top of every hour (e.g., 00:00, 01:00, 02:00โ€ฆ)
0 0 * * *Once a day at midnight
0 6 * * *Once a day at 6:00 AM
0 12 * * *Once a day at noon
0 0 * * 0Every Sunday at midnight
0 0 * * 1Every Monday at midnight
0 0 * * 1-5Every weekday (Mondayโ€“Friday) at midnight
0 9 * * 1-5Every weekday at 9:00 AM โ€” a typical business-hours job
0 0 1 * *On the 1st of every month at midnight
0 0 15 * *On the 15th of every month at midnight
0 0 1 1 *Once a year, on 1 January at midnight
*/5 * * * *Every 5 minutes
*/10 * * * *Every 10 minutes
*/15 * * * *Every 15 minutes
*/30 * * * *Every 30 minutes
0 */2 * * *Every 2 hours, on the hour
0 */6 * * *Every 6 hours (00:00, 06:00, 12:00, 18:00)
0 8,12,17 * * 1-5At 8:00 AM, 12:00 PM, and 5:00 PM on weekdays
30 8 * * 1-5At 8:30 AM every weekday
0 0 1,15 * *On the 1st and 15th of every month at midnight
0 0 * 1,7 *Every day at midnight, but only in January and July
0 3 * * 7Every Sunday at 3:00 AM โ€” common for weekly maintenance
5 4 * * 0Every Sunday at 4:05 AM
0 0 * * 6,0Every Saturday and Sunday at midnight

Common schedules explained

Every minute: * * * * *

All five fields are wildcards, so the job fires at every tick of the cron clock โ€” once per minute, 1440 times per day. This is rarely used in production except for lightweight polling jobs or health-check pings where frequency matters more than resource cost. If your job takes longer than a minute to complete, it will overlap with the next invocation unless you add a lock mechanism.

Hourly: 0 * * * *

The minute field is 0 and everything else is a wildcard. The job fires at the top of every hour โ€” 00:00, 01:00, 02:00, and so on through 23:00. This is the most commonly used recurring schedule for tasks like log rotation, cache warm-up, and summary reports.

Daily: 0 0 * * *

The job fires once a day at midnight. The time can be adjusted by changing the hour field: 0 3 * * * fires at 3:00 AM instead, which is a popular choice for nightly batch jobs that need low system load. Bear in mind that "midnight" is relative to the system's timezone โ€” see the timezone section below.

Weekly: 0 0 * * 1

The day-of-week field is 1 (Monday), and all other day-related fields are wildcards. The job fires once a week, at midnight on Monday morning. Changing 1 to 0 or 7 shifts it to Sunday night. Use 1-5 instead of a single day to fire on all weekdays.

Monthly: 0 0 1 * *

Day-of-month is 1, so the job fires at midnight on the first of every month โ€” the canonical monthly billing or report-generation job. Change 1 to 15 for mid-month runs, or use 1,15 (a comma-separated list) to fire twice per month.

Every N minutes: */N * * * *

Step syntax makes "every N minutes" trivial to express. */5 * * * * fires at minutes 0, 5, 10 โ€ฆ 55 of every hour. */15 * * * * fires at 0, 15, 30, and 45. Remember that the step always starts from 0, not from the current minute: if the cron daemon starts at minute 3, the job still fires at 5, 10, 15 โ€” not at 3, 8, 13.

Weekdays only: 0 9 * * 1-5

The range 1-5 in the day-of-week field covers Monday through Friday. Combined with a specific minute and hour, this is the classic pattern for business-hours automation: sending a daily stand-up reminder, kicking off a morning data pipeline, or triggering a deployment window.

Crontab basics: where cron jobs live

On a Linux system, each user's scheduled jobs are stored in a crontab (cron table) file. You interact with it through the crontab command:

  • crontab -e โ€” open your crontab in the default editor to add, edit, or remove jobs.
  • crontab -l โ€” list your current crontab without editing.
  • crontab -r โ€” remove your entire crontab (use with care).
  • crontab -u username -l โ€” (as root) list another user's crontab.

Each non-blank, non-comment line in a crontab has the format:

minute hour dom month dow /path/to/command arg1 arg2

Lines beginning with # are comments. You can also define environment variables above the job lines, with the most important being SHELL (which shell runs the command) and MAILTO (where cron sends the output; set to an empty string to silence email output: MAILTO="").

System-wide cron jobs live in /etc/crontab and the directories /etc/cron.d/, /etc/cron.daily/, /etc/cron.weekly/, and /etc/cron.monthly/. The system-wide crontab format includes an extra field for the username under which the command runs:

minute hour dom month dow username /path/to/command

Special string shortcuts

Many cron implementations accept special strings as a replacement for the five-field expression:

StringEquivalent expressionMeaning
@reboot(none)Run once at system startup
@yearly / @annually0 0 1 1 *Once a year, 1 January
@monthly0 0 1 * *Once a month, on the 1st
@weekly0 0 * * 0Once a week, Sunday midnight
@daily / @midnight0 0 * * *Once a day at midnight
@hourly0 * * * *Once an hour, on the hour

Common mistakes and how to avoid them

1. Timezone confusion

Cron always uses the system's local timezone. On a server set to UTC this is unambiguous, but in mixed-timezone environments โ€” where developers are in one timezone and the server is in another โ€” expressions like 0 9 * * * (intended to mean "9 AM local time") will fire at the wrong clock time for anyone not in the server's timezone. Two mitigations:

  • Keep servers set to UTC and convert to UTC when writing expressions.
  • Some modern platforms (e.g., Google Cloud Scheduler) allow you to specify a timezone in the job configuration alongside the cron expression โ€” use that feature when available.

Daylight saving time is a related trap: when clocks spring forward, a job scheduled in the skipped hour (e.g., 0 2 * * * on the night clocks skip 2:00โ€“3:00) will not fire that day. When clocks fall back, it may fire twice. Scheduling critical jobs at times that don't overlap with DST transitions (e.g., 3:00 AM or 4:00 AM) reduces risk.

2. The day-of-month / day-of-week OR behavior

As noted in the special-characters section, setting both the DOM and DOW fields to non-wildcard values causes the job to run when either condition is met. The expression 0 12 13 * 5 fires at noon every Friday and at noon on the 13th of every month โ€” not only at noon on Friday the 13th. This is one of the most frequently cited cron gotchas. If you need the intersection (and), move the day-of-week check into the script:

0 12 13 * * [ "$(date +\%u)" = "5" ] && /path/to/script

3. Overlapping / long-running jobs

Cron does not prevent a new instance of a job from starting while a previous instance is still running. If a job scheduled every minute occasionally takes two minutes, you can end up with two overlapping processes competing for the same resources or corrupting shared state. Use a lock file or a tool like flock to serialize execution:

* * * * * flock -n /tmp/myjob.lock /path/to/script

4. Missing environment variables

Cron runs commands in a minimal environment โ€” it does not source your shell profile, so environment variables available in an interactive terminal session (like PATH, JAVA_HOME, or NVM_DIR) may not exist when the job runs. Always use full absolute paths to binaries in crontab commands, or explicitly set the required variables at the top of the crontab or within the script.

5. Cron output silently emailing you

By default, cron captures any output (stdout and stderr) from a job and attempts to email it to the user running the job. On most servers, the local mail system is not configured for delivery, so mail accumulates in a local spool and grows without being read. Set MAILTO="" at the top of your crontab to suppress this, and redirect important output explicitly to a log file:

0 3 * * * /path/to/script >> /var/log/myjob.log 2>&1

6. Off-by-one in day-of-week numbering

In the POSIX standard, 0 is Sunday and 6 is Saturday. Most implementations also accept 7 as Sunday. However, some platforms (notably Quartz Scheduler, used in many Java applications) use a 1โ€“7 range where 1 is Sunday and 7 is Saturday โ€” the opposite of the POSIX convention. Always verify the numbering convention for your platform before deploying. The Cron Expression Builder uses the standard POSIX 0โ€“6 convention.

7. Step values that don't divide evenly

A step value of */7 in the minute field fires at minutes 0, 7, 14, 21, 28, 35, 42, 49, and 56 โ€” then the next fire is at minute 0 of the next hour, not at minute 3 of the current hour. The step resets at the top of each field's range. If you actually want the job to fire exactly every 7 minutes across hour boundaries, you need a different approach (such as running the job every minute with an internal counter, or using a platform that supports fractional hours).

Building and testing expressions with the tool

Writing cron expressions by hand and predicting when they will fire takes practice. The Cron Expression Builder shortens the feedback loop: edit any of the five fields and see an immediate plain-English description of the schedule. Useful patterns for the tool:

  • Start from a preset (Every minute, Hourly, Daily midnight, Weekly Monday, Monthly 1st) and tweak the fields rather than building from scratch.
  • Paste any existing cron expression from a config file or documentation into the raw input and click Parse to decode it instantly.
  • Use the validation feedback to catch invalid field values before they cause a silent failure in production.
  • Copy the final expression directly to your clipboard to paste into a crontab, a YAML configuration, or a platform scheduler.

All processing happens locally in your browser. No expression, schedule, or data is sent to any server.

Cron expressions on modern platforms

The five-field cron syntax has spread well beyond traditional Unix crontabs. Here are the key variations to know:

  • GitHub Actions uses standard five-field cron under the schedule trigger. All times are interpreted as UTC. Note that jobs scheduled at high-traffic minutes (e.g., exactly on the hour) may experience delays due to load on GitHub's infrastructure.
  • Kubernetes CronJobs use standard five-field cron. The timezone field was added as a beta feature in Kubernetes 1.25, so you can now set spec.timeZone: "America/New_York" instead of converting to UTC manually.
  • AWS EventBridge (formerly CloudWatch Events) uses a six-field format with a year field appended, and uses a slightly different syntax for day-of-week (it uses ? as a "no value" placeholder when the other day field is specified, rather than the POSIX OR behavior).
  • Google Cloud Scheduler supports standard five-field cron with an explicit timezone configuration, eliminating the UTC conversion burden.
  • Heroku Scheduler only supports limited intervals (every 10 minutes, every hour, every day) rather than the full cron syntax.

This guide covers the standard POSIX five-field cron format as used by Linux crontab and most Unix-compatible schedulers. Always verify field ranges, day-of-week numbering, and timezone behavior against the documentation of the specific platform you are using.