A valid crontab expression consists of exactly five fields separated by spaces: minute, hour, day of month, month, and day of week. To check syntax, ensure each field follows its specific range rules and that separators like commas, hyphens, and slashes are used correctly without extra spaces between fields. If your scheduler supports seconds, a sixth field is added at the beginning.
Why Cron Syntax Errors Happen
Most cron errors stem from misunderstanding how fields interact or misplacing separators. A common mistake is adding spaces around commas or hyphens, which some parsers reject. Another frequent issue is confusing day-of-month with day-of-week logic, causing jobs to run less frequently than intended. Syntax errors also occur when using unsupported characters like semicolons or parentheses, which are not part of the standard cron specification.
The core problem is often ambiguity. Does 1-5 mean Monday through Friday? Does */5 mean every five minutes from the top of the hour, or every five minutes from when the daemon started? Understanding the strict grammar rules eliminates this ambiguity.
The Standard 5-Field Format Explained
The standard format is minute hour day-of-month month day-of-week. Each field accepts integers, ranges, lists, and steps.
| Field | Range | Allowed Values | Example |
|---|---|---|---|
| Minute | 0-59 | *, 0, 15, */5 | Every minute: * |
| Hour | 0-23 | *, 9, 1-5 | 9 AM: 9 |
| Day of Month | 1-31 | *, 1, L | First day: 1 |
| Month | 1-12 | *, 1, JAN | January: 1 |
| Day of Week | 0-7 | *, 1, MON | Monday: 1 |
Note that both day-of-month and day-of-week can constrain execution. If both are specified, the job runs when either condition is met in many implementations, though some require both. Always test this behavior on your specific server.
Handling Seconds and Nicknames
Some environments, like Quartz Scheduler or certain cloud platforms, support a six-field format where the first field represents seconds. The format becomes second minute hour day month weekday. This allows for precise timing like every 30 seconds (*/30 * * * * *).
Standard Unix cron does not support seconds. If you use a six-field expression on a standard Linux box, it may fail or ignore the extra field. Check your scheduler’s documentation.
Nicknames simplify common schedules. These are shorthand for specific expressions:
@hourly: Equivalent to0 * * * *@daily: Equivalent to0 0 * * *@weekly: Equivalent to0 0 * * 0@monthly: Equivalent to0 0 1 * *@yearly: Equivalent to0 0 1 1 *@reboot: Run once when the system boots.
Using nicknames reduces syntax errors because you do not need to remember the numeric equivalents. However, they offer less control. For example, @daily always runs at midnight, whereas 0 1 * * * runs at 1 AM.
Validating Your Expression Step-by-Step
To manually validate a cron expression, break it down field by field. Start with the minute field, then hour, and so on. Check each field against its allowed range. If a field is empty, it defaults to *.
Consider this example: */5 * * * *.
- **Minute (
*/5)**: The asterisk means every minute. The slash 5 means step every 5 units. This fires at minutes 0, 5, 10, 15, etc. - **Hour (
*)**: Fires every hour. - **Day of Month (
*)**: Fires every day of the month. - **Month (
*)**: Fires every month. - **Day of Week (
*)**: Fires every day of the week.
Combined, this runs every 5 minutes, regardless of hour, day, month, or weekday.
If you are unsure about the output, use a tool to visualize the schedule. CronExplain provides a plain-English explanation and previews the next ten firing times in your local timezone, which helps verify that */5 behaves as expected without waiting for the actual cron daemon to trigger.
Previewing Next Fire Times
Seeing the actual timestamps removes guesswork. For the expression */5 * * * *, the next fires might look like this:
2023-10-27 10:05:00
2023-10-27 10:10:00
2023-10-27 10:15:00
2023-10-27 10:20:00
Notice that it aligns with the clock’s minute boundaries (0, 5, 10...). It does not run 5 minutes after the previous job finishes; it runs at fixed intervals from the top of the hour.
For a more complex example: 0 9 * * 1-5.
- **Minute (
0)**: Exactly at minute 0. - **Hour (
9)**: Exactly at hour 9. - **Day of Month (
*)**: Any day of month. - **Month (
*)**: Any month. - **Day of Week (
1-5)**: Monday through Friday.
This runs at 9:00 AM every weekday. It does not run on Saturday or Sunday. If you want it to run every day regardless of weekend status, change the last field to *.
Common Mistakes and Fixes
Mistake: Extra spaces. Incorrect: */5 * * * * (trailing space) or */5 * * * *. Some strict parsers dislike trailing whitespace. Fix: Ensure the line ends immediately after the last asterisk. Trim whitespace if your editor adds it automatically.
Mistake: Confusing day-of-month and day-of-week. Expression: 0 0 1 * MON. This intends to run on the first Monday of every month. In many cron implementations, this runs on the first day of the month OR any Monday. It does not calculate "first Monday." Fix: Use logic in your script to check if today is the first Monday, or use a scheduler that supports advanced expressions like Quartz (0 0 ? * MON#1).
Mistake: Using commas incorrectly. Incorrect: 1, 2, 3 * * * *. Spaces after commas can break parsing. Fix: Use 1,2,3 * * * *. No spaces inside the list.
Mistake: Assuming UTC. Cron uses the server’s local timezone unless configured otherwise. If your server is in UTC and you are in EST, 0 9 * * * runs at 9 AM UTC, which is 4 AM EST. Fix: Check your server’s timezone settings. Use TZ=America/New_York in your crontab file if supported, or adjust the hour manually to match your desired local time relative to the server’s timezone.
By breaking down each field and verifying the output against expected timestamps, you ensure your schedule behaves correctly before deploying it to production.