Cron Builder & Expression Reader
Build a cron schedule from dropdowns, or paste one you already have to see what it means and when it next fires. Outputs crontab, systemd, k8s and Actions.
Cron Builder & Expression Reader
No day of the week is ticked, so that field stays * — this schedule runs every day, not weekly.
Hand-edited, so the dropdowns above no longer describe this expression.
How to use Cron Builder
Build one, or read one
Two modes, same outputs. Build one starts from what you want to schedule and writes the expression. Read one takes an expression you already have — pasted from a crontab, a workflow file or a code review — and tells you what it means and when it fires next. Shorthands like @daily and @reboot are understood, as are names like MON-FRI and JAN.
Set the exact time — or edit a field directly
For daily/weekly/monthly schedules, pick the hour and minute (24-hour clock). For weekly, also tap the day-of-week pills — multiple days are allowed. For monthly, choose day-of-month; for yearly, pick the month too. The five boxes underneath show what those choices produced, field by field, and you can type straight into them in either mode — useful for the things no dropdown offers, like */3 hours or 1-5 day-of-month. Do that and the dropdowns stop driving (they cannot express most hand edits, so the tool says so rather than guessing); one click rebuilds from them again.
Paste your command
The command goes into the rendered crontab line and the example k8s/GitHub Actions output. Leave it as the placeholder if you only want the cron expression.
Check the next five runs, then copy
Before you copy anything, read the five fire times. That is the check that catches the mistakes a plain-English summary can hide — a day-of-week off by one, a 31st that skips February and April, a schedule that can never fire at all. Then click Copy on whichever of the four formats you need: bare crontab, systemd timer pair, Kubernetes CronJob YAML, or GitHub Actions workflow snippet. Share link copies a URL that reopens this page with the same expression loaded — useful for a pull-request comment or a ticket, where the reviewer gets the schedule and the fire times rather than five fields to decode.
Cron — still the world's most important scheduler
Cron has shipped with Unix since at least the Seventh Edition (1979), whose manual calls it the "clock daemon", and POSIX.1-2024 crontab(1) still specifies the same five fields — minute, hour, day-of-month, month, day-of-week (0 = Sunday) — that a crontab line had then. The schedulers layered on top since (systemd timers, Kubernetes CronJob, GitHub Actions schedules, AWS EventBridge) all take a cron expression as input, with small dialect differences this page names where they matter. If you write code that needs to run on a schedule, you will eventually write a cron expression — often while wondering whether asterisk-slash-15 means "every 15 minutes" or "for 15 minutes".
Why a visual builder beats writing it by hand
Most cron bugs come from the same three confusions: forgetting that day-of-week starts at Sunday=0 (not Monday=1 like every calendar humans use), writing * * 1 * 1 when you meant "first Monday of the month" (it actually runs every Monday and the 1st of every month), and mistaking the comma operator for a range. A wizard sidesteps all three by translating your intent into the right expression. Power users still prefer to type it directly — so Read one takes the typed expression instead and answers the question a wizard cannot: not "what did I mean" but "what does this line, already in production, actually do". Either way the five fire times are the real check. A summary can describe 0 9 1 * 1 accurately and still leave you thinking it means the first Monday of the month; a list showing the 1st, then the 2nd, then the 9th does not.
The cron expression for "every day at 9am" is0 9 * * *. The cron expression for "every weekday at 9am" is0 9 * * 1-5. Get the day-of-week field wrong and you discover the bug at 09:01 on Saturday.
Cron in modern infrastructure
Plain crontab is fine for a single-server VPS or a shared host where you can SSH in. systemd timers have largely replaced it on modern Linux distros — they survive missed runs (the Persistent=true option), integrate with the journal for logging, and can be unit-tested in isolation. Kubernetes CronJob handles scheduling across a cluster, manages pod lifecycle, and provides retries via backoffLimit. GitHub Actions schedules run inside CI/CD without needing infrastructure of your own — with three documented limits: the shortest interval is once every 5 minutes; the expression is read as UTC unless the schedule entry adds a timezone: line with an IANA name such as Asia/Singapore; and GitHub says runs "can be delayed during periods of high loads", names the start of every hour as the worst time, and warns that "some queued jobs may be dropped".
What the fire-time preview checks that a summary cannot
The parser behind Read one applies the rules the schedulers themselves apply, and the five fire times make them visible. The two day fields are OR-ed. POSIX crontab(1) puts it this way: if day-of-month and day-of-week are both given as elements or lists, "any day matching either the month and day of month, or the day of week, shall be matched"; cronie's crontab(5) says the same — "if both fields are restricted (i.e., do not contain the '*' character), the command will be run when either field matches". So 0 9 1 * 1 fires on the 1st and on every Monday. The subtlety this tool gets right is that */2 is still a star form: 0 9 */2 * 1 is AND-ed, and fires only on Mondays that are odd days of the month. Steps count within their field. */7 in the minute field is 0, 7, 14 … 56, then 0 again at the next hour; 5/10 expands to 5-59/10, as Vixie cron does. Sunday is 0 and 7, and MON-FRI, JAN and the @daily-style shorthands are accepted (all cronie extensions, not POSIX). Impossible dates are reported: 0 0 30 2 * shows "never", and a local time that a daylight-saving jump skips is dropped rather than shown an hour off. And six- or seven-field expressions — the Quartz and AWS EventBridge dialects with a seconds or year column — are refused with a message rather than misread.
Time zones are the remaining source of "why did this fire at 17:00" tickets, and every platform answers differently: cronie uses the system's local zone (/etc/localtime) unless the crontab sets CRON_TZ=; Kubernetes uses the kube-controller-manager's local zone unless spec.timeZone is set (stable since v1.27); GitHub Actions uses UTC unless the schedule entry sets timezone:. Singapore, Malaysia, the Philippines and Hong Kong all sit at +08:00 with no daylight saving, so a job written "for 09:00" against a UTC scheduler fires at 17:00 local. The Kubernetes output above writes timeZone from your browser's zone for exactly this reason.
Things cron does not handle well
Cron does not retry on failure (your script has to handle that). It does not track whether the previous run is still in progress (use a lockfile or systemd's OnUnitInactiveSec= instead). It cannot fire on an interval like "every 90 minutes" — the field is minute or hour, not multiples in between. For anything more complex than a fixed schedule, look at task queues (Celery, Sidekiq, Laravel queues) backed by Redis or a database; those handle retries, distributed locking, and observability in ways that no version of cron ever will.
Sunday is 0 and 7, and the two day fields are OR-ed
Seventh Edition Unix (1979) shipped cron as its "clock daemon" — the manual's own words — reading /usr/lib/crontab once a minute. The five-field line has outlived every scheduler built since.
POSIX.1-2024 crontab(1) still specifies exactly five fields with these ranges: minute 0–59, hour 0–23, day of month 1–31, month 1–12, day of week 0–6 with 0 = Sunday. Names, steps and @daily are all extensions.
Day-of-week numbering varies: POSIX cron uses Sunday=0, ISO 8601 uses Monday=1. cronie's crontab(5) accepts "0 or 7 is Sunday, or use names" — and so does this page.
When both day-of-month AND day-of-week are restricted, cron runs when EITHER matches — POSIX and crontab(5) agree. The catch this tool models: */2 counts as a star form, so 0 9 */2 * 1 is AND-ed, not OR-ed.
GitHub Actions documents that scheduled runs "can be delayed during periods of high loads", that the start of every hour is a high-load time, and that "some queued jobs may be dropped" — so :07 or :23 is a better minute than :00.
Kubernetes CronJob's spec.timeZone is stable since v1.27. Without it, the schedule is read in the kube-controller-manager's local zone — UTC in most clusters — which is where the "why is my 09:00 job running at 17:00" tickets come from.
systemd's OnCalendar= accepts fractional seconds (rounded to microseconds), but AccuracySec= defaults to 1min, so a timer fires anywhere inside a minute-wide window unless you tighten it. Cron's own resolution is one minute.
AWS EventBridge Scheduler supports cron AND rate expressions ("every 5 minutes") plus one-time scheduled events — extending classic cron's vocabulary.
The string "@reboot" in crontab runs a command at system startup. "@daily", "@hourly", "@weekly", "@monthly", and "@yearly" are all valid shorthands.
Not every "cron" is this cron: AWS EventBridge Scheduler uses six fields (with a year) and Quartz uses six or seven (with seconds). Paste one here and the reader says so instead of shifting every field by one.
FAQ
-
Yes — switch to Read one and paste it. You get a plain-English summary, the next five fire times in your own timezone, and a specific error if it will not parse (which field, and what that field accepts). The five per-field boxes stay in sync with the expression in both modes, so you can change just the day-of-week and watch the fire times move. Shorthands (
@daily,@hourly,@reboot), names (MON-FRI,JAN) and7for Sunday are all understood. Six- and seven-field expressions — the Quartz, AWS EventBridge and Alibaba Function Compute dialects, which add seconds and/or year — are rejected with a note saying so rather than silently misread. -
Share link copies a URL with the expression in it —
?e=0+9+*+*+1-5— which reopens the page with that schedule loaded, explained, and showing its next five runs. The link is deliberately readable rather than an encoded blob, so it still says what it does when someone reads it in a pull request, and it can be edited by hand. On a phone it opens the native share sheet instead of copying. Nothing is stored on our side: the schedule travels in the link, and the address bar tracks your current expression so a refresh or a bookmark keeps it. -
Each platform answers differently. cronie (the crontab on most Linux distributions) uses the system's local zone,
/etc/localtime, unless the crontab setsCRON_TZ=Asia/Singaporefor its lines. Kubernetes reads the schedule in the kube-controller-manager's local zone unlessspec.timeZoneis set (stable since v1.27). GitHub Actions uses UTC unless the schedule entry addstimezone: "Asia/Singapore"beside thecron:line. The preview above is your browser's zone. Whatever the platform, run the job once and check the actual fire time. -
The 5-field expression works on POSIX cron, Linux crontab, Kubernetes CronJob, GitHub Actions and GitLab CI schedules; systemd wants
OnCalendar=syntax instead, which the systemd output converts to. Two dialects are different: AWS EventBridge Scheduler's cron takes six fields including a year, and Quartz-style schedulers take six or seven with a seconds column. Those need their own tools, and this page refuses them rather than guessing which column is which. -
It's the "step" operator.
*/15in the minute field means "every 15 minutes" (i.e. 0, 15, 30, 45). It does NOT mean "for 15 minutes". The starting offset is 0 unless you write a range like5-55/15. -
Not in one line. A step counts within its own field and restarts at the top of the range —
*/7in the minute field is 0, 7, 14 … 56 and then 0 again at the next hour — so there is no way to carry a remainder from one hour into the next. "Every 90 minutes" needs two entries:0 */3 * * *and30 1-23/3 * * *. If you can choose the scheduler, a systemd timer withOnUnitActiveSec=90minor a task queue expresses it directly. -
Runs the command once at system startup. Not a true cron schedule but a useful shortcut for boot-time scripts. Not supported in Kubernetes CronJob or GitHub Actions — use the platform's native startup hooks instead.
-
Cron itself has no overlap protection. Wrap the command in
flock -n /tmp/myjob.lock --to use a filesystem lock. systemd timers can useOnUnitInactiveSec=instead ofOnCalendar=to chain runs. Kubernetes CronJob hasconcurrencyPolicy: Forbid. -
GitHub's documentation says the schedule event "can be delayed during periods of high loads of GitHub Actions workflow runs", that high-load times "include the start of every hour", and that "if the load is sufficiently high enough, some queued jobs may be dropped". Its advice is to schedule at a different minute of the hour — :07, :13, :23 rather than :00. Anything that must run on time should run elsewhere.
-
Cron for simple, fixed-schedule tasks that don't need retries or visibility (rotate logs, backup database). Task queue (Celery, Sidekiq, Laravel queues backed by Redis) for anything that needs retries on failure, distributed locking, observability, or fire-and-forget jobs triggered by user actions.
-
For classic cron: just run the command directly in a shell. For systemd timers:
systemctl start myjob.servicetriggers an immediate run. For Kubernetes CronJob:kubectl create job --from=cronjob/myjob myjob-manual. For GitHub Actions: use theworkflow_dispatchtrigger to add a manual-run button.
Method & sources
How it computes
Build mode writes a five-field POSIX/Vixie cron expression from the dropdowns; Read mode parses one you paste. Parsing expands each field to its set of values — lists, ranges, */n and n/m steps (n/m runs from n to the field maximum, as Vixie cron does), three-letter month and weekday names, 7 as Sunday and the @hourly … @reboot shorthands — then computes the next five fire times in your browser's zone by walking days and testing the hour and minute sets.
What this tool implements
- Fields and ranges per POSIX.1-2024 crontab(1): minute 0–59, hour 0–23, day of month 1–31, month 1–12, day of week 0–6 with 0 = Sunday. 7 for Sunday, three-letter names, step values and the @-shorthands are cronie/Vixie extensions (crontab(5)) and are accepted and labelled as such.
- The day rule is the POSIX/Vixie one: when day-of-month and day-of-week are both restricted, 'any day matching either … shall be matched' (POSIX); 'if both fields are restricted (i.e., do not contain the "*" character), the command will be run when either field matches' (crontab(5)). A */n field is a star form, so 0 9 */2 * 1 is AND-ed — Mondays that are odd days of the month — not OR-ed.
- Steps expand within their field and never carry across it: */7 in the minute field is 0,7,…,56 then 0 again; 5/10 is 5-59/10 (Vixie). Hence 'every 90 minutes' is not expressible in one line, and the FAQ shows the two-line form.
- Six- and seven-field dialects (Quartz seconds column, AWS EventBridge Scheduler's year column) are rejected with a message naming the dialect rather than parsed with shifted columns.
- Zone handling is stated per platform rather than assumed: the preview uses the browser's IANA zone; cronie uses the system zone or CRON_TZ=; Kubernetes uses the kube-controller-manager's local zone unless spec.timeZone (stable v1.27) — the Kubernetes output writes timeZone from the browser's zone; GitHub Actions uses UTC unless the schedule entry sets timezone: with an IANA name, and its shortest interval is five minutes.
- The systemd output is a timer/service pair with OnCalendar= derived from the parsed sets and Persistent=true; AccuracySec= is left at its documented 1-minute default, which the facts section explains.
Sources
- IEEE Std 1003.1-2024 (POSIX.1-2024), Shell & Utilities, crontab — Input Files: the five fields, their ranges, 0 = Sunday, and the day-of-month/day-of-week matching rule: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/cronta…
- crontab(5), cronie project manual page — step values, names, '0 or 7 is Sunday', @reboot/@yearly/…/@hourly, CRON_TZ, and the note that both-restricted day fields match when either does: https://man7.org/linux/man-pages/man5/crontab.5.html
- systemd.time(7) — calendar event syntax for OnCalendar=, fractional seconds rounded to six decimals; systemd.timer(5) — AccuracySec= 'Defaults to 1min', Persistent=: https://man7.org/linux/man-pages/man7/systemd.time.7.html and https://man7.org/linux/man-pages/man5/systemd.timer.5.html
- Kubernetes documentation, CronJob — Time zones: spec.timeZone stable since v1.27; without it the kube-controller-manager interprets schedules relative to its local time zone: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
- GitHub Docs, Events that trigger workflows — schedule: POSIX cron syntax, UTC by default with an optional IANA timezone, shortest interval once every 5 minutes, delays at high load and possible dropped jobs: https://docs.github.com/en/actions/writing-workflows/choosing-whe…
- Amazon EventBridge Scheduler User Guide — Schedule types (rate-based, cron-based, one-time): https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-t…
- Unix Programmer's Manual, Seventh Edition (1979), cron(8) 'clock daemon': https://man.cat-v.org/unix_7th/8/cron
What can make this go out of date
- Platform behaviour cited on the page — Kubernetes spec.timeZone (stable v1.27), GitHub Actions' timezone: support and 5-minute minimum, systemd's AccuracySec= default — moves with those projects' releases; the stamp date is when each was read.
- The next-run preview uses your browser's copy of the IANA time-zone database for daylight-saving edges; a browser with stale tz rules would show a different local time at those edges only.
Abridged — the full review record for this tool runs longer than the list above.
Pick up where you left off
Stored only in this browser — never sent to our servers.