The cron expression 0 9 * * 1-5 as a crontab line on the left, and the same weekday-morning schedule written for a Kubernetes CronJob, a GitHub Actions workflow and a systemd OnCalendar timer on the right.
Weekdays at 09:00, written four times. The five fields survive unchanged into Kubernetes and GitHub Actions, systemd wants weekday, date and time instead. What differs is the clock: the crontab and the timer read the machine timezone, the CronJob whatever timeZone says, the GitHub workflow UTC unless a timezone key is set.
5 fields, 4 controls each·6 export targets·1 minute is the finest crontab resolution·GitHub Actions: 5 minutes minimum

Five fields, four choices each

A cron expression is minute, hour, day of month, month and day of week, in that order, and each field is one of four things. The builder above offers exactly those four, so clicking cannot produce a token a crontab would reject. What comes out is the plain five-field dialect every cron daemon, Kubernetes and most CI schedulers read.

ChoiceTokenExampleMeans
every** in hourany hour, no restriction
every N*/N*/15 in minute:00, :15, :30, :45, counted from the start of the field and restarting every hour
specifica,b,c6,18 in hourexactly those values, any number of them
rangea-b1-5 in day of weekevery value from a through b, both ends included

Typing into the big field works in the other direction. The builder reads the expression and sets its controls. A token that mixes forms, say 1,3-5/2, is valid cron but has no single control, so that column shows it as custom and leaves the text alone. Months and weekdays appear as names in the builder, and --names writes them into the expression too.

Ranges do not wrap. 22-6 in the hour field is an error, not "overnight", and the builder's range selects will not let you build it. Type 22-23,0-6 if you need it.

Schedules ready to copy

The schedules people search for, as the expression the generator would build. Paste any of them into the field above to see the next runs against your own clock and the six export snippets. The syntax behind them, token by token, is on the cron cheatsheet.

ScheduleExpressionNote
every minute* * * * *1440 starts a day
every 5 minutes*/5 * * * *288 a day, the GitHub Actions minimum
every 10 minutes*/10 * * * *
every 15 minutes*/15 * * * *
every 30 minutes*/30 * * * *same as 0,30 * * * *
every hour0 * * * *@hourly
every 2 hours0 */2 * * *even hours, 0 1-23/2 * * * for odd
every 6 hours0 */6 * * *00, 06, 12, 18
every 90 minutes0 0-21/3 * * * + 30 1-22/3 * * *two lines, the minute field resets every hour
twice a day0 6,18 * * *or 0 */12 * * * for 00 and 12
every day at midnight0 0 * * *@daily, shared with every other default job
every day at 6am0 6 * * *
every night at 03:3030 3 * * *outside the DST hour, unlike 02:30
every Monday0 0 * * 10 0 * * MON
every Monday at 8am0 8 * * 1
every Sunday at midnight0 0 * * 0@weekly
weekdays at 90 9 * * 1-5
every weekend0 10 * * 0,6Saturday and Sunday at 10:00
every 10 minutes between 9 and 5*/10 9-16 * * 1-5last run 16:50, add 0 17 * * 1-5 for 17:00 sharp
every 15 minutes, business hours*/15 9-17 * * 1-5last run 17:45
first day of month0 0 1 * *@monthly
15th of the month at noon0 12 15 * *
every quarter0 0 1 */3 *Jan, Apr, Jul, Oct, same as 1,4,7,10
every year0 0 1 1 *@yearly
last day of month0 23 28-31 * * + a date testnot in cron, see the parser FAQ
last Friday of month0 17 25-31 * * + [ "$(date +\%u)" = 5 ]the last seven days hold exactly one Friday, Quartz writes 6L
every 30 secondsnonecron has no seconds, two lines with sleep 30 or a systemd timer

Two entries say "none" or need a date test, and that is deliberate. A generator that silently emits 0 0 13 * 5 for "Friday the 13th" or */90 for "every 90 minutes" produces an expression that installs fine and runs on the wrong days, and we would rather the table says so than the crontab does at 3 am.

A table of twelve common cron schedules with their five-field expression and the number of runs per day, from 1440 for every minute down to once on the first of the month.
The expressions people copy most, with the run count the generator computes for each: the product of the minute and hour values on every day the schedule admits. The last three rows run once a day but not every day, which is where a quick look at the next-runs list earns its place.

One schedule, every scheduler

"Weekdays at 09:00" in every place a schedule string goes. The five-field core is the same in the first four rows, the Java and AWS rows add a seconds or year field and count Sunday differently, which is why the export writes MON-FRI there instead of 1-5. Type a 7 for Sunday and the builder writes it back as 0, the one spelling Kubernetes and GitHub accept as well.

SchedulerWeekdays at 09:00FieldsSunday is
crontab0 9 * * 1-550 or 7
Kubernetes CronJobschedule: "0 9 * * 1-5"5, plus @daily and friends0 only, 7 is rejected
GitHub Actions- cron: '0 9 * * 1-5'5, no macros0, the docs list 0-6
JenkinsH 9 * * 1-55, H spreads the minute0 or 7
systemd timerOnCalendar=Mon..Fri *-*-* 09:00:00weekday, date, timeSun
Spring @Scheduled0 0 9 * * MON-FRI6, seconds first0 or 7
Quartz0 0 9 ? * MON-FRI6 or 7, ? in one day field1
AWS EventBridgecron(0 9 ? * MON-FRI *)6, year last, ? required1

The export panel writes the first six of these for whatever you built. Jenkins is not there because its whole point is the H token, which picks the concrete minute per job from a hash of the job name. You write H where this page would write a number. Quartz is not there either, because a Quartz expression without L, W or # is the Spring line with a ? in the unused day field. If you need those tokens you are no longer building a five-field schedule.

Which clock each platform reads

The expression never carries a timezone. Each scheduler supplies its own, and that is where "runs at 9" turns into 7 or 10.

PlatformDefault clockHow to change it
crontabsystem timezone of the machineCRON_TZ=Europe/Vienna at the top of the crontab with cronie, plain vixie cron and macOS have nothing
Kubernetes CronJobkube-controller-manager, normally UTCspec.timeZone: "Europe/Vienna", GA since 1.27
GitHub ActionsUTCtimezone: "Europe/Vienna" under the cron entry, a skipped DST hour moves the run to the next valid time
systemd timersystem timezoneappend a zone: OnCalendar=*-*-* 09:00:00 Europe/Vienna
Spring @ScheduledJVM defaultzone = "Europe/Vienna" on the annotation
AWS EventBridgeUTC for rulesEventBridge Scheduler takes ScheduleExpressionTimezone per schedule
Jenkinscontroller timezoneTZ=Europe/Vienna as the first line of the spec

The next-runs list above uses your browser's timezone by default and shows UTC beside it. With --utc on, the schedule is read in UTC outright, which is the honest view for an EventBridge rule. The Kubernetes, GitHub and Spring exports carry your timezone name in timeZone, timezone and zone, because that is what the schedule you just built actually means. Whether the job itself should then store its results in UTC is a separate question, answered in store dates in UTC.

The three traps the findings catch

The findings panel is not a validator, the builder already cannot produce invalid syntax. It catches the expressions that are valid and wrong.

  • Both day fields restricted. 0 0 13 * 5 is every 13th plus every Friday, about 62 days a year, not Friday the 13th. POSIX defines the two day fields as either-or once both carry a value. The finding shows both counts for your expression, and the systemd and EventBridge snippets go blank because neither can say OR.
  • The hour DST deletes. The generator probes your browser's timezone for the next spring-forward night and names the date and hour that will not exist. A job at 02:30 in Vienna lands in that hour once a year, and whether it then runs once, never or twice depends on the daemon. 30 3 * * * is the same job without the question.
  • A step that does not divide its field. */7 in minutes is 00, 07, 14 ... 56 and then 00 again, a 4-minute gap every hour. */5 in hours leaves 4 hours between 20:00 and midnight. The finding names the short interval. Steps that divide 60 and 24 cleanly are 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30 and 1, 2, 3, 4, 6, 8, 12.

Each of these has a longer write-up. The field-by-field reading and the dialect differences on the cron parser page, the environment and DST stories in cron pitfalls.

What the export snippets assume

The crontab line logs to a file with >> /var/log/job.log 2>&1, redirect order included, so a failing job leaves evidence. The CronJob manifest quotes the schedule (the YAML linter catches the unquoted */5 if you forget), sets concurrencyPolicy: Forbid and uses busybox:1.36 as a placeholder image. The GitHub workflow adds workflow_dispatch so it can be run by hand, and a timezone key unless --utc is on. The systemd timer sets Persistent=true, which runs a missed schedule at the next boot, something cron never does. Replace the placeholders, keep the flags.

--utc and --names

--utc reads the schedule on the UTC clock for the next-runs list, writes Etc/UTC into the Kubernetes and Spring snippets and drops the timezone key from the GitHub workflow. --names spells months and weekdays as JAN and MON-FRI in the expression itself. Single names are in crontab(5), ranges of names are documented as unsupported there but cronie, busybox and Kubernetes take them, and names are the one spelling that survives a move to Quartz or EventBridge, where 1 is Sunday. The GitHub Actions docs list SUN-SAT as well, so that snippet follows the flag too.

The schedule is in the address bar as ?e=, so copy link hands a colleague the same builder state. The parser link in the corner opens the same expression on the parser page for the field-by-field reading.

Scheduling questions, by platform

How do I run a cron job every 5 minutes starting at a different minute, like :02, :07, :12?

2-57/5 * * * *. A step counts from the start of its range, so 2-57/5 fires at 02, 07, 12 and so on up to 57. Plain */5 always starts at :00.

Is 0 * * * * the same as */60 * * * *?

In effect yes, both fire once an hour at minute 0, because a step of 60 over the range 0-59 only ever hits 0. Write 0 * * * * anyway. */60 reads like "every 60 minutes" but means nothing of the sort once the hour field is restricted (0 */60 is not every 60 hours).

How do I schedule a cron job at midnight?

0 0 * * * runs every day at 00:00, and @daily or @midnight mean the same thing. Midnight is also when every other default job on the box runs, from logrotate to package indexes, so a busy server sees a spike exactly then. Anything that is not tied to the date boundary is better off at 00:17 or 03:40.

How do I run a cron job every Monday at 8am?

0 8 * * 1, or 0 8 * * MON with names. Leave day of month as *, cron ORs both day fields.

What is the cron expression for the first day of the month?

0 0 1 * * for midnight on the 1st, @monthly is the shorthand. For a fixed time use the minute and hour fields, 15 3 1 * * is 03:15. The 1st falls on a weekend about two months in seven. If the job must land on a working day, schedule 1-3 and test date +%u in the command, because "first weekday" has no cron syntax.

What is the cron expression for every 2 hours?

0 */2 * * * fires at 00:00, 02:00 and every even hour after that. For the odd hours write 0 1-23/2 * * *. A step in the hour field restarts at midnight, which is why */5 in hours gives a 4-hour gap between 20:00 and 00:00.

How do I run a cron job every 15 minutes during business hours?

*/15 9-17 * * 1-5 runs at :00, :15, :30 and :45 from 09:00 through 17:45, Monday to Friday. The hour range is inclusive, so hour 17 means the whole hour. If the last run must be 17:00 sharp, use two lines: */15 9-16 * * 1-5 and 0 17 * * 1-5.

How do I run a cron job twice a day?

List both hours: 0 6,18 * * * runs at 06:00 and 18:00, 0 */12 * * * at 00:00 and 12:00.

How do I set up a cron job in Kubernetes?

Create a CronJob resource: kind: CronJob in batch/v1, spec.schedule with a five-field expression in quotes, and spec.jobTemplate with the pod spec. The quotes matter, */5 * * * * unquoted is read by YAML as an alias and kubectl fails with "did not find expected alphabetic or numeric character". Set spec.timeZone (stable since 1.27) and concurrencyPolicy: Forbid if two runs must never overlap. Test a run without waiting: kubectl create job test --from=cronjob/name.

How do I schedule a GitHub Actions workflow with cron?

Add on: schedule: with a list item - cron: '0 9 * * 1-5' to the workflow file on the default branch. The expression is read in UTC unless a timezone: "Europe/Vienna" line sits under the cron entry, the shortest interval is 5 minutes, and runs start late when GitHub's queue is busy, worst at the top of the hour. Add workflow_dispatch: next to schedule so the same workflow can be started by hand for a test.

Do Kubernetes CronJobs run in UTC?

Yes, unless spec.timeZone is set. The controller manager clock is UTC on practically every managed cluster. spec.timeZone is stable since Kubernetes 1.27.

How do I convert a crontab expression for Spring @Scheduled?

Put a seconds field in front: 0 9 * * 1-5 becomes @Scheduled(cron = "0 0 9 * * MON-FRI"). Spring numbers weekdays like cron (0 or 7 is Sunday), so the numbers would survive too, but names keep the meaning if the same string ever lands in Quartz, where 1 is Sunday. Two differences remain: Spring ANDs the two day fields where cron ORs them, and without zone = "..." the schedule runs on the JVM default timezone.

How do I test a cron expression before deploying it?

Read the next run times instead of waiting for one. This page lists the next five in your timezone and UTC. On a server, systemd-analyze calendar 'Mon..Fri *-*-* 09:00:00' does the same for a timer, and kubectl create job --from=cronjob/name fires a CronJob immediately. For the command itself, run it once with the environment cron provides: env -i HOME=$HOME SHELL=/bin/sh PATH=/usr/bin:/bin /path/to/command.