How to Read a Cron Expression
The complete cron reference — five-field syntax, operators, special strings, common patterns, timezone conversion for IST servers, and the mistakes that silently break scheduled jobs.
TL;DR — Key Points
What Is Cron?
Cron is a time-based job scheduler built into Unix and Linux operating systems. It runs as a background daemon and executes commands at specified intervals — from once a minute to once a year. The name comes from the Greek word for time, chronos. Every web server, database server, CI/CD pipeline, and cloud function scheduler you interact with either uses cron expressions directly or implements a scheduling format derived from cron.
Cron expressions appear in Linux crontabs, AWS EventBridge Scheduler, Google Cloud Scheduler, GitHub Actions workflows, Kubernetes CronJobs, Laravel task scheduling, Celery Beat, Sidekiq Scheduler, and virtually every modern application framework that needs scheduled tasks. Understanding cron expressions is a foundational skill for anyone who builds or maintains backend systems.
The cron expression format consists of five space-separated fields representing: minute, hour, day of month, month, and day of week. Reading left to right, you can describe any recurring schedule. 0 9 * * 1-5 means "at minute 0, hour 9, any day of month, any month, Monday through Friday" — which translates to every weekday at 9:00 AM.
The asterisk (*) is the wildcard meaning "every value." The expression * * * * * means every minute of every hour of every day — the most frequent valid cron schedule. Most production jobs should not run this frequently; use */5 or */15 for polling tasks and reserve minute-by-minute execution for genuinely time-critical work.
The Five Fields: Complete Reference
Each field has a defined range and set of special characters it supports:
| Field | Position | Valid Range | Operators | Example |
|---|---|---|---|---|
| Minute | 1st | 0–59 | * / - , | 30 = at minute 30; */15 = every 15 min |
| Hour | 2nd | 0–23 | * / - , | 9 = 9 AM; 0 = midnight; 13 = 1 PM |
| Day of Month | 3rd | 1–31 | * / - , ? | 1 = 1st of month; 15 = 15th; L = last day |
| Month | 4th | 1–12 | * / - , | 1 = January; 12 = December; 6 = June |
| Day of Week | 5th | 0–7 (0,7=Sun) | * / - , ? | 1 = Monday; 5 = Friday; 1-5 = Mon–Fri |
The four special operators work as follows:
* (asterisk)
Every value. '* in hours' means every hour (0–23).
/ (slash)
Step. '*/5 in minutes' means every 5 minutes: 0, 5, 10, 15...
- (hyphen)
Range. '1-5 in weekday' means Monday through Friday.
, (comma)
List. '1,15 in day-of-month' means the 1st and the 15th.
Common Cron Expressions Reference
A comprehensive reference of the most commonly used cron expressions in production systems:
| Expression | Meaning | Common Use Case |
|---|---|---|
| * * * * * | Every minute | Health checks, polling (use sparingly) |
| */5 * * * * | Every 5 minutes | Metrics collection, cache warming |
| */15 * * * * | Every 15 minutes | Sync jobs, lightweight data pulls |
| 0 * * * * | Every hour at minute 0 | Hourly reports, cleanup tasks |
| 0 */2 * * * | Every 2 hours | Semi-regular background jobs |
| 0 9 * * * | Every day at 9:00 AM | Daily email digests, morning reports |
| 0 9 * * 1-5 | Weekdays at 9:00 AM | Business day reports, work notifications |
| 0 0 * * * | Every day at midnight | Daily DB backups, log rotation |
| 0 0 * * 0 | Every Sunday at midnight | Weekly cleanup, weekly digests |
| 0 0 1 * * | 1st of every month at midnight | Monthly billing, monthly reports |
| 0 0 1 1 * | January 1st at midnight | Yearly archiving, annual reset tasks |
| 30 9 * * 1 | Every Monday at 9:30 AM | Weekly standup reminders, sprint starts |
| 0 18 * * 5 | Every Friday at 6:00 PM | End-of-week reports, weekly digests |
| 0 2 * * * | Every day at 2:00 AM | Maintenance windows, heavy DB jobs |
| 0 9,17 * * * | Every day at 9 AM and 5 PM | Twice-daily reports, shift summaries |
Special Strings (@daily, @weekly, etc.)
Many cron implementations support special string shortcuts that are easier to read than numeric expressions. These are not part of the POSIX standard but are supported by GNU crontab, Vixie cron, and most Linux distributions:
| String | Equivalent Expression | Meaning |
|---|---|---|
| @yearly (or @annually) | 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 (or @midnight) | 0 0 * * * | Once a day at midnight |
| @hourly | 0 * * * * | Once an hour at minute 0 |
| @reboot | N/A | Runs once at system startup (not POSIX standard) |
Note: Special strings are not universally supported — GitHub Actions cron, AWS EventBridge, and Kubernetes CronJobs require standard 5-field expressions. Always use the numeric equivalent when writing cross-platform cron schedules.
Timezone Conversion for Cron (IST and Other Zones)
This is the most common source of cron bugs for Indian developers. Cloud servers (AWS EC2, GCP Compute Engine, DigitalOcean Droplets) default to UTC. If you write a cron expression for "9 AM" assuming IST, it will run at 9 AM UTC — which is 2:30 PM IST, not 9 AM IST.
The solution is to convert your target local time to UTC before writing the expression. India Standard Time (IST) is UTC+5:30. To run a job at 9:00 AM IST, subtract 5 hours 30 minutes: 9:00 AM − 5:30 = 3:30 AM UTC. The cron expression is 30 3 * * *.
| Target Local Time | UTC Cron Expression | Explanation |
|---|---|---|
| 9:00 AM IST | 30 3 * * * | IST = UTC+5:30, so 9:00 AM IST = 3:30 AM UTC |
| 6:00 PM IST | 30 12 * * * | 6:00 PM IST = 12:30 PM UTC |
| Midnight IST | 30 18 * * * | Midnight IST (00:00 IST) = 6:30 PM UTC previous day |
| 9:00 AM EST | 0 14 * * * | EST = UTC-5, so 9:00 AM EST = 2:00 PM UTC |
| 9:00 AM GMT | 0 9 * * * | GMT = UTC+0, same as UTC |
| 9:00 AM SGT | 0 1 * * * | SGT = UTC+8, so 9:00 AM SGT = 1:00 AM UTC |
Some modern schedulers (AWS EventBridge Scheduler, GCP Cloud Scheduler) allow specifying a timezone directly in the schedule configuration. When available, always use this instead of manually converting to UTC — it makes your intent clear and handles DST automatically for regions that observe it.
Common Cron Mistakes and How to Fix Them
These are the errors that cause cron jobs to silently run at the wrong time or not run at all:
⚠ Forgetting server runs in UTC
0 9 * * * → runs at 9 AM UTC, which is 2:30 PM IST, not 9 AM IST
✓ Fix: Adjust hour for server timezone. For 9 AM IST on a UTC server: 30 3 * * * (3:30 AM UTC = 9:00 AM IST)
⚠ Using both day-of-month and day-of-week
0 9 15 * 1 → some cron implementations run on the 15th OR Monday, not the 15th AND Monday
✓ Fix: Use one or the other, not both. Check your cron implementation's behaviour.
⚠ Sunday is both 0 and 7
0 9 * * 7 → should work on most systems, but 0 9 * * 8 → invalid
✓ Fix: Use 0 for Sunday consistently. Some systems accept 7 but 0 is safer and more portable.
⚠ Step values starting at wrong point
*/2 in hours field: runs at 0, 2, 4, 6... not at 1, 3, 5...
✓ Fix: Understand that */2 means every Nth value starting from the minimum (0). To start at 1: use 1-23/2.
⚠ Month numbers start at 1, not 0
0 0 1 0 * → invalid, month 0 does not exist
✓ Fix: Months are 1–12. January = 1, December = 12. Day of week is 0–7. Only months start at 1.
⚠ No space between fields
0 9** * → syntax error
✓ Fix: Five fields separated by single spaces: 0 9 * * *
How to Write the Right Expression
Apply the following approach for common scheduling requirements:
You want to run a job every N minutes (e.g. every 10 minutes)
→ Use step syntax: */10 * * * *. This runs at 0, 10, 20, 30, 40, 50 minutes past every hour. Note: it cannot start at an arbitrary minute like 3, 13, 23 — use a list for that: 3,13,23,33,43,53 * * * *.
You want to run a job on weekdays only during business hours
→ Combine day-of-week range and hour range: 0 9-17 * * 1-5. This runs every hour from 9 AM to 5 PM, Monday through Friday only.
You want a job to run at 9 AM India time on a UTC server
→ Convert IST to UTC: IST is UTC+5:30, so 9:00 AM IST = 3:30 AM UTC. Expression: 30 3 * * *. If the server observes DST for other regions, always verify the UTC equivalent.
You want to run a job on the last day of every month
→ Standard cron has no direct 'last day' syntax except in extended cron implementations (like Quartz which uses L). In standard cron: use a script that checks if tomorrow is the 1st of the next month. Or use 0 0 28-31 * * with a script that validates the actual last day.
You want to run a job every 2 hours starting at 1 AM, not midnight
→ Step values always start from the minimum (0 for hours). Use a list instead: 0 1,3,5,7,9,11,13,15,17,19,21,23 * * *. Or use an offset step: 1-23/2 in the hour field.
You are not sure if your cron expression is correct before deploying
→ Use the Cron Expression Parser tool to visualise the next 10 execution times. Never deploy a cron job without verifying the next runs. A typo in the expression can cause jobs to run every minute or never run.
Real-World Cron Schedule Examples
Common production scheduling requirements with their correct cron expressions:
| Requirement | Expression | Note |
|---|---|---|
| Send daily email digest at 8 AM IST | 30 2 * * * | 3:30 AM UTC = 8 AM IST on UTC server |
| Run DB backup every night at 2 AM | 0 2 * * * | 2 AM server time — confirm server timezone |
| Generate weekly report every Monday 9 AM | 0 9 * * 1 | 9 AM server time on Mondays |
| Clear cache every 30 minutes | */30 * * * * | Runs at :00 and :30 every hour |
| Run billing on 1st of month at midnight | 0 0 1 * * | Midnight server time, 1st of every month |
| Health check every 5 minutes on weekdays | */5 * * * 1-5 | Monday–Friday only |
| Archive logs at 11:59 PM every night | 59 23 * * * | One minute before midnight |
| Sync data at 9 AM and 6 PM IST on UTC server | 30 3,30 12 * * * | 9 AM IST = 3:30 UTC, 6 PM IST = 12:30 UTC |
Frequently Asked Questions
What is the difference between cron and crontab?
Cron is the daemon (background service) that reads schedules and executes commands at the specified times. Crontab (cron table) is the configuration file that stores the schedules. You edit crontab files using 'crontab -e' to schedule jobs for the current user. The system crontab at /etc/crontab has an additional field for the user to run as. Most cloud platforms (AWS EventBridge, GitHub Actions, GCP Cloud Scheduler) accept cron expressions directly in their configuration, without using the system crontab.
Does cron run in UTC or local time?
Cron runs in the timezone configured for the operating system (the TZ environment variable, or the system timezone set via timedatectl). On most cloud servers and containers, this is UTC by default. If you are on a server configured to UTC and want a job at 9 AM IST, you must specify 3:30 AM UTC in your cron expression. Always verify your server's timezone with 'date' or 'timedatectl status' before writing cron schedules.
What happens if a cron job is still running when the next scheduled time arrives?
Standard cron does not wait — it starts a new instance of the job at the scheduled time regardless of whether the previous run has finished. If the job takes longer than its interval, you can end up with multiple concurrent instances. This is a common source of resource exhaustion and data corruption. Solutions: use a lock file (flock), use a job scheduler that supports concurrency control (like Sidekiq or Celery Beat), or use 'run-one' wrapper on Linux systems.
Can I specify seconds in a cron expression?
Standard Unix/Linux cron does not support seconds — the minimum granularity is 1 minute. If you need sub-minute scheduling, use Quartz Cron (Java, supports 6 or 7 fields including seconds), Spring Scheduler (@Scheduled annotation), or platform-specific schedulers. AWS EventBridge Scheduler supports rate expressions like 'rate(30 seconds)' for sub-minute scheduling. For most use cases, 1-minute granularity is sufficient.
What does */5 actually mean in a cron field?
*/5 means 'every 5th value starting from the minimum'. In the minute field (0–59), */5 means 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 — 12 times per hour. In the hour field (0–23), */5 means 0, 5, 10, 15, 20 — 5 times per day. The starting point is always the minimum of the field's range, not an arbitrary offset. If you want to start at an offset (e.g. every 5 minutes starting at minute 2: 2, 7, 12...), you need to use a list or a range with step: 2-59/5.
How do I run a cron job on the last day of the month?
Standard cron has no built-in 'last day of month' operator except in extended implementations. In Quartz Cron, use 'L' in the day-of-month field. In standard cron, the common workaround is to schedule for day 28-31 and include a shell check in the script: [ "$(date -d tomorrow +%d)" == "01" ] && your_command. This checks whether tomorrow is the 1st of the month, effectively running only on the last day. AWS EventBridge Scheduler also has no native last-day support — the same script workaround applies.
Why does my cron job run twice on the day DST ends?
When clocks fall back (DST ending), the same local hour occurs twice. If your cron is scheduled at 1:30 AM local time and DST ends at 2:00 AM (clocks revert to 1:00 AM), the 1:30 AM timeslot occurs twice — and cron will execute twice. The fix is to use UTC on your server, which has no DST transitions. If you cannot use UTC, be aware of this behaviour for jobs scheduled during the DST transition window (1:00–2:00 AM typically) and handle idempotency in your job logic.
What is the difference between cron on Linux and AWS EventBridge cron?
Linux cron uses the standard 5-field syntax (minute, hour, day-of-month, month, day-of-week). AWS EventBridge Scheduler uses a 6-field cron expression with an additional year field at the end: cron(minute hour day-of-month month day-of-week year). EventBridge also supports 'L' for last day, 'W' for nearest weekday, and '?' for unspecified. EventBridge schedules are always in UTC unless you configure a timezone. GitHub Actions cron syntax follows standard 5-field POSIX cron.
Related Concepts
Related Tools
Cron Expression Parser
Parse any cron expression and see the next 10 scheduled execution times with human-readable descriptions.
Timestamp to Date Converter
Convert cron job log timestamps to readable dates — useful for debugging past runs.
Time Zone Converter
Convert cron scheduled times between UTC and any local timezone including IST.
World Clock
Check what time it is in UTC and multiple cities simultaneously when planning cron schedules.