Cron Expressions:
A Complete Scheduling Guide
Cron powers scheduled tasks across the entire tech stack. From the five-field syntax to DST pitfalls — everything you need to write schedules that fire exactly when expected, every time.
What Cron Expressions Do
A cron expression is a string of five (or sometimes six) fields that defines when a scheduled task should run. It was invented for Unix's cron daemon in Version 7 Unix (1979) by Brian Kernighan, but the concept has far outlived its origin. Today, cron expressions schedule jobs in GitHub Actions workflows, Kubernetes CronJobs, AWS CloudWatch Events and EventBridge rules, Google Cloud Scheduler, Vercel cron jobs, and virtually every CI/CD platform. The syntax is one of those rare standards that escaped its original context and became infrastructure.
The power of cron expressions is their precision combined with their simplicity. A single string defines an exact repeating schedule. No GUI calendar picker, no YAML schedule block, no programming — just five fields that describe when a job fires. Master them once, and you can schedule tasks on any platform that supports cron syntax.
The Five Fields
minute hour day-of-month month day-of-week
0-59 0-23 1-31 1-12 0-6 (0=Sunday, 7=Sunday on some systems)
Every field accepts a number, a wildcard, or combinations using special characters. Missing or extra fields are the most common cron mistake — copying a six-field expression (which adds seconds as the first field, used by Quartz Scheduler and some Java frameworks) into a five-field system silently shifts every field left, producing a completely different schedule. Always verify how many fields your platform expects.
Special Characters
Asterisk (*) — Match Every Value
* in the hour field means every hour. * in the day-of-week field means every day. The expression * * * * * means every minute — it fires 1,440 times per day. This is almost never what you intended. It's the default in example code because it's the simplest expression to demonstrate, but it's dangerous in production. A resource-intensive job running every minute can overwhelm a server.
Step Values (/) — Every Nth Interval
*/5 in the minute field means every 5 minutes — it produces 0, 5, 10, 15 ... 55. The step starts from the field's minimum value (0 for minutes and hours, 1 for day-of-month and month, 0 for day-of-week). To start from a different value, specify the starting point: 3/5 in minutes produces 3, 8, 13, 18 ... 58. This is useful for staggering jobs — if you have three similar jobs that shouldn't all run at the same time, schedule them at 0/5, 2/5, and 4/5.
Ranges (-) — From X Through Y
1-5 in day-of-week means Monday through Friday. 9-17 in hours means 9 AM to 5 PM inclusive (both endpoints fire). Ranges combine with steps: 9-17/2 means every 2 hours from 9 to 17: 9, 11, 13, 15, 17. No spaces around the hyphen.
Lists (,) — Specific Values
1,15 in day-of-month means the 1st and 15th only. 0,30 in minutes means at :00 and :30 past each hour. 1-5,10-15 is valid — it means 1 through 5 and 10 through 15. Commas have the lowest precedence; everything else in the field is evaluated before the comma splits.
Common Patterns
| Expression | Schedule | When You'd Use It |
|---|---|---|
*/5 * * * * | Every 5 minutes | Health checks, metrics collection |
0 * * * * | Hourly at :00 | Cache warming, log rotation |
0 9 * * 1-5 | 9 AM weekdays | Daily report emails |
0 0 1 * * | Midnight, 1st of month | Monthly billing, report generation |
30 2 * * 0 | 2:30 AM Sundays | Weekly database maintenance |
0 0 * * 0 | Midnight every Sunday | Weekly backups |
Production Gotchas
Day-of-month AND day-of-week interaction. When both fields have specific values (not *), the job fires when EITHER condition matches — it's an OR, not an AND. 0 0 1 * 0 means midnight on the 1st of the month AND every Sunday. This surprises developers who expect AND semantics ("the first of the month only if it's a Sunday"). To get AND behavior, you need logic within the script itself: check the date inside your job and exit early if conditions aren't met.
Daylight Saving Time transitions. When clocks spring forward, jobs scheduled during the skipped hour (usually 2:00-3:00 AM) simply don't run that day — those times don't exist. When clocks fall back, the 1:00 AM hour happens twice, and jobs fire twice. For critical jobs, schedule outside the 1:00-3:00 AM danger zone, run on UTC to avoid DST entirely, or add idempotency checks so double execution doesn't cause problems. Most cron implementations handle DST differently — test your specific platform.
Server timezone assumptions. Cron uses the server's local timezone unless configured otherwise. A server provisioned in UTC-5 runs 0 9 * * * at 9 AM Eastern, while an identical server in UTC runs it at 9 AM UTC (4 AM Eastern). Containerized environments may default to UTC. Always verify the timezone your cron daemon or scheduler uses. Cloud providers (AWS, GCP) typically use UTC for scheduled events.
Testing Cron Expressions
Never deploy a cron expression without verifying what it actually does. Use our Cron Expression Parser to see the next five execution times for any expression. This catches the "every minute instead of every hour" mistake and the "firing on weekends when I meant weekdays" mistake before they reach production. For complex scheduling logic, write a test that asserts the job fires at the expected times for a range of dates — including DST transition dates.
Quick Reference
| Schedule | Expression |
|---|---|
| Every 5 minutes | */5 * * * * |
| Hourly | 0 * * * * |
| Weekdays 9 AM | 0 9 * * 1-5 |
| Midnight 1st | 0 0 1 * * |
Testing Cron Before Deployment
Before deploying, verify your expression shows the expected execution times. Use our Cron Expression Parser. For production cron debugging, check execution logs — most cron daemons log to syslog. When migrating between platforms, verify field count: standard Unix uses 5 fields; Quartz (Java) uses 6 or 7. Never assume a cron expression from one platform works unchanged on another.
Cron in Modern Infrastructure
While traditional cron runs on a single server, modern infrastructure distributes scheduled jobs across clusters. Kubernetes CronJobs create Jobs on a schedule, with configurable concurrency policies (Forbid prevents overlap, Replace kills the old job, Allow runs them concurrently). Vercel Cron Jobs run serverless functions on a schedule, with a maximum duration of 10-900 seconds depending on your plan. GitHub Actions scheduled workflows use cron syntax in the on.schedule field, but they run with a 5-minute granularity minimum and may be delayed during high-load periods. Each platform has its own quirks — test your schedule on the actual platform before relying on it.
Monitoring scheduled jobs requires different thinking than monitoring always-on services. A cron job that silently fails leaves no symptom except the work that did not get done — emails that were not sent, backups that were not created, reports that were not generated. Set up heartbeat monitoring: each successful job execution pings a monitoring endpoint. If the heartbeat stops, alert. This catches both "the job ran and failed" and "the job never ran at all" — two failure modes that cron exit codes do not distinguish.