Cron Expression Guide: Syntax, Examples, and Common Patterns
Since its creation in the 1970s for the Unix operating system, cron has remained the undisputed standard for scheduling recurring tasks. Despite its age, the cryptic strings of asterisks and numbers that make up a cron expression appear everywhere in modern infrastructure — from server maintenance shell scripts and database backups, to AWS Lambda triggers, GitHub Actions, and Kubernetes CronJobs.
Writing an incorrect cron expression doesn’t just result in a syntax error; it can cause serious financial damage. A misconfigured script meant to run “once a month” might accidentally run “every minute of every day for a month,” overwhelming your database and racking up thousands of dollars in cloud computing bills.
This comprehensive guide breaks down standard 5-field cron syntax, extends into 6-field and AWS variations, provides battle-tested examples, and highlights the dangerous edge cases (like Daylight Saving Time anomalies) that frequently cause production outages.
Stop guessing. Validate your schedules instantly with our Cron Expression Parser — it translates any cron expression into plain English and calculates the exact next 5 scheduled run times.
The Standard 5-Field Cron Format
Standard Linux cron (the version found in crontab on Ubuntu, Debian, macOS, etc.) uses exactly five fields, separated by spaces.
┌───────────── minute (0–59)
│ ┌───────────── hour (0–23)
│ │ ┌───────────── day of month (1–31)
│ │ │ ┌───────────── month (1–12)
│ │ │ │ ┌───────────── day of week (0–7, where Sunday = 0 or 7)
│ │ │ │ │
* * * * *
When the system clock matches the criteria defined in all five fields, the scheduled command executes.
Valid Field Values
Every field can contain one of the following structures:
| Structure | Syntax | Meaning | Example |
|---|---|---|---|
| Exact Number | n | Match exactly this value | 5 in the minute field means “exactly at 5 minutes past the hour” |
| Asterisk (Any) | * | Match every possible value | * in the hour field means “every hour” |
| Range | x-y | Match inclusive values between x and y | 9-17 in the hour field means “from 9 AM to 5 PM inclusive” |
| List (Comma) | x,y,z | Match a specific set of values | 1,15 in the day field means “on the 1st and 15th of the month” |
| Step (Slash) | */n | Match every nth value | */15 in the minute field means “every 15 minutes” |
Note: You can combine these! 0 8-17/2 * * 1-5 means “every 2 hours between 8 AM and 5 PM, Monday through Friday.”
Essential Cron Expression Examples
Here is a cheat sheet of the most frequently used cron expressions in production environments.
Minutes and Hours
| Expression | Plain English Translation | Use Case |
|---|---|---|
* * * * * | Every single minute | Health checks, high-frequency polling |
*/5 * * * * | Every 5 minutes | Cache clearing, queue processing |
0 * * * * | Every hour, exactly at the top of the hour (:00) | Hourly log rotation |
30 * * * * | Every hour, at 30 minutes past the hour | Staggered hourly jobs |
0 */6 * * * | Every 6 hours (00:00, 06:00, 12:00, 18:00) | Mid-day data synchronization |
Daily and Weekly
| Expression | Plain English Translation | Use Case |
|---|---|---|
0 0 * * * | Every day at midnight exactly | Daily database backups |
0 8 * * * | Every day at 8:00 AM | Morning report generation |
30 18 * * 1-5 | Every weekday (Mon-Fri) at 6:30 PM | Shutting down staging servers |
0 2 * * 0 | Every Sunday at 2:00 AM | Weekly deep system scans |
Monthly and Yearly
| Expression | Plain English Translation | Use Case |
|---|---|---|
0 0 1 * * | Midnight on the first day of every month | Monthly billing cycles |
0 0 1,15 * * | Midnight on the 1st and 15th of the month | Bi-monthly payroll tasks |
0 0 1 1 * | Midnight on January 1st every year | Yearly SSL certificate checks |
Special Shorthand Strings (Macros)
To make crontab files more readable, standard Linux cron daemon implementations support special predefined string macros. Instead of writing five asterisks, you can write a single word starting with @.
| Macro | Equivalent Expression | Description |
|---|---|---|
@yearly | 0 0 1 1 * | Run once a year, on Jan 1 at midnight. |
@annually | 0 0 1 1 * | Identical to @yearly. |
@monthly | 0 0 1 * * | Run once a month, on the 1st at midnight. |
@weekly | 0 0 * * 0 | Run once a week, on Sunday at midnight. |
@daily | 0 0 * * * | Run once a day, at midnight. |
@hourly | 0 * * * * | Run once an hour, at the beginning of the hour. |
@reboot | (None) | Crucial: Run exactly once when the server boots up. Perfect for startup scripts. |
Warning: While these are standard in Linux crond, they are NOT universally supported in cloud platforms like AWS CloudWatch or GitHub Actions. When in doubt, use the explicit 5-field syntax.
The 6-Field Format (Seconds)
Standard cron does not support a “seconds” field. The finest granularity in Linux cron is 1 minute. However, many modern software frameworks (like Java’s Spring Framework, Quartz Scheduler, or Go’s robfig/cron) extended the syntax to 6 fields to allow sub-minute precision.
┌─────────────── second (0–59)
│ ┌───────────── minute (0–59)
│ │ ┌───────────── hour (0–23)
│ │ │ ┌───────────── day of month (1–31)
│ │ │ │ ┌───────────── month (1–12)
│ │ │ │ │ ┌───────────── day of week (0–7)
│ │ │ │ │ │
* * * * * *
If you see a 6-field cron expression (e.g., 0/30 * * * * *), it means it is a framework-specific implementation running “every 30 seconds.”
Platform Syntax Differences
Not all cron engines are created equal. When moving from a bare-metal Linux server to a managed cloud service, syntax differences can break your schedules.
AWS CloudWatch Events / EventBridge
AWS uses a highly modified 6-field format (adding a ‘Year’ field at the end, not a ‘Seconds’ field at the beginning): cron(minute hour day-of-month month day-of-week year).
AWS imposes strict rules:
- You cannot use
*in both the day-of-month and day-of-week fields simultaneously. One of them must be a?(question mark). - Supports advanced modifiers:
L(Last day of month),W(Nearest weekday). - Example:
cron(0 12 * * ? *)— Runs every day at noon UTC.
Kubernetes CronJobs
Kubernetes (K8s) uses the standard 5-field format in its manifest files.
- Before K8s 1.27, all CronJobs strictly ran in UTC based on the kube-controller-manager clock.
- In K8s 1.27+, you can explicitly declare timezones:
spec.timeZone: "America/New_York".
GitHub Actions
GitHub uses the standard 5-field format inside workflow YAML files: on: schedule: - cron: '0 0 * * *'.
- All jobs execute in UTC.
- Limitation: The minimum supported interval is 5 minutes. GitHub explicitly states that they may delay scheduled jobs during times of high platform load. Do not rely on GitHub Actions for mission-critical, exact-minute timing.
Avoiding Catastrophic Cron Mistakes
1. The “Every 5 Minutes” Typo
This is the most common mistake made by beginners:
# ❌ FATAL ERROR: This does NOT mean "every 5 minutes."
# It means "run exactly at the 5th minute of every hour" (1:05, 2:05, 3:05).
5 * * * *
# ✅ CORRECT: The slash indicates a step value (every 5th minute).
*/5 * * * *
2. The “Last Day of the Month” Problem
Standard Linux cron does not support the L character to find the last day of the month. Because months have 28, 29, 30, or 31 days, you cannot hardcode a number.
The Workaround: Run the job on days 28-31, but add bash logic to check if tomorrow is the 1st of the month before executing the real script.
0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /path/to/monthly-billing.sh
3. The Daylight Saving Time (DST) Anomaly
Cron relies entirely on the server’s system clock. If your server is configured to a local timezone that observes Daylight Saving Time (e.g., America/New_York), you are exposed to DST transition bugs.
- Spring Forward: At 2:00 AM, the clock jumps to 3:00 AM. If you have a cron job scheduled at
30 2 * * *(2:30 AM), it will never run that day. - Fall Back: At 3:00 AM, the clock jumps back to 2:00 AM. If you have a cron job scheduled at
30 2 * * *, it will run twice that day.
The Fix: Always run your servers in UTC (sudo timedatectl set-timezone UTC). UTC does not observe Daylight Saving Time, meaning every minute of the year happens exactly once. If you must run jobs at local times, use an application-level scheduler (like Celery or Agenda) rather than system cron.
Debugging Failed Cron Jobs
A core philosophy of cron is that it fails silently. It has no terminal attached to it, so if a script throws an error, you won’t see it unless you explicitly capture the output.
1. Always Redirect Output
Never deploy a cron job without capturing stdout and stderr to a log file:
# Append both normal output (1) and error output (2) to the log file
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup_job.log 2>&1
2. The Absolute Path Rule
When you log into a server, your .bashrc loads your $PATH. When cron runs, it runs in a highly restricted shell (usually /bin/sh) with a minimal $PATH.
A script that runs perfectly when you type ./backup.sh may fail in cron because cron cannot find the node, python, or aws executables.
The Fix: Always use absolute paths for both the script and the executables inside the script.
# ❌ BAD: cron might not find 'node'
0 * * * * node /var/www/script.js
# ✅ GOOD: Provide the absolute path to the executable
0 * * * * /usr/local/bin/node /var/www/script.js
Further Reading
- Timezone Handling for Developers: UTC and DST
- Environment Variables and Configuration Guide
- Linux File Permissions & chmod Guide
Never deploy a cron expression without testing it first. Use our completely free Cron Expression Parser to translate your syntax into human-readable text and verify the exact upcoming execution times.