A cron schedule is a string of five fields that tells a scheduler exactly when to run a task. Each field controls a specific unit of time—minute, hour, day of month, month, and day of week. The job runs when the current time matches the criteria defined in these fields, with the specific rule that if both day-of-month and day-of-week are restricted, the job runs if either matches rather than requiring both to match.
Understanding the Matching Logic
The standard cron expression consists of five space-separated fields. The order is fixed: minute, hour, day of month, month, and day of week. You cannot reorder them. Most systems accept either these five fields or a six-field format where the first field adds seconds precision, but the five-field format is the universal baseline for standard crontabs.
The matching logic has a specific nuance that often causes confusion. For the minute, hour, and month fields, the current time must match the value specified. However, the interaction between the day-of-month and day-of-week fields is different. If both fields are restricted (meaning neither is an asterisk *), the job runs if the current date matches the day-of-month OR the day-of-week. If only one is restricted, that restriction applies. If both are asterisks, any day qualifies. This "OR" logic applies only when both specific fields are set; otherwise, it behaves like a standard AND condition across all fields.
Breaking Down Each Cron Field
Each field accepts specific values and ranges. Understanding what each column controls prevents common scheduling mistakes.
The first field sets the minute. It accepts values from 0 to 59. A single number like 30 runs the job at half past the hour. A step value like */15 runs every 15 minutes.
The second field sets the hour. It accepts values from 0 to 23, using 24-hour clock notation. 9 means 9 AM. 17 means 5 PM.
The third field sets the day of month. It accepts values from 1 to 31. This refers to the calendar date within the month.
The fourth field sets the month. It accepts values from 1 to 12. January is 1, December is 12.
The fifth field sets the day of week. It accepts values from 0 to 7, where both 0 and 7 represent Sunday. Monday is 1, Saturday is 6.
When you want "every" value in a field, you can use an asterisk *. This means "any matching value." For example, * in the hour field means the hour condition is always satisfied.
Common Cron Expression Examples
Consider the expression */15 9-17 * * 1-5. This is a practical schedule for business-hours monitoring. Here is how each field contributes:
*/15 Every 15 minutes (0, 15, 30, 45)
9-17 Between hours 9 and 17 inclusive
* Any day of the month
* Any month
1-5 Monday through Friday
This expression fires every quarter-hour during business hours on weekdays. If the current time is Tuesday at 10:03 AM, the next fire times in your local timezone would be:
Tuesday 10:15 AM
Tuesday 10:30 AM
Tuesday 10:45 AM
Tuesday 11:00 AM
Tuesday 11:15 AM
Tuesday 11:30 AM
Tuesday 11:45 AM
Tuesday 12:00 PM
Tuesday 12:15 PM
Tuesday 12:30 PM
Notice that the minute field */15 produces four values per hour: 0, 15, 30, and 45. The hour field 9-17 creates a nine-hour window. The day-of-week field 1-5 excludes weekends. The result is a predictable, repeating pattern that avoids weekend noise.
Another common pattern is 0 * * * *, which runs exactly once per hour at the top of the hour. The 0 in the minute field locks the minute to zero. The * in the hour field means every hour qualifies. This is useful for hourly data aggregation or cache warming.
Handling Special Characters and Ranges
Cron supports several shorthand characters that simplify complex schedules. The asterisk * matches any value in its field. It is the equivalent of saying "always."
The comma , lists specific values. 1,15 in the minute field runs at minute 1 and minute 15. This is useful when you need irregular intervals that do not divide evenly into hours.
The hyphen - defines an inclusive range. 9-17 means hours 9, 10, 11, 12, 13, 14, 15, 16, and 17. Both endpoints are included. Ranges must be ascending; 17-9 is invalid.
The slash / creates a step value. */5 means every 5 units. In the minute field, this produces minutes 0, 5, 10, 15, and so on. You can combine a range with a step: 10-50/5 produces minutes 10, 15, 20, 25, 30, 35, 40, 45, and 50.
These characters can be combined. */10 9-12 * * * runs every ten minutes between 9 AM and noon, every day of the month and every month. The expression is concise but precise. Avoid over-complicating expressions; if a combination becomes hard to read, consider splitting it into multiple jobs.
Verifying Your Schedule with Next-Fire Times
The most reliable way to confirm a cron expression works as intended is to view its next firing times. Mental calculation of cron logic is error-prone, especially with ranges and steps interacting across fields.
Take the expression */15 9-17 * * 1-5 again. A human might assume it runs every 15 minutes from 9 AM to 5 PM. But does it include exactly 5 PM? Does it skip noon? The answer depends on how the scheduler evaluates the hour range boundary.
By computing the next ten actual timestamps, you verify edge cases. If today is Wednesday, you see continuous quarter-hour intervals from 9:00 AM through 5:00 PM. If today is Saturday, the expression produces no fires until Monday. If today is Sunday, the first fire is Monday at 9:00 AM. These behaviors emerge from the interaction of the hour range and weekday range.
CronExplain provides this verification directly. Paste any expression and see the plain-English explanation alongside the next ten firing times in your browser's local timezone. This eliminates guesswork about whether your schedule includes boundary hours or skips intended days. The visual builder lets you adjust each field via dropdowns while watching the resulting expression and fire times update in real time.
Best Practices for Readable Cron Jobs
Write expressions that communicate intent clearly. Prefer explicit values over clever shortcuts when the schedule is simple. 0 9 * * * is more readable than 0 */1 * * * for a daily 9 AM task, even though both work.
Comment your expressions in your configuration file. A short note like # Business hours check every quarter-hour helps future maintainers understand why certain fields were chosen. CronExplain supports saving named schedules with comments, which is useful for teams managing multiple cron jobs across repositories.
Test with realistic timezones. If your server runs in UTC but your team operates in Eastern time, verify that your hour ranges align with your actual working hours. A schedule of 0 9 * * * in UTC fires at 4 AM Eastern, which may not be the intended behavior. Pin your timezone explicitly in your scheduler configuration rather than relying on the system default.
Avoid overlapping schedules. If two jobs both run at the top of the hour, consider staggering them by five minutes to reduce contention. 0 * * * * and 5 * * * * create predictable, non-overlapping execution windows.
Keep expressions within your team's mental model. If everyone understands */15, use it. If your team struggles with step syntax, write out 0,15,30,45 instead. Clarity beats brevity when the cost of misunderstanding is a missed backup or a duplicate email.