Cron is one of those tools that every developer encounters eventually. You need to run a database backup every night, send a weekly digest email, or poll an API every five minutes — and someone mentions cron. You look up the syntax, copy something from Stack Overflow, it works, and you move on. Then six months later you need to change the schedule and you're staring at five numbers wondering what each one means again.

This guide is meant to be the one you save. After reading it you should be able to read and write any cron expression from scratch, without guessing.

What cron actually is

Cron is a time-based job scheduler built into Unix-like operating systems. A cron job is a command or script that cron runs automatically on a defined schedule. The schedule is defined by a cron expression — a string of five fields that describe when to run the job.

Cron expressions show up everywhere: Linux crontabs, GitHub Actions schedules, AWS EventBridge, Kubernetes CronJobs, Vercel cron functions, Railway cron jobs, and more. The syntax is largely the same across all of them, with minor variations.

Anatomy of a cron expression

A standard cron expression has five fields separated by spaces:

* * * * *
minute hour day of month month day of week

A helpful mnemonic: M H D M W — Minutes, Hours, Days, Months, Weekdays. Each field has a valid range and a set of special characters you can use.

Field ranges and allowed values

Field Range Special values
Minute 0–59 * , - /
Hour 0–23 * , - /
Day of month 1–31 * , - / ?
Month 1–12 or JAN–DEC * , - /
Day of week 0–7 or SUN–SAT (0 and 7 are both Sunday) * , - / ?

Special characters

* — every

An asterisk means "every valid value for this field." * in the minute field means every minute. * in the hour field means every hour.

, — list

A comma lets you specify multiple values. 1,15,30 in the minute field means at minute 1, 15, and 30.

- — range

A hyphen defines a range. 9-17 in the hour field means every hour from 9am to 5pm inclusive.

/ — step

A slash defines a step interval. */5 in the minute field means every 5 minutes. 0-30/10 means every 10 minutes between minute 0 and minute 30.

? — no specific value

Used in day-of-month and day-of-week fields to mean "I don't care." When you specify a day of week, use ? in the day-of-month field, and vice versa. Not all cron implementations support this — standard Linux crontab uses * instead.

Real examples

// run at midnight every day
0 0 * * *
// run every 5 minutes
*/5 * * * *
// run at 9am Monday through Friday
0 9 * * 1-5
// run at 6am and 6pm every day
0 6,18 * * *
// run at 2:30am on the 1st of every month
30 2 1 * *
// run every weekday at noon
0 12 * * 1-5
// run at 11:59pm on December 31st
59 23 31 12 *
// run every 15 minutes between 8am and 5pm on weekdays
*/15 8-17 * * 1-5

Common mistakes

Confusing day-of-week numbering

Different systems handle Sunday differently. In standard crontab, Sunday is both 0 and 7. In some systems, 0 is Sunday and 6 is Saturday. In others, 1 is Monday and 7 is Sunday. Always check the documentation for the platform you're using. When in doubt, use the three-letter abbreviation (SUN, MON, etc.) if the platform supports it — it's unambiguous.

Forgetting timezone

Cron runs in the timezone of the server unless configured otherwise. If your server is UTC and your users are in EST, a job scheduled for 0 9 * * * runs at 4am local time for East Coast users. Always be explicit about timezone. Most modern platforms (GitHub Actions, AWS, Vercel) let you specify timezone separately.

Thinking */5 means "every 5 minutes starting now"

*/5 in the minute field means at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, and 55 — fixed to the clock, not relative to when the job was added. If you add a job at 2:03pm with */5, it first runs at 2:05pm, not 2:08pm.

Using both day-of-month and day-of-week

If you specify a value in both the day-of-month and day-of-week fields (rather than using * in one), most cron implementations treat them as an OR condition, not AND. 0 0 1 * 1 runs at midnight on the 1st of every month and every Monday — not just on Mondays that fall on the 1st. If you want "the first Monday of the month" you need a workaround, since standard cron can't express that directly.

Platform-specific variations

Standard Linux crontab uses the five-field format above. But many platforms extend or modify it:

  • GitHub Actions uses standard five-field cron, always in UTC. The minimum interval is every 5 minutes.
  • AWS EventBridge (CloudWatch Events) uses a six-field format with an optional seconds field, and uses ? for the day-of-month/day-of-week conflict. It also adds L (last) and W (weekday nearest to) special characters.
  • Kubernetes CronJobs use standard five-field cron and support @hourly, @daily, @weekly, @monthly, and @yearly shortcuts.
  • Vercel and Railway use standard five-field cron. Railway requires a minimum interval of 1 minute.

Shorthand expressions

Many cron implementations support named shortcuts for common schedules:

Shorthand Equivalent Meaning
@yearly0 0 1 1 *Once a year at midnight on January 1st
@monthly0 0 1 * *Once a month at midnight on the 1st
@weekly0 0 * * 0Once a week at midnight on Sunday
@daily0 0 * * *Once a day at midnight
@hourly0 * * * *Once an hour at the start of the hour
@rebootOnce at startup (Linux crontab only)

How to read an unfamiliar expression

When you encounter a cron expression you don't immediately recognize, read it field by field left to right: minute, hour, day-of-month, month, day-of-week. Ask yourself: is this field a specific value, a range, a list, a step, or a wildcard? Build up the meaning piece by piece.

Take 0 */6 * * *. Minute is 0 — on the hour. Hour is */6 — every 6 hours (0, 6, 12, 18). Day, month, and day-of-week are all wildcards. So: at midnight, 6am, noon, and 6pm, every day.

Take 30 8 * * 1. Minute 30, hour 8, day-of-month wildcard, month wildcard, day-of-week 1 (Monday). So: 8:30am every Monday.

I built the DevCrate cron builder because I found myself testing expressions by deploying jobs and watching logs — which is a slow, painful way to verify a schedule. The builder lets you paste any expression and instantly see the next several run times in plain English, without deploying anything. It also works the other way: pick a schedule from dropdowns and it generates the expression for you.

It runs entirely in your browser, so there's nothing to sign up for and no data is sent anywhere.

Build and verify cron expressions instantly

Paste an expression to see the next run times in plain English, or use the visual builder to generate one from scratch. Free, private, no login required.

Open Cron Builder →
← All posts A developer's guide to JWTs →