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 (0–59)
│ ┌───────── hour (0–23)
│ │ ┌─────── day of month (1–31)
│ │ │ ┌───── month (1–12)
│ │ │ │ ┌─── day of week (0–7, 0 and 7 are both Sunday)
│ │ │ │ │
* * * * *
A helpful mnemonic: M H D M W — Minutes, Hours, Days, Months, Weekdays.
Field ranges and allowed values
| Field | Range | Special characters |
|---|---|---|
| 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 addsL(last) andW(weekday nearest to) special characters. -
Kubernetes CronJobs use standard five-field cron and support
@hourly,@daily,@weekly,@monthly, and@yearlyshortcuts. - 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 |
|---|---|---|
@yearly |
0 0 1 1 * |
Once a year at midnight on January 1st |
@monthly |
0 0 1 * * |
Once a month at midnight on the 1st |
@weekly |
0 0 * * 0 |
Once a week at midnight on Sunday |
@daily |
0 0 * * * |
Once a day at midnight |
@hourly |
0 * * * * |
Once an hour at the start of the hour |
@reboot |
— | Once 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.
Try it without deploying anything
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 tool 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 the visual builder and it generates the expression for you. Free, runs entirely in your browser, nothing to sign up for.
Top comments (0)