What Is a Cron Expression
A cron expression is a string of five or six fields that defines a recurring schedule. The name comes from the Greek word "chronos" (time), and the format has been a standard part of Unix and Linux systems since the 1970s. When you see a cron expression like 0 9 * * 1-5, it is telling the system to run a task at 9:00 AM, every day, Monday through Friday. Understanding how to read and write these expressions unlocks one of the most reliable automation tools available to developers, system administrators, and DevOps engineers.
Cron is used everywhere: Linux servers run cron daemons that execute scheduled scripts, cloud platforms like AWS EventBridge and Google Cloud Scheduler accept cron expressions for triggering functions, CI/CD pipelines use them for nightly builds, and frameworks like Node.js node-cron and Python APScheduler rely on them for in-app scheduling. Even Kubernetes CronJobs use the same format. Despite minor variations between implementations, the core syntax is universal, which means learning it once pays off across every platform you work with.
The challenge is that cron syntax is dense. A single expression like */15 8-17 * * 1-5 packs a lot of meaning into a few characters: every 15 minutes, between 8 AM and 5 PM, on weekdays. Without a reference, these expressions are hard to read and even harder to debug when something goes wrong. This guide walks through each field systematically, provides a library of common patterns, and shows you how to use a cron expression builder to generate and validate schedules without guessing.
The Five Fields Explained
A standard cron expression has five fields separated by spaces. Reading left to right, they represent: minute, hour, day of month, month, day of week. Each field accepts specific values and special characters that determine when the task runs.
Field 1: Minute (0-59). Controls the minute of the hour when the task fires. Use a single number (5 for minute 5), a range (10-20 for minutes 10 through 20), a list (0,15,30,45 for every quarter hour), or a step (*/10 for every 10 minutes). An asterisk (*) means "every minute." This is the most granular control you have in cron — there is no seconds field in standard cron.
Field 2: Hour (0-23). Controls the hour using a 24-hour clock. 0 is midnight, 12 is noon, 23 is 11 PM. Combine with the minute field to pin an exact time: 30 14 * * * fires at 2:30 PM daily. A range like 9-17 means "between 9 AM and 5 PM," and when combined with a minute step, you can create patterns like "every 30 minutes during business hours" — */30 9-17 * * *.
Field 3: Day of Month (1-31). Specifies which day of the month the task runs. Use with caution — not every month has 31 days. If you specify 31, the task will only run in months that have 31 days (January, March, May, July, August, October, December). For "last day of month" semantics, some cron implementations support the special character L, but standard Linux cron does not. A common workaround is to schedule for day 28 and let your script check if it is actually the last day.
Field 4: Month (1-12). Controls the month. You can use numbers (1 for January, 12 for December) or three-letter abbreviations in most implementations (JAN, FEB, MAR). A pattern like * * 1 1,7 * runs on the first day of January and July. This field is often left as * (every month) unless you have seasonal or quarterly tasks.
Field 5: Day of Week (0-7). Controls the day of the week. Both 0 and 7 represent Sunday. Monday is 1, Friday is 5, Saturday is 6. Use a range like 1-5 for weekdays or 0,6 for weekends. Some implementations allow three-letter abbreviations (MON, TUE). When both the day-of-month and day-of-week fields are specified (neither is *), the task runs when either condition is met — they are ORed, not ANDed. This is a common source of confusion.
Special Characters and Syntax
Beyond plain numbers, cron expressions support several special characters that give you fine-grained control over scheduling patterns.
Asterisk (*) means "every possible value." In the minute field, * means every minute. In the hour field, * means every hour. An expression of * * * * * runs every minute of every hour of every day — useful for testing, dangerous in production.
Comma (,) creates a list of specific values. 0,15,30,45 in the minute field means "at minutes 0, 15, 30, and 45." Use commas when you need to match specific values that do not follow a regular pattern — for example, 0 9,12,18 * * * runs at 9 AM, noon, and 6 PM.
Hyphen (-) defines a range. 1-5 in the day-of-week field means Monday through Friday. 9-17 in the hour field means 9 AM through 5 PM. Ranges are inclusive — both endpoints are included.
Forward slash (/) defines a step interval. It is written as start/step. */5 in the minute field means "every 5 minutes starting from 0" (0, 5, 10, 15, ...). 10/5 means "every 5 minutes starting from 10" (10, 15, 20, 25, ...). This is especially useful for health checks, polling, and periodic cleanup tasks.
Combining these characters creates powerful patterns. 0 9-17 * * 1-5 means "at the top of every hour, 9 AM to 5 PM, Monday through Friday." */10 * * * 6,0 means "every 10 minutes, but only on weekends." 0 0 1 1,4,7,10 * means "midnight on the first day of January, April, July, and October" — quarterly reports.
Some cron implementations also support shorthand aliases: @yearly (or @annually) equals 0 0 1 1 *, @monthly equals 0 0 1 * *, @weekly equals 0 0 * * 0, @daily equals 0 0 * * *, @hourly equals 0 * * * *, and @reboot runs once at system startup. These aliases are convenient for common schedules and are supported by most modern cron daemons.
Common Scheduling Patterns
Here are the most frequently used cron patterns, with explanations and real-world use cases for each.
Every minute: * * * * * — Testing and monitoring. Avoid in production unless you genuinely need minute-level granularity, as it creates significant load.
Every 5 minutes: */5 * * * * — Health checks, API polling, and cache warming. A common pattern for microservices that need to report status or refresh tokens periodically.
Every hour at minute 0: 0 * * * * — Hourly batch processing, log rotation, and metric aggregation. Many monitoring dashboards update on this schedule.
Every day at midnight: 0 0 * * * — Daily database backups, report generation, data cleanup, and email digests. The most common cron schedule in production environments.
Every weekday at 9 AM: 0 9 * * 1-5 — Morning standup reminders, daily status reports, market open notifications. The 1-5 range excludes weekends.
Every Monday at 8 AM: 0 8 * * 1 — Weekly team summaries, sprint planning reminders, and newsletter generation.
First day of every month: 0 0 1 * * — Monthly billing runs, subscription renewals, payroll processing, and compliance reports.
Every Sunday at 2 AM: 0 2 * * 0 — Weekly full database backups, log archival, and maintenance windows. Sunday 2 AM is a traditional low-traffic window.
Every 15 minutes during business hours: */15 9-17 * * 1-5 — Intraday stock price snapshots, queue monitoring, and customer notification batching.
Quarterly on the first day: 0 0 1 1,4,7,10 * — Tax filing reminders, quarterly business reviews, and compliance audits.
Debugging Cron Schedules
Cron issues are notoriously difficult to diagnose because they happen in the background, often on remote servers, and the error evidence is usually a log line buried in a syslog file. Here are the most common problems and how to resolve them.
The task does not run at all. Check that the cron daemon is running (systemctl status cron or service cron status). Verify that the crontab is loaded (crontab -l). Ensure the command path is absolute — cron runs with a minimal PATH environment, so a command like node script.js may fail because cron cannot find the node binary. Use the full path: /usr/bin/node /home/user/script.js. Also check file permissions — the script must be executable.
The task runs at the wrong time. Cron uses the server timezone by default. If your server is in UTC but you wrote the schedule thinking in South African Standard Time (SAST, UTC+2), your 9 AM job will fire at 11 AM SAST. Either set the CRON_TZ environment variable in your crontab (CRON_TZ=Africa/Johannesburg) or adjust the hour values to account for the offset. Cloud platforms like AWS and GCP typically use UTC, so this timezone mismatch is one of the most common cron bugs.
The task runs twice or not at all on daylight saving transitions. South Africa does not observe daylight saving time, but if your servers are in regions that do (Europe, North America), cron schedules can fire twice or skip entirely during the transition. The best mitigation is to schedule critical tasks outside the 1 AM to 3 AM window when transitions typically occur, or to use UTC consistently.
Environment variables are missing. Cron runs with a minimal environment — it does not load your shell profile (.bashrc, .zshrc). If your script depends on environment variables (database URLs, API keys, PATH entries), define them at the top of the crontab file or source a dedicated environment file in the cron command itself.
Logging cron output. By default, cron silently discards stdout and emails stderr to the crontab owner. Redirect both to a log file for reliable debugging: 0 * * * * /path/to/script.sh >> /var/log/myscript.log 2>&1. For cloud-based cron (AWS EventBridge, Cloud Scheduler), check the platform-specific logging — CloudWatch for AWS, Cloud Logging for GCP.
Using a Cron Expression Builder
Writing cron expressions by hand is error-prone, especially for complex schedules that combine ranges, steps, and multiple field constraints. A cron expression builder eliminates the guesswork by providing a visual interface where you select your schedule and the tool generates the correct expression.
A typical builder lets you specify: the frequency (every minute, every N minutes, hourly, daily, weekly, monthly, or custom), the specific times and days, and any exceptions or constraints. As you configure each option, the builder updates the expression in real time and, more importantly, displays a human-readable description of what the expression means — for example, "At minute 0 past every hour from 9 through 17 on every day-of-week from Monday through Friday." This description is invaluable for verifying that your schedule matches your intent before you deploy it.
The builder also handles edge cases that are easy to get wrong manually. For instance, if you want a task to run on the last Friday of every month, standard cron cannot express this directly. A builder will either generate the closest standard cron equivalent or warn you that the schedule requires a non-standard extension. Similarly, if you specify day-of-month 31, the builder will remind you that the task will not run in months with fewer days.
For developers working with multiple platforms, the builder can also flag platform-specific differences. AWS EventBridge uses a six-field cron format with an additional year field, and it requires expressions to be wrapped differently than standard Linux cron. Kubernetes CronJobs add their own validation rules. A good builder accounts for these variations and generates the correct format for your target platform.
The workflow is simple: open the builder, configure your schedule visually, read the human-readable description to verify, copy the generated expression, and paste it into your crontab, CI/CD config, or cloud scheduler. The entire process takes seconds instead of the minutes (or hours) you might spend debugging a hand-written expression that does not behave as expected.
Cron in Modern Development Workflows
Cron expressions have evolved beyond the traditional Linux cron daemon. Modern development platforms have adopted and extended the format, making it relevant to a wider range of developers than ever before.
CI/CD pipelines use cron expressions for scheduled builds and deployments. GitHub Actions supports the schedule trigger with cron syntax: on: schedule: cron: "0 2 * * *" runs your workflow at 2 AM UTC daily. GitLab CI/CD pipelines support the same format for scheduled pipeline runs. These scheduled pipelines are commonly used for nightly test suites, dependency updates (like Dependabot), and staging environment refreshes.
Serverless functions are increasingly triggered on cron schedules. AWS Lambda functions can be invoked on a schedule via EventBridge rules that accept cron expressions. Google Cloud Functions and Azure Functions offer similar capabilities. This pattern replaces the traditional approach of running a cron daemon on a persistent server — instead, the cloud platform handles the scheduling and your function executes only when needed, reducing costs.
Application-level scheduling uses cron expressions within your codebase. Libraries like node-cron (Node.js), APScheduler (Python), Sidekiq-Cron (Ruby), and Quartz.NET (C#) all accept cron expressions for defining recurring jobs within your application. This is useful for tasks that need access to your application context — sending scheduled emails, generating reports, cleaning up expired sessions, or synchronising data with external systems.
Kubernetes CronJobs manage containerised workloads on a schedule. A Kubernetes CronJob creates a Job resource on the schedule defined by the cron expression, ensuring that your scheduled tasks run in a clean, reproducible container environment with built-in retry logic and concurrency controls. The CronJob spec also adds useful fields like startingDeadlineSeconds (how late a missed run can be executed) and concurrencyPolicy (whether overlapping runs are allowed).
Understanding cron expressions is a foundational skill that transfers across all these platforms. The syntax you learn for a Linux crontab is the same syntax used by GitHub Actions, AWS EventBridge, and Kubernetes CronJobs. Investing the time to master it pays dividends across your entire development career.