Check Your Crontab Fields Before You Deploy
Paste a cron expression into the tool above and it breaks the line into its five fields immediately: minute (0 to 59), hour (0 to 23), day-of-month (1 to 31), month (1 to 12), and day-of-week (0 to 7). Checking each field against its valid range and operator support is the fastest way to catch a mistake before a job silently never fires, because a single out-of-range value or misplaced operator stays invisible until you go looking for it.1
Each field independently supports four operator types: a literal value, a wildcard (*), a range using a hyphen (1-5), a comma-separated list (1,3,5), and a step using a slash (*/15 or 3-59/4). Fields are evaluated independently, and a match requires all five to pass at once. Before you trust a schedule to a live server, confirm what happens when you combine a specific day-of-month with a specific day-of-week: that pairing produces a union, not an intersection.2
Run this check yourself in the Cron Expression Parser & Next-Run Preview.
Open in the tool →Field ranges and operators
The minute field (position 1) accepts 0 to 59. The hour field (position 2) accepts 0 to 23. Day-of-month (position 3) uses 1 to 31, and months shorter than 31 days skip the nonexistent dates automatically when the daemon evaluates the expression. For position 4, the month field accepts 1 to 12 or the abbreviated names JAN to DEC. Day-of-week (position 5) uses 0 to 7, where both 0 and 7 mean Sunday; named shortcuts MON to SUN also work on many implementations.
Verifying field ranges before deployment
Before you deploy a new expression, confirm each field's valid range on your target platform, because a value that parses correctly on Linux crontab may be out of range on AWS EventBridge or another scheduler with different numbering. Checking the ranges before the job goes live prevents the silent failure mode where the daemon accepts the line but never finds a matching time, leaving you waiting for a run that will never happen.
Remember that the five fields are independent and must all match the current time at once, which is why a single field set too narrowly can suppress the entire job. The day-of-month and day-of-week pair is the usual trap, because those two fields use OR rather than AND and a value in each widens the set of firing days instead of narrowing it. Validate the full expression, not just the one field you changed, before you trust the schedule.
Practical application of operators
Steps are the most compact way to express recurring intervals. Inside the minute field, */15 fires at 0, 15, 30, and 45; that is four times per hour. Building on this, 5-55/10 fires at minutes 5, 15, 25, 35, 45, and 55.3 The range 0 9-17 * * 1-5 captures every hour from 9 AM to 5 PM on weekdays, which suits business-hours polling. Lists allow non-uniform schedules: 0 0,12 * * * runs at midnight and noon.
Selecting the narrowest operator
Choose the operator that matches the business rule, not the shortest expression. If a job should run every 15 minutes, use a step. If it should run only at quarter boundaries, use a list. If it should run during a continuous block, use a range. This keeps the expression readable when someone else audits it later. A range like 9-17 in the hour field immediately communicates a business-hours window, while a list of every individual hour is harder to scan and easier to misread during a code review.
Common mistakes and edge cases
When both day-of-month and day-of-week contain non-wildcard values, cron fires if either condition is true, not both simultaneously. This surprises most users: 0 0 1 * 1 fires on the first of every month AND every Monday, not only on Mondays that fall on the first. Furthermore, some implementations silently skip month and day-of-month values outside the valid range, while others raise a parse error. Always validate with a parser before deploying. Another common mistake is writing 0 0 31 * 2 expecting it to run on the last day of February; this expression fires only on the 31st of any month that has a 31st day, and it never fires in February because that month has at most 29 days.
Understanding field independence and combined evaluation
When you combine multiple non-wildcard fields in one expression, the cron daemon evaluates all five simultaneously and fires the job only when every field matches the current time. A job set to 0 9 1 3 1 (minute 0, hour 9, 1st of March, Monday) fires only at 9 AM on Mondays that fall on March 1st; this combination occurs very rarely in practice.
The day-of-month and day-of-week OR exception
For any expression where both day-of-month and day-of-week contain non-wildcard values, cron abandons the AND requirement and applies OR instead: the job fires if day-of-month matches OR day-of-week matches. This is the most misunderstood behavior in cron. To enforce AND logic, leave one of the two fields as a wildcard and perform the date intersection check inside the script itself.
Confirming field ranges on your target platform
On most standard cron implementations, field ranges and operators behave consistently. Yet some platform extensions shift the valid range: AWS EventBridge uses 1 to 7 for day-of-week (Sunday = 1), not 0 to 7 as in standard Unix cron.4 Quartz Scheduler adds a seconds field at position 0, shifting all other fields one position to the right.5
Before deploying an expression to any platform other than standard Linux crontab, verify the field positions and valid ranges in the platform documentation. A single position shift or a different Sunday value turns a correctly written expression into one that fires on the wrong day or not at all. CapyToolkit's parser operates on standard 5-field Unix syntax and flags out-of-range values for that format.
When to use this
Use this guide when you need to understand what a cron expression does before deploying it, when you're writing a new schedule from scratch, or when an existing job is firing at unexpected times and you need to audit each field. Once you have the field ranges straight, pasting your own expression in lets you preview every day your cron expression matches, right there in the next several trigger times, before you trust the schedule to a live server.
Examples
Run every weekday at 9 AM
0 9 * * *
0 9 * * 1-5
Adding 1-5 in the day-of-week field restricts the run to Monday through Friday.
Run on the 15th and 30th of each month at midnight
0 0 15 * *
0 0 15,30 * *
A comma-separated list in day-of-month fires on both dates.
- 1.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 2.
The Open Group, "crontab — schedule periodic background work," opengroup.org, 2018. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
- 3.
Paul Vixie, "crontab.5," vixie/cron, GitHub, accessed June 2026. https://github.com/vixie/cron/blob/master/crontab.5
- 4.
Amazon Web Services, "Schedule types in EventBridge Scheduler," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html
- 5.
Quartz Scheduler, "CronExpression," quartz-scheduler.org, 2024. https://www.quartz-scheduler.org/api/2.5.x/org/quartz/CronExpression.html
Build Cron Lists and Ranges With the Parser Above
Paste a comma or hyphen into a cron field in the parser above and it shows you exactly which values match, before you commit the expression to a live schedule. The comma (,) creates discrete values that fire independently, while the hyphen (-) creates a continuous range between two endpoints, inclusive.1 Both operators work in every field and combine with each other and with the step operator, giving you fine-grained control over when a job runs without writing multiple crontab lines.
Choosing between them comes down to whether your target values are contiguous. Ranges suit contiguous spans: weekdays (1-5), business hours (9-17), or the first half of the year (1-6). Lists suit non-contiguous values: specific hours (0,6,12,18), alternate months (1,4,7,10), or arbitrary days (1,15). Mixing them in the same field is valid too, and running 1,15-20,25 through the parser confirms it fires on the 1st, 15th through 20th, and 25th exactly as expected.
What to look for
- 1,5-10,15 minutes 1, 5, 6, 7, 8, 9, 10, 15
- 1,3,5 (days) Monday, Wednesday, Friday
Opens the Cron Expression Parser & Next-Run Preview with this section's reference values shown at the top of the tool.
Open in the tool →List operator (,): discrete values
A comma-separated list in any field fires at each listed value independently. 0,30 * * * * fires at minute 0 and minute 30 of every hour, twice per hour. 0 0 * * 1,3,5 fires at midnight on Monday, Wednesday, and Friday. Lists can contain as many values as needed, in any order. The list MON,WED,FRI is equivalent to 1,3,5 in the day-of-week field on systems that support named days.2 Choosing a list over a range is the right call whenever the target values are not contiguous, because a range cannot skip intermediate values.
Keeping lists readable
Use one value per semantic target when a list grows long. A list such as 1,4,7,10 immediately reads as quarterly months, while a longer comma chain is easier to audit if the values are ordered and grouped by purpose. When a list exceeds five or six values, consider whether a range or step could express the same set more compactly; the goal is to make the schedule scannable at a glance, not to enumerate every trigger explicitly.
Order inside a list is cosmetic, so write values ascending even though cron treats 0,30 and 30,0 identically, because a sorted list is far easier to scan during a review. The same scannability argument favors a named day list such as MON,WED,FRI over a bare numeric one when the platform supports it, since the intent reads directly from the field. Reserve a step only when the values truly are uniform, because a step that happens to match today can drift from your intent the moment the start point changes.
Range operator (-): continuous spans
A hyphen between two values creates an inclusive range. 1-5 in the day-of-week field covers Monday through Friday. 9-17 in the hour field covers 9 AM through 5 PM. Building on this, 0 9-17 * * 1-5 fires at minute 0 of every hour from 9 AM to 5 PM on weekdays. Ranges require the start value to be less than or equal to the end value: 5-1 produces a parse error on most implementations.1
The inclusive nature of ranges is easy to forget when you think of the endpoints as boundaries that exclude the next value. In practice, both endpoints are valid trigger points: 9-17 fires at both 9 AM and 5 PM, not just at the hours between them. When you need to exclude the upper boundary for readability or to avoid overlap with another range, use a list instead of a hyphen.
Combining list, range, and step operators
All three operators can appear in the same field. A field like 1,10-20,30 first creates a discrete value of 1, adds the continuous range 10 through 20, and appends the discrete value 30, giving the cron daemon a single unified set of 13 valid matches for that field position. This composability is what makes cron expressions compact: a single field can encode a complex trigger set that would otherwise require multiple separate job lines.
Auditing list and range combinations
0,30 9-17 * * 1-5 fires at minutes 0 and 30 of every hour between 9 AM and 5 PM on weekdays. 1,15-20,25 in the day-of-month field fires on the 1st, the 15th through 20th, and the 25th. You can also combine a range with a step: 1-5,10-15 fires on the values 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, and 15, a concatenated multi-range list. When auditing these combinations, expand each operator into its literal value set and count the total matches to confirm the frequency aligns with your intent.
When to use a list instead of a range
When the values you need to target are non-contiguous, a list is clearer than approximating the pattern with ranges and exclusions. A job that should fire on Tuesday and Friday only needs 2,5 in the day-of-week field. For hours 0, 6, 12, and 18, the list 0,6,12,18 is immediately readable; the step */6 achieves the same result but requires the reader to know it starts at 0.
Non-uniform quarterly month distributions
For quarterly jobs targeting specific months, use a list to name each month explicitly: 1,4,7,10 targets January, April, July, and October. The step */3 starting from month 1 happens to produce the same set in this case, but for a distribution starting in February (2,5,8,11), the step */3 starting from 1 gives 1, 4, 7, 10, the wrong months.3 Write 2,5,8,11 explicitly to guarantee the correct targets.
List and range portability across platforms
In all standard cron implementations (Linux crontab, GitHub Actions, Kubernetes CronJob, Cloudflare Workers, AWS EventBridge), both the list and range operators work in all five fields. An expression using 1-5 or 1,3,5 in the day-of-week field is portable across these platforms without modification.4 This portability makes lists and ranges the safest choice when you need a schedule to behave identically across development, staging, and production environments.
One behavioral difference to know: most implementations require the start of a range to be less than or equal to the end.5 If you write 5-1 expecting a wrap-around (Saturday through Monday), most parsers produce an error rather than interpreting it as wrapping. To express a schedule that covers Saturday through Monday, use a list: 0,1,6 in the day-of-week field covers Sunday, Monday, and Saturday without relying on wrap-around behavior, and you can turn a cron day-of-week list into real dates to confirm a workaround actually covers the days you intended.
When to use this
Reach for the list operator in the parser above when a job needs to fire at multiple specific, non-contiguous times, and the range operator for contiguous spans like weekdays or business hours. Combine both and check the result whenever the schedule needs a subset of values a single range or step cannot express.
Examples
Run twice a day at 6 AM and 6 PM
A comma-separated list in the hour field fires at both values.
Run on weekdays AND the 1st and 15th
List in day-of-month combined with range in day-of-week. Note: cron uses OR, so this fires on all weekdays AND on the 1st and 15th regardless of weekday.
- 1.
The Open Group, "crontab — schedule periodic background work," opengroup.org, 2018. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
- 2.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 3.
Rob Pike, "cron package — github.com/robfig/cron/v3," pkg.go.dev, accessed June 2026. https://pkg.go.dev/github.com/robfig/cron/v3
- 4.
GitHub, "Events that trigger workflows," docs.github.com, accessed June 2026. https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
- 5.
Rob Ford, "robfig/cron: Range parsing and validation," github.com, accessed June 2026. https://github.com/robfig/cron/blob/v3/parser.go
Yes. 1,5-10,15 in the minute field fires at minutes 1, 5, 6, 7, 8, 9, 10, and 15. The list and range operators can both appear in the same field and are evaluated as a union.
1,3,5 fires on Monday, Wednesday, and Friday only. 1-5 fires on Monday through Friday, covering all five weekdays. Use a list for non-contiguous days and a range for a continuous span.
Yes, on most standard cron implementations. MON,WED,FRI is equivalent to 1,3,5. Case sensitivity varies; some systems require uppercase (MON), others accept lowercase. Named days in ranges are also valid: MON-FRI is the same as 1-5.
Functionally, no. 0,30 and 30,0 are equivalent in the minute field. By convention, values are listed in ascending order for readability, but cron does not require a specific order.
Yes, but only for contiguous spans. 1-6 covers January through June. For non-contiguous months (like quarter-end months), use a list: 3,6,9,12. Alternatively, a range-qualified step: 3-12/3 fires on months 3, 6, 9, and 12. CapyToolkit's parser helps you confirm the expanded month set.
Cron Step Expressions
A slash in a cron field means repeat at regular intervals. The operator divides a range into predictable points: */5 in the minute field fires at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, and 55. Without steps, expressing a 5-minute interval would require listing all 12 values: 0,5,10,15,20,25,30,35,40,45,50,55. Steps make complex schedules readable and less error-prone.1
The step operator combines with either a wildcard or an explicit range. */N divides the field's entire valid range by N, starting at zero. start-end/N divides only the sub-range from start to end by N, which lets you build intervals that skip early-morning or late-night hours. Understanding which starting value the step uses is critical: many users assume */15 starts at minute 1, but it starts at minute 0 because * expands to the range 0 to 59.
What to look for
- 10-50/10 fires at 10, 20, 30, 40, 50
- */5 equivalent to 0,5,10,...,55
Opens the Cron Expression Parser & Next-Run Preview with this section's reference values shown at the top of the tool.
Open in the tool →How the step operator works
For any field, */N is shorthand for 0-max/N, where max is the field's maximum valid value. In the minute field, */15 expands to 0-59/15, which fires at 0, 15, 30, and 45. In the hour field, */6 expands to 0-23/6, which fires at 0, 6, 12, and 18. The step always starts at the range's minimum value, not at the first match greater than zero.1 Understanding this start-from-zero behavior is essential when you need triggers offset from the field minimum, because a plain step cannot express that offset on its own.
Confirming step coverage
When combining steps with specific values in other fields, verify the combined schedule with a parser to confirm the expected behavior. A step in the minute field paired with a narrow hour range can produce fewer daily triggers than you assume, especially when the step does not evenly divide the range. Always read the next 10 or 20 trigger times rather than eyeballing the expression, because the interaction between fields is easier to spot in a concrete list of timestamps than in abstract field values.
Also watch for steps that do not divide the field cleanly, because a range such as 0 to 23 with a step of 8 lands on 0, 8, and 16 and then stops short of the top instead of wrapping around. The same expression can therefore fire three times on one platform and feel wrong on another simply because the endpoints are not what you pictured. Validating against the next trigger list makes the actual stop point obvious before the job goes live.
Range-qualified steps
Combining a range with a step restricts when the interval fires. 10-50/10 in the minute field fires at 10, 20, 30, 40, and 50 (never at 0, because the range starts at 10). Building on this, 0 9-17/2 * * * fires at hours 9, 11, 13, 15, and 17 on every day; this pattern suits business-hours polling jobs. The range endpoint is inclusive, so 1-59/2 fires at odd minutes: 1, 3, 5, ..., 59.
Reading the range before the slash
The range before the slash defines the candidate values; the slash then selects every Nth value from that candidate set. This order matters when the range does not start at the field minimum. If the selected points do not match the schedule you intended, change the range before changing the step. For example, 5-55/10 in the minute field fires at 5, 15, 25, 35, 45, and 55, which is useful when you need triggers offset from the hour boundary rather than aligned to it.
Common mistakes with steps
Using */0 as a step is undefined and either causes a parse error or fires continuously depending on the implementation; treat zero as an invalid step in all contexts. A step larger than the field's range (like */100 in the minute field) fires only once, at the start of the range. Consequently, */60 in the minute field fires at minute 0 only.2 When combining steps with specific values in other fields, verify the combined schedule with a parser to confirm the expected behavior.
The most common mistake is treating a step as a "every N units" operator without considering the field's maximum value. In the hour field, */8 fires at 0, 8, and 16, which is three times per day, not every 8 hours. Similarly, */9 in the minute field fires at 0, 9, 18, 27, 36, 45, and 54, which is seven times per hour, not once every 9 minutes. Reading the trigger list reveals these patterns instantly, which is why a parser is the safest way to validate a step expression before deploying.
Choosing between a step and an explicit list
When the values you want to trigger on are evenly spaced and start from the field minimum, a step is the right tool. */10 in the minute field fires at 0, 10, 20, 30, 40, and 50, which is exactly what you need for a 10-minute polling interval. When the values are not evenly spaced, or when the interval must not start at zero, a list is clearer and less error-prone.
When a list beats a range-qualified step
If your job must fire at minutes 5, 15, 25, 35, 45, and 55 (aligned to 5 past each hour rather than to the hour), you need 5,15,25,35,45,55 or the range-qualified step 5-55/10, because */10 starts at minute 0, not minute 5. For non-standard offsets, always verify the range-qualified step produces the expected trigger set with a parser before committing it to the crontab.
Step expressions in cloud and platform contexts
On GitHub Actions, Cloudflare Workers, and Kubernetes CronJob, step expressions work identically to standard Unix cron. */5 * * * * fires every 5 minutes on all three platforms, subject to each platform's minimum interval (5 minutes on GitHub Actions,3 1 minute on Kubernetes4 and Cloudflare Workers Paid plan).
For AWS EventBridge cron() expressions, the step operator is available in all fields including the year field.5 A step in the year field such as 2024-2030/2 fires only in the listed even years, so it pays to verify a step expression before deploying to AWS; year-field steps are an EventBridge-only feature and produce an error if copied into a standard Unix crontab.
When to use this
Use step expressions when you need a job to repeat at a fixed interval within an hour, day, or week: polling every 5 minutes, refreshing a cache every 15 minutes, or running a health check every 2 hours. If the interval must not start at minute 0 or hour 0, use a range-qualified step, and since step expressions are easy to get subtly wrong, paste yours into a parser and check that the expanded trigger list matches the interval you actually meant.
Examples
Run every 15 minutes
*/15 fires at 0, 15, 30, and 45. All other fields are wildcards.
Run every 2 hours during business hours
The range 9-17 combined with /2 fires at hours 9, 11, 13, 15, and 17.
- 1.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 2.
Kelektiv, "node-cron Issue #742," GitHub, accessed June 2026. https://github.com/kelektiv/node-cron/issues/742
- 3.
GitHub, "Events that trigger workflows," docs.github.com, accessed June 2026. https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
- 4.
Kubernetes, "CronJob - Kubernetes Official Docs," kubernetes.io, accessed June 2026. https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
- 5.
Amazon Web Services, "Scheduled Rule Patterns," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html
In any field, */5 means "every 5 units, starting from the field's minimum." In the minute field, it fires at 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, and 55. In the hour field, it fires at 0, 5, 10, 15, and 20.
Yes. The pattern start-end/step fires at every step value within the range. 10-50/10 in the minute field fires at 10, 20, 30, 40, and 50. The range limits where the step applies, while the step controls the interval within that range.
One. */1 is valid (though redundant with *) and means "every value." The step must be a positive integer; zero and negative values are invalid.
*/5 is equivalent to 0,5,10,15,20,25,30,35,40,45,50,55 in the minute field. The step operator is more readable and less error-prone for uniform intervals. Use a list when the values are not uniformly spaced or when you need specific non-zero starting points.
*/2 expands to 0-23/2, which starts at hour 0. The sequence is 0, 2, 4, 6, ..., 22. Hour 1 is odd and outside this sequence. To start at hour 1, use 1-23/2: this fires at 1, 3, 5, 7, ..., 23. CapyToolkit's parser shows the expanded trigger pattern so you can confirm the offset.
Crontab Special Strings
For common schedules, @-prefixed shortcuts can make a crontab easier to scan. Rather than decoding 0 0 * * 0, you write @weekly and the daemon maps it to the 5-field equivalent internally. Seven shortcuts cover the most common scheduling intervals.1
Support for @-shortcuts varies by implementation. Most modern Linux cron implementations (vixie-cron, cronie, systemd cron compatibility layer) support all seven. Some minimal implementations like busybox cron do not support any.2 When portability matters, across containers, embedded systems, or CI environments, use the equivalent 5-field expression, which is universally supported.
Opens the Cron Expression Parser & Next-Run Preview with the value from this section already filled in.
Open in the tool →All seven shortcuts and their equivalents
@reboot has no 5-field equivalent and runs once when the cron daemon starts. @yearly and @annually are interchangeable aliases for 0 0 1 1 *: midnight on January 1st. @monthly maps to 0 0 1 * *, midnight on the first of each month. @weekly maps to 0 0 * * 0, midnight every Sunday. @daily and @midnight are aliases for 0 0 * * *. @hourly maps to 0 * * * *, at minute 0 of every hour.1 Memorizing these seven mappings is straightforward once you use them regularly, because the names map directly to common English scheduling terms.
When shortcuts are safe to use
Shortcuts are safe when the cron implementation is known and stable, such as a Linux server running vixie-cron or cronie where the daemon version rarely changes. In that controlled environment, @daily is immediately understood by anyone reading the crontab, and there is no risk of the expression being copied to a platform that lacks shortcut support. Before copying a shortcut into another platform, map it to its 5-field equivalent: @hourly becomes 0 * * * *, @daily becomes 0 0 * * *, and you can translate @weekly into its 5-field form before you commit to a platform-specific format.
The same caution applies to systemd, whose daily and weekly presets look like the cron shortcuts but follow a different calendar syntax and cannot be pasted into a crontab unchanged. When you are unsure which daemon will run the file, the 5-field form is the lowest-risk choice because every implementation understands it and none of them reinterpret it. Treat a shortcut as a readability aid for a known host, not as a portable unit of scheduling logic.
Practical use cases
@reboot is used for startup initialization: starting background services, mounting shares, or sending a boot-time notification. @daily is the most common production shortcut, appearing in backup scripts, log rotation, and cache-clearing jobs. Building on this, @weekly covers full-system maintenance like disk space checks or package update reports. @monthly fits billing scripts and monthly report generation. @hourly suits polling jobs and queue flushes where a one-minute interval is too aggressive and a day is too coarse.
The key consideration when picking a shortcut is whether the crontab will ever move off its current platform. A @daily entry that has run on the same Linux server for years is perfectly safe to leave as-is. The risk appears when someone copies that crontab into a GitHub Actions workflow file or a Kubernetes CronJob manifest without translating the shortcut first, because those platforms reject @-shortcuts and the deployment either fails silently or never schedules the job.
Portability and when not to use shortcuts
@-shortcuts are supported by vixie-cron, cronie, and most Linux cron implementations.3 Yet AWS EventBridge, Kubernetes CronJob, and GitHub Actions do not support them.4 Cloudflare Workers requires 5-field cron expressions in wrangler.toml. systemd OnCalendar uses different named presets (daily, weekly) that look similar but use different syntax. Consequently, when writing cron expressions for portability across platforms, the 5-field equivalent is safer. Choosing the 5-field form from the start is the simplest way to avoid a failed deployment caused by a platform that silently ignores @-shortcuts.
Mapping shortcuts to standard cron
Every @-shortcut has a deterministic 5-field equivalent, and memorizing the seven mappings takes less time than you might expect. @reboot is the only exception with no 5-field form; all others translate directly: @hourly is 0 * * * *, @daily is 0 0 * * *, @weekly is 0 0 * * 0, @monthly is 0 0 1 * *, and @yearly is 0 0 1 1 *. Keeping this mapping in a team wiki or code comment ensures that anyone porting a crontab to GitHub Actions, Kubernetes, or Cloudflare Workers can do the substitution without guessing.
Choosing between a shortcut and its 5-field equivalent
Choosing @daily over 0 0 * * * is a readability decision for contexts where portability is not a concern. For crontabs that run only on Linux servers with vixie-cron or cronie, @daily is unambiguous and self-documenting. A new team member reading the crontab does not need to decode the 5-field equivalent to understand the intent. Yet for crontabs that may be copied to a CI pipeline, a Kubernetes manifest, or a container image with busybox cron, the 5-field form is safer because support for @-shortcuts is not guaranteed.
For documentation and code review, add a comment above each crontab line with the plain-English description regardless of which form you use. A line reading # Run daily at midnight UTC above 0 0 * * * costs nothing and makes the schedule auditable without requiring the reader to memorize shortcut definitions.
@reboot and system service dependencies
@reboot jobs start when the cron daemon initializes, which happens before your application services are fully ready on most systems. A @reboot entry that starts a scraper depending on a database fails intermittently if the database has not finished its own startup sequence. This race condition is one of the most common reasons @reboot jobs fail in production, because the job starts successfully on the first attempt but exits when a required service is not yet accepting connections.
Using sleep or systemd ordering for @reboot jobs
The standard workaround is a prefixed sleep: @reboot sleep 30 && /usr/local/bin/myjob. On systemd systems, the stronger solution is a dedicated .service unit with After=postgresql.service and Requires=postgresql.service in the [Unit] section.5 This guarantees your job starts only after the dependency is ready, relying on a timed sleep alone that can fail on slower systems or during heavy reboot sequences, so reserve @reboot for jobs with no service dependencies.
When to use this
Use @-shortcuts in Linux crontabs for common recurring schedules (daily, weekly, monthly) where readability is more important than portability. Use @reboot for startup tasks. Use the 5-field equivalent when targeting platforms that may not support shortcuts, or when the schedule must be auditable without knowing the shortcut definitions. If you are not sure which 5-field expression a shortcut like @weekly or @monthly maps to, a parser shows both forms side by side so you can check them before deploying.
Examples
Daily cleanup job
0 0 * * * /usr/bin/cleanup.sh
@daily /usr/bin/cleanup.sh
@daily is identical to 0 0 * * * and is clearer in intent.
Start a background service on boot
@reboot runs once when the cron daemon starts. Use absolute paths.
- 1.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 2.
BusyBox, "crond.c," GitHub, accessed June 2026. https://github.com/mirror/busybox/blob/371fe9f7/miscutils/crond.c
- 3.
GitHub, "Events that trigger workflows," docs.github.com, accessed June 2026. https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows
- 4.
Cronie, "crontab(5) — cronie man page," github.com, accessed June 2026. https://github.com/cronie-crond/cronie/blob/master/man/crontab.5
- 5.
Lennart Poettering, "systemd.unit(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/systemd.unit.5.html
@daily is an alias for 0 0 * * *, which runs the command at midnight (00:00) every day. It is also available as @midnight, and the two are identical.
@reboot runs when the cron daemon starts, which is early in the boot sequence. Depending on the init system, some services may not be available yet. Adding sleep 10 or sleep 30 before the command is a common workaround for commands that depend on network or database services.
The GitHub Actions documentation recommends standard 5-field cron expressions for on.schedule. While some runner environments may accept shortcuts, this is not guaranteed. Use 0 0 * * * instead of @daily for reliable scheduled workflows. CapyToolkit's parser follows the standard 5-field model, so it is useful for checking the translated expression.
@weekly maps to 0 0 * * 0, which runs at midnight every Sunday. A step expression like 0 0 */7 * * runs every 7 days starting from the 1st of the month, not every Sunday. The two are not equivalent unless you are specifically scheduling Sunday-midnight runs.
@annually is an alias for @yearly, which maps to 0 0 1 1 *: midnight on January 1st. It runs once per calendar year. Use @yearly for clarity; the two are interchangeable.