Build visually, see next runs, get plain-text explanation
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.
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.
`*` 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.
The five fields of a classic cron expression (POSIX crontab) each have a fixed value range. Out-of-range values cause parse errors.
0-59. Example */5 = every 5 minutes starting at 0.0-23 in 24-hour format. Example 9-17 = hourly from 9 AM through 5 PM.1-31. Beware: a 31 here never triggers in February.1-12 or JAN-DEC (case-insensitive) in many implementations.0-6 where both 0 and 7 mean Sunday. Aliases SUN-SAT are common.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.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.
* * * * * mean?* * * * * and * * * * * sleep 30; cmd, or use Quartz/Spring which add a seconds field.chmod +x), or a job that reads from stdin and gets nothing.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).0 0 1 * MON the same as "first Monday of the month"?0 0 0 ? * MON#1 or a wrapper check in the script: "if day > 7, exit".