Cron Expression Generator

Build visually, see next runs, get plain-text explanation

{{ __t('description') }}: {{ description }}
{{ __t('next_runs') }}
  • {{ d.toLocaleString() }}
{{ __t('presets') }}

What is a cron expression?

A cron expression is a five-field notation from the Unix world that defines when a job runs: minute, hour, day of month, month, and weekday. Used in Unix systems since the 1970s, it's now standard in Kubernetes CronJobs, GitLab/GitHub Actions, AWS CloudWatch and many other systems.

How cron works internally

The classic Unix cron daemon reads /etc/crontab and /var/spool/cron/crontabs/ at startup and keeps a list of all jobs in memory. Every minute it wakes up, compares the current wall-clock time against the five fields of each expression, and launches every job whose fields match. Important: cron resolves at minute granularity, never sub-minute — there is simply no seconds component in POSIX cron. Quartz and some Spring/Java cron libraries extend the syntax to six fields with a leading seconds field, which makes them incompatible with plain Unix cron.

The DayOfMonth and DayOfWeek fields have a quirk: if both are set (not *), they are combined with OR, not AND. So 0 0 13 * 5 means "every 13th OR every Friday at midnight", not "every Friday that is also the 13th". For the latter you need Quartz syntax or you have to check inside the job itself. Also important: cron evaluates fields against the server's local time, not UTC. A job 0 3 * * * on a Europe/Berlin server simply does not fire during the spring DST switch (3 AM does not exist) and fires twice in autumn (3 AM happens twice) — modern implementations like systemd timers or anacron handle this more robustly.

Extended cron dialects bring special characters: L (last day of month/week), W (nearest weekday), # (n-th weekday in month, e.g. 5#2 = second Friday). These are Quartz/Spring-specific and are not understood by Unix cron, GitHub Actions or Vercel Cron. GitHub Actions accepts standard POSIX cron with five fields but does not guarantee on-the-minute execution — jobs can be delayed by several minutes when the runner queue is busy. For millisecond-accurate scheduling, cron is the wrong tool; use task queues like Sidekiq, Celery or dedicated schedulers like Temporal instead.

Syntax quick reference

`*` means all values, commas (1,2,3) separate values, dashes (1-5) mark ranges, slashes (*/15) mark steps. Examples: `0 0 * * 0` = every Sunday at midnight, `*/10 * * * *` = every 10 minutes, `0 9-17 * * 1-5` = every hour from 9-17 on weekdays.

Fields in detail

The five fields of a classic cron expression (POSIX crontab) each have a fixed value range. Out-of-range values cause parse errors.

  • Minute: 0-59. Example */5 = every 5 minutes starting at 0.
  • Hour: 0-23 in 24-hour format. Example 9-17 = hourly from 9 AM through 5 PM.
  • Day of month: 1-31. Beware: a 31 here never triggers in February.
  • Month: 1-12 or JAN-DEC (case-insensitive) in many implementations.
  • Day of week: 0-6 where both 0 and 7 mean Sunday. Aliases SUN-SAT are common.

Practical real-world examples

These expressions appear in real production crontabs. Copy them as a starting point and adjust to your timezone.

  • 0 9 * * 1-5 — weekdays at 9:00 AM, classic for morning reports or Slack standups.
  • */15 8-18 * * 1-5 — every 15 minutes during office hours, for polling vendor APIs.
  • 0 2 * * 0 — Sundays at 2 AM: typical for full backups or VACUUM ANALYZE in Postgres.
  • 30 3 1 * * — first of each month at 03:30 for month-end close, invoice export or logrotate archiving.
  • 0 0 1 1 * — once per year on January 1st at midnight, for example to reset annual counters.

Limits, pitfalls and good practice

cron does not guarantee exactly-once semantics. If the server crashes between two wakeups, the trigger is lost — anacron closes this gap for once-per-day jobs, classic cron does not. If two instances of a job run in parallel (because the previous run has not finished), race conditions appear; use flock or file locks inside the script itself. In container environments like Kubernetes, CronJob replaces the daemon, but the concurrencyPolicy: Forbid flag must be set explicitly, otherwise K8s spawns new pods while the old one is still running. Timezones are another classic trap: containers usually run in UTC but the crontab was written for Europe/Berlin — the result is jobs firing two hours early. Set CRON_TZ=Europe/Berlin at the top of the crontab or convert everything to UTC explicitly. Logging via >> /var/log/job.log 2>&1 is mandatory, because cron mails stdout/stderr by default, which is lost in container environments.

Frequently asked questions about cron

What does * * * * * mean?
Every minute, every hour, every day, every month, every weekday — i.e. once per minute, 1440 times a day.
How do I write "every 30 seconds" in cron?
Not directly possible in POSIX cron because the smallest resolution is one minute. Workaround: two lines * * * * * and * * * * * sleep 30; cmd, or use Quartz/Spring which add a seconds field.
Why is my cron job not running?
The most common causes: wrong PATH (cron starts with a minimal environment — use absolute paths), missing trailing newline in the crontab, wrong account (user crontab vs. /etc/crontab), missing execute permission (chmod +x), or a job that reads from stdin and gets nothing.
Which timezone does cron use?
By default the system's local time. Set CRON_TZ=UTC (or any IANA zone) as the first line of the crontab, or via the TZ variable. In Kubernetes CronJob, the spec.timeZone field controls evaluation (available since K8s 1.27).
Is 0 0 1 * MON the same as "first Monday of the month"?
No. Since DayOfMonth and DayOfWeek combine with OR, this expression fires on the 1st of every month AND on every Monday. For the actual first Monday you need Quartz syntax 0 0 0 ? * MON#1 or a wrapper check in the script: "if day > 7, exit".
What happens at a daylight saving switch?
Classic Unix cron handles DST poorly: in spring, jobs between 02:00 and 03:00 are skipped, in autumn they fire twice. Vixie cron and systemd timers have heuristics for this. Safe practice: do not schedule jobs between 02:00 and 04:00 local time, or use UTC for all schedules.

Related tools