Cron Expression Cookbook: 25 Schedules People Actually Search For
Cron's five fields are simple in theory and a constant source of off-by-one mistakes in practice. Below are 25 real schedules — the ones people actually type into a search box, not a tour of every possible field combination — each with the exact expression, a one-line explanation, and a live link into Cron Master prefilled with that schedule so you can see its next 10 run times before you commit it to a crontab.
minute hour day-of-month month day-of-week. Minutes and hours use 0-indexed 24-hour values; day-of-week is 0-6 with Sunday as 0 (7 is also accepted as Sunday by most implementations, but 0 is the safe default to assume).
Quick-copy reference
| Schedule | Expression | Meaning |
|---|---|---|
| Every minute | * * * * * | Runs 1,440 times a day — almost always a debugging/testing schedule, not a production one |
| Every 5 minutes | */5 * * * * | :00, :05, :10 … aligned to the clock |
| Every 10 minutes | */10 * * * * | :00, :10, :20, :30, :40, :50 |
| Every 15 minutes | */15 * * * * | :00, :15, :30, :45 — four runs an hour |
| Every 30 minutes | */30 * * * * | :00 and :30 |
| Every hour | 0 * * * * | At minute 0 of every hour |
| Every 2 hours | 0 */2 * * * | Midnight, 2am, 4am … starts counting from 0, not from install time |
| Every 6 hours | 0 */6 * * * | Midnight, 6am, noon, 6pm |
| Every 6 hours, offset to start at 3am | 0 3,9,15,21 * * * | 3am, 9am, 3pm, 9pm — the step syntax can't express an offset, so list the hours explicitly |
| Every 12 hours | 0 */12 * * * | Midnight and noon |
| Daily at midnight | 0 0 * * * | The default "nightly job" schedule |
| Daily at 9am | 0 9 * * * | Every day, including weekends |
| Daily at noon | 0 12 * * * | 12:00, 24-hour format — not 12 12 * * *, which is 12:12pm |
| Twice daily, 9am and 9pm | 0 9,21 * * * | Comma-separated hour list, both in 24-hour format |
| Weekdays at 9am | 0 9 * * 1-5 | Monday through Friday only |
| Weekdays at 6pm | 0 18 * * 1-5 | End-of-business-day job, skips weekends |
| Every 15 minutes during business hours on weekdays | */15 9-17 * * 1-5 | 9am–5pm, Mon–Fri — the hour range and the step syntax combine cleanly |
| Every Monday at 9am | 0 9 * * 1 | Weekly job, single day |
| Every Friday at 5pm | 0 17 * * 5 | End-of-week job |
| Weekends at 10am | 0 10 * * 0,6 | Saturday and Sunday only |
| Weekly reset, Sunday midnight | 0 0 * * 0 | Common for weekly usage counters and report resets |
| First day of the month at midnight | 0 0 1 * * | Billing cycles, monthly reports |
| Twice a month, 1st and 15th | 0 0 1,15 * * | Semi-monthly payroll and reconciliation jobs |
| Last day of every month | not directly expressible | See the worked example below — standard cron has no "last day" token |
| Quarterly, 1st of Jan/Apr/Jul/Oct at midnight | 0 0 1 1,4,7,10 * | Fiscal-quarter jobs |
| Yearly, Jan 1st at midnight | 0 0 1 1 * | Annual license/certificate rotation reminders |
Worked examples
1. Every 6 hours, but not starting at midnight
The most common surprise with step syntax: */6 in the hour field doesn't mean "every 6 hours from whenever this job starts running." It means "every hour value that's a multiple of 6" — 0, 6, 12, 18. If you actually want the cycle offset (say, a job that should first run at 3am so it doesn't collide with a midnight backup), you have to abandon the step syntax and list the exact hours:
# Runs at 0, 6, 12, 18 — not what you want if 3am was the intent
0 */6 * * *
# Runs at 3, 9, 15, 21 — the actual offset schedule
0 3,9,15,21 * * *
See it live
Open the offset version in Cron Master and check the next 10 execution times — they land on 3am/9am/3pm/9pm, not the clock-aligned hours the step syntax alone would give you.
Open this example in Cron Master2. The last day of the month, without a native token
This is the single most-searched cron schedule that standard cron simply cannot express directly. Day-of-month is a fixed field with no concept of "however many days this particular month has" — there's no way to write one expression that means the 28th in February and the 31st in a month like January. Two portable workarounds, depending on what your scheduler supports:
- Run on the 1st of the next month instead, and process "yesterday." Schedule
0 0 1 * *and have the job compute the last day of the previous month as its target date. This is the most portable option — every cron implementation supports it. - Run on each candidate date and self-check. Schedule
0 0 28-31 * *— note this fires on every date in that range that the month actually has, so four times in a 31-day month, three in a 30-day month, and once (or twice in a leap year) in February — and have the job itself check "is tomorrow the 1st?" before doing real work, exiting immediately if not. The guard runs on each of those firings, so keep anything with side effects behind it, not before it.
If your specific scheduler is Quartz, Jenkins, or a cron fork that documents an L token in the day-of-month field, that solves it natively — but that's a scheduler-specific extension, not standard POSIX cron, so verify against your own tool's docs rather than assuming it's portable.
See it live
The "run on the 1st, process yesterday" version — check that it lands on the 1st of every month regardless of how long the prior month was.
Open this example in Cron Master3. Why "the first Monday of the month" isn't one field combination
A schedule like 0 9 1-7 * 1 looks like it should mean "9am on the first Monday of the month" — restrict the day-of-month to the first week, restrict the day-of-week to Monday. It doesn't work that way. When both the day-of-month and day-of-week fields are restricted (anything other than *), standard cron treats them as an OR, not an AND: this expression actually fires at 9am on the 1st through the 7th of every month and at 9am on every Monday, which in most months is six or seven runs, not one.
# Looks like "first Monday of the month at 9am" — it isn't.
# Fires on the 1st–7th (any weekday) AND every Monday (any date).
0 9 1-7 * 1
The portable fix is to run daily and check inside the job: schedule 0 9 1-7 * * (9am, every day 1st–7th) and have the job itself verify new Date().getDay() === 1 before doing real work, so it only actually executes on whichever of those seven days is the Monday.
Common gotchas, in one place
- Day-of-month and day-of-week combine with OR, not AND, whenever both are restricted. See worked example 3 above — this single misunderstanding accounts for a large share of "why did my cron job run on the wrong day" reports.
- Cron runs on the server's system timezone, not UTC and not the browser reading this page. Check with
dateon the actual machine, or set an explicitTZ=line in the crontab if your implementation supports it — don't assume. - Step syntax (
*/N) always counts from the field's zero point, never from whenever the job happens to start.*/6in the hour field is 0/6/12/18, full stop — see worked example 1 for the offset workaround. - There's no native "last day of month" token in standard cron. See worked example 2 for the two portable workarounds.
- A missing environment does not mean a broken expression. If a job's schedule fires correctly (confirm with Cron Master's execution list) but the job itself fails, the cron expression isn't the bug — check
PATHand other environment variables, since cron doesn't inherit your interactive shell's environment.
Frequently asked questions
What is the cron expression for every 15 minutes?
*/15 * * * * runs at minutes 0, 15, 30 and 45 of every hour — four times an hour, aligned to the clock. It is not "15 minutes from whenever the job was installed"; the step syntax always resets at the top of the hour.
How do I run a cron job on the last day of every month?
Standard cron (vixie-cron, the format used by crontab on Linux and most schedulers) has no "last day of month" token — months have 28 to 31 days and cron's day-of-month field can't express "whichever number that is this month." The portable fix is to schedule the job for the 1st of the following month at 00:00 and have it operate on the previous day, or run it daily and exit immediately unless tomorrow's date rolls into a new month. Some schedulers (Quartz, certain cron forks) support an L token for this directly — check your specific implementation before assuming it's unavailable.
Does cron run on UTC or my local time?
Neither, by default — cron runs on whatever timezone the server's system clock is set to, which is often UTC on cloud infrastructure but not guaranteed. It has nothing to do with the timezone of whoever is reading the crontab. Check with the date command on the actual machine running the job, or set an explicit TZ= line in the crontab (supported by most modern cron implementations) rather than assuming.
What happens if both the day-of-month and day-of-week fields are set?
Standard cron treats them as OR, not AND, whenever both are restricted (not *). A schedule like 0 9 1 * 1 doesn't mean "the 1st, if it's a Monday" — it fires on the 1st of every month AND every Monday, which is almost never what someone intended. If you need an actual AND (e.g. "the first Monday of the month"), you need scheduler-level logic or an application check inside the job, not a single cron field combination.
How do I schedule something every 6 hours starting at 3am instead of midnight?
0 */6 * * * runs at 00:00, 06:00, 12:00 and 18:00 — the step syntax always starts counting from 0, not from whenever you want the first run to land. To offset the whole cycle, list the exact hours instead: 0 3,9,15,21 * * * runs every 6 hours starting at 3am.
🧪 Paste any schedule and see exactly when it runs
Cron Master parses the expression entirely client-side and lists the next 10 execution times in any timezone — the fastest way to confirm a schedule does what you think before it's live in a crontab. The natural-language generator and AI logic breakdown send your prompt to Gemini; the parsing and execution list above them run locally and don't.
Open Cron Master →