Cron Expression Generator
Build a cron schedule visually - set each field, use a preset, and get the expression plus a plain-English description of when it runs.
- Free, no account
- No watermark
- No usage limit
About the Cron Expression Generator
A cron expression is five fields that tell a scheduler when to run something, and this generator builds that string for you, field by field or straight from a preset, entirely in your browser, with no sign-up and nothing uploaded. But building the string was never the hard part. The trap with cron is that a wrong schedule looks exactly like a right one. 0 * * * * runs once an hour. * 0 * * * runs sixty times, every minute from midnight to 12:59. One swap, completely different job, and cron accepts either without a peep.
So the piece that matters most here is the plain-English line under the expression, the one that reads your schedule back in words the moment you build it. Set 30 8 * * 1-5 and it says "At 08:30, on weekdays (Monday through Friday)." If that sentence doesn't match the schedule in your head, a field is wrong, and you catch it now instead of at 3 AM when the "nightly" job you shipped fires every minute. That readback is the edge here, it flags a wrong field before the string ever reaches your crontab.
How to use
- Pick a preset if one fits. Every 15 minutes, Hourly, Daily at midnight, Weekdays at 8:30, and a handful more. It fills every field, and you tweak from there.
- Or build each field by hand. For minute, hour, day-of-month, month, and day-of-week, choose a mode: Every (the
*wildcard, meaning every value of that field), Every N (a step like*/15), Specific (one fixed value from a dropdown), or Range (a span, like Monday to Friday). - Watch the expression assemble live in the highlighted box. There's a label under each token so you never lose track of which field is which.
- Read the plain-English line below it. That's the whole point of the tool. It says when the schedule actually fires, in words.
- Copy and paste it into your crontab, a workflow YAML, or your scheduler config. Reset clears everything back to
* * * * *.
The silent failures, and why you read the schedule back
The classic cron slip is a lone * in the minute field. You want an hourly job, you set the hour, and you leave the minute alone because * feels like a sensible default. Now the job fires every minute of that hour, sixty runs where you wanted one, with no error and nothing in a log to warn you. On the page, "every minute during the 9th hour" reads nothing like "at 09:00," and that gap is what stops you before you paste it.
Steps hide a quieter one. */15 in the minute field is clean, it lands on :00, :15, :30, :45, evenly spaced. But a step counts up from the lowest value the field allows and restarts every time the field wraps. So */40 hits :00 and :40, then wraps back to :00 the next hour. The gap from :40 to the following :00 is twenty minutes, not forty. Any step that doesn't divide the field evenly comes out lumpy like that. If you build one and the readback looks wrong, this is usually why.
The lowest value is not zero in every field, which trips people who read a step as "divisible by n". Minutes and hours start at 0, so */2 there does mean the even ones. Day-of-month and month start at 1, so */2 in the date field is the 1st, 3rd, 5th and so on, and */3 in the month field is January, April, July and October. The readback names the starting value for exactly this reason.
A description that updates while you type also settles the questions the raw syntax makes you guess at. 9-17 in the hour field, nine hours or eight? Read it back: "between 09:00 and 17:59," inclusive both ends, so nine.
Set both day fields and cron means OR, not AND
This is the cron rule that catches the most people, and it's why the description switches to the word OR instead of joining the two days with an and. When you set both the day-of-month field and the day-of-week field to fixed values, cron treats them as an OR. The job runs whenever either one matches.
Say you build 0 0 13 * 5 expecting "midnight on Friday the 13th." What you actually get is a job that runs at midnight on every 13th of the month and on every Friday. That's most weeks of the year, not the rare calendar coincidence you had in mind. The rule goes back to the original Vixie cron, if both day fields are restricted, the command runs when either matches the current day. Build that expression here and the readback says "on the 13th of the month OR on Friday" rather than gluing the two together with an and, which is the difference between a couple of nights a year and about sixty.
The safe habit is simple: restrict day-of-month or day-of-week, never both, unless you genuinely want the OR. Every Monday? Set the weekday to Monday, leave the date as *. The 1st of the month? Set the date, leave the weekday alone. This builder lets you set both because it won't police your intent, but now you know what happens when you do.
And a step in either day field flips the same pair back to AND
The man page words the rule as "if both fields are restricted (i.e., aren't *)", which reads as though anything other than a bare * counts. The code is narrower than the sentence. Vixie cron sets its DOM_STAR and DOW_STAR flags off the first character of each day field before parsing it, and if either flag is set the two fields are combined with AND instead of OR. A step like */2 begins with a *, so it sets the flag.
That makes 0 0 */2 * 5 run only on odd-numbered days that are also Fridays, about twenty-six times a year, where the OR reading would predict roughly two hundred and twelve. The slash is not in POSIX at all, so there is no standard to appeal to here and the implementation is the only authority. Build a step alongside a fixed day here and the readback prints AND and says why.
Whose clock does it actually run on?
Your expression says 0 9 * * *, which is nine in the morning, but nine where, exactly? Cron runs against the clock of whatever machine executes it, and that's very often not your zone. GitHub Actions cron is always UTC. AWS EventBridge defaults to UTC. Plenty of cloud servers ship set to UTC. So 0 9 * * * on GitHub Actions fires at 9 AM UTC, which might be the middle of your night. Do the conversion yourself and set the hour to the scheduler's zone, not the one on your wall.
Daylight saving is the follow-on. UTC has no DST, so a UTC-based cron holds still while your local clock springs forward and falls back, quietly shifting your "9 AM" job by an hour twice a year. All this tool does is build the expression, since what zone it runs in is a property of wherever you paste it, so confirm that before you trust the time.
Frequently asked questions
What's the difference between `*/15` and `15` in the minute field?
They look similar and do opposite things. A bare 15 is a single value, so the job fires once an hour at exactly fifteen past: 9:15, 10:15, 11:15. Put a */ in front and it becomes a step, firing four times an hour at :00, :15, :30, and :45. Reading the wrong one as the other is one of the most common cron mistakes, which is why the description line is worth a glance before you copy.
Does this build a five-field or six-field expression?
Five fields, in the order minute, hour, day-of-month, month, day-of-week. That's the standard Unix format Linux crontab, GitHub Actions, and most cloud schedulers expect. Some systems (Quartz, Spring's @Scheduled, certain node-cron setups) use six fields with seconds at the front, and pasting a five-field string into one of those shifts everything a place right. If your target wants six fields, prepend a seconds value, usually 0, to the expression this tool gives you.
Is Sunday 0 or 7 in the day-of-week field?
Sunday is 0 in the standard numbering, with Monday through Saturday running 1 to 6. Many implementations also accept 7 for Sunday, but that's not guaranteed everywhere. This generator uses 0. Pasting into a system you don't know well? Stick with 0, it's accepted universally while 7 isn't.
Can I make a list, like the 1st and the 15th?
Cron supports comma-separated lists (1,15 in the day-of-month field, 0,30 in the minute field), but this builder sticks to the Every, step, specific, and range modes because those cover almost every real schedule. If you need a list, build the closest single value here, copy it, and hand-edit the commas in. Cron reads 0 0 1,15 * * fine. Same with combined forms like 0-30/5, a range with a step.
Can it schedule "the last day of the month"?
Not in plain five-field cron. There's no last-day token, and months run 28 to 31 days, so there's nothing fixed to point at. Some extended schedulers add an L character (Quartz uses L for exactly this), but standard Unix cron doesn't understand it. The usual workaround: run the job daily near midnight and have the script check whether tomorrow is the 1st. If it is, today was the last day. Build the daily trigger here, put the date logic in your code.
Do the shortcuts like `@daily` and `@reboot` work everywhere?
No, and that's the catch. Vixie cron and most Linux distros accept named shortcuts (@daily is 0 0 * * *, @hourly is 0 * * * *, @reboot runs once at startup), and they read nicely, but they do not travel between platforms. GitHub Actions, for one, rejects @daily and demands the explicit 0 0 * * *. When a platform refuses a nickname, translate it back to the five-field form, which is what this tool builds anyway.
Why does my nightly job sometimes not run, or run twice?
That's daylight saving on a server set to a local, DST-observing zone. When the clock springs forward, an hour vanishes, so a job scheduled inside that missing window (roughly 2 to 3 AM) doesn't fire that night. When it falls back, the same wall-clock hour happens twice, and a job in it can run twice. UTC schedulers dodge this since UTC never shifts. If a job's exact minute matters, keep it out of the 2 to 3 AM local window, or run it on a UTC clock.