How-To cron scheduling linux devops

Cron Expressions Explained: Syntax, Examples, and Platform Differences

By ClickSolveTools Editorial TeamUpdated 12 min read

A cron expression is five fields separated by spaces (minute, hour, day of month, month, day of week) that tell a scheduler when to run a job. For example, 30 2 * * * runs a job at 2:30 AM every day, and */15 9-17 * * 1-5 runs it every 15 minutes from 9:00 AM through 5:45 PM on weekdays. An asterisk matches every value, a comma lists values, a hyphen makes a range and a slash sets a step.

This guide covers the standard five-field format, the extra characters used by Quartz, Spring and AWS, 15 expressions you can copy, how Linux, AWS EventBridge, GitHub Actions and Kubernetes differ, and the mistakes behind most failed jobs. To check any expression, paste it into our Cron Expression Parser, which shows a plain-English description and the next 10 run times in your browser's time zone.

The 5-Field Cron Format

A standard cron expression has five fields separated by spaces. This is the format read by the Linux crontab, GitHub Actions and Kubernetes:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ minute (0 to 59) โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ hour (0 to 23) โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ day of month (1 to 31) โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ month (1 to 12, or JAN to DEC) โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ day of week (0 to 7, or SUN to SAT; 0 and 7 are Sunday) โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ * * * * *

Each field takes a single value, a range, a list or a step. The job runs when the current time matches all five fields, with one exception for the two day fields that is covered under common mistakes below.

FieldAllowed valuesNotes
Minute0 to 59
Hour0 to 2324-hour clock, so 0 is midnight and 13 is 1 PM
Day of month1 to 31A job set for the 31st skips months with fewer days
Month1 to 12 or JAN to DECNames are not case sensitive
Day of week0 to 7 or SUN to SAT0 and 7 both mean Sunday

POSIX, the formal standard, is narrower than what Linux cron accepts. It defines only numbers, asterisks, lists and ranges, and its day of week runs from 0 to 6. Steps such as */5, month and day names, 7 for Sunday and the @daily style macros are extensions. Vixie-style crons such as cronie and Debian's cron support all of them, so this guide treats them as standard.

Special Characters Explained

CharacterNameMeaningExample
*WildcardEvery value in the field* * * * * runs every minute
,ListSeveral specific values0 8,12,18 * * * runs at 8 AM, noon and 6 PM
-RangeEvery value from one to another, inclusive0 9-17 * * * runs every hour from 9 AM to 5 PM
/StepEvery nth value of a range or of **/15 * * * * runs every 15 minutes; 0-30/10 * * * * runs at minutes 0, 10, 20 and 30

Extra characters in Quartz, Spring and AWS

Java schedulers and AWS add characters that Linux cron does not understand. Quartz and Spring also put a seconds field first, so the examples below have six fields. Quartz and AWS require a ? in one of the two day fields, and Spring allows it. AWS EventBridge has no seconds field and ends with a year field instead.

CharacterMeaningExample (seconds first)Result
?No specific value, used in day of month or day of week0 0 12 ? * MONNoon every Monday
LThe last day of the month, or the last given weekday0 0 0 L * ?Midnight on the last day of every month
WThe weekday nearest a given day of the month0 0 0 15W * ?Midnight on the weekday nearest the 15th
#The nth given weekday of the month0 0 0 ? * FRI#3Midnight on the third Friday of every month

These characters are not supported in Linux crontab, GitHub Actions or Kubernetes. Day-of-week numbers also differ: Quartz and AWS count from 1 (Sunday) to 7, so 5#3 there means the third Thursday. Day names such as FRI mean the same thing everywhere they are supported, so prefer them.

15 Common Cron Expressions (With Plain English)

#ExpressionPlain English
1* * * * *Every minute
2*/5 * * * *Every 5 minutes
30 * * * *Every hour, at minute 0
40 0 * * *Every day at midnight
50 9 * * *Every day at 9:00 AM
60 9 * * 1-5Every weekday at 9:00 AM
70 0 * * 0Every Sunday at midnight
80 0 1 * *The first day of every month at midnight
90 0 1 1 *January 1 at midnight (yearly)
1030 4 * * *Every day at 4:30 AM
110 */2 * * *Every 2 hours, on the hour (12 AM, 2 AM and so on)
120 9,18 * * *Twice a day, at 9:00 AM and 6:00 PM
13*/30 9-17 * * 1-5Every 30 minutes on weekdays, from 9:00 AM through 5:30 PM
140 0 1,15 * *The 1st and 15th of every month at midnight
150 3 * * 6Every Saturday at 3:00 AM

The last run of */30 9-17 * * 1-5 is 5:30 PM, because the hour range includes 17 and the step fires at minutes 0 and 30 of every hour in it. To stop at 5:00 PM, use */30 9-16 * * 1-5 plus a separate 0 17 * * 1-5. Paste any of these into the Cron Expression Parser to see the next 10 run times. It reads the standard five-field format, including names and steps, but not @ macros or the Quartz characters above.

Cron Across Different Systems

The core syntax is shared, but each platform has its own extras and its own default time zone.

Linux crontab

Edit your own table with crontab -e. Each line is the five time fields followed by the command:

# Run the backup script every day at 2:30 AM 30 2 * * * /home/user/scripts/backup.sh # Delete export files older than 7 days, every Sunday at midnight 0 0 * * 0 find /home/user/exports -type f -mtime +7 -delete # Health check every 5 minutes; -f makes curl fail on HTTP errors */5 * * * * curl -fsS https://example.com/health >> "$HOME/health.log" 2>&1

Files in /etc/cron.d/ and /etc/crontab add a sixth field before the command that names the user to run as. Vixie-style crons also accept these shorthand macros, which are not part of POSIX:

ShorthandEquivalent
@rebootRun once after the system starts
@yearly or @annually0 0 1 1 *
@monthly0 0 1 * *
@weekly0 0 * * 0
@daily or @midnight0 0 * * *
@hourly0 * * * *

GitHub Actions does not support these macros. Kubernetes CronJobs accept all of them except @reboot.

AWS EventBridge

AWS uses six fields: minutes, hours, day of month, month, day of week and year. The expression sits inside cron(), days of week are numbered 1 (Sunday) to 7, and one of the two day fields must be ? because you cannot give a value or * to both. EventBridge scheduled rules always run in UTC. AWS recommends the newer EventBridge Scheduler for new work, and it lets you pick a time zone. Both also accept rate() expressions.

# Minutes Hours Day-of-month Month Day-of-week Year cron(0 9 ? * MON-FRI *) # Weekdays at 9:00 AM UTC cron(0/15 * * * ? *) # Every 15 minutes cron(0 0 1 * ? *) # First of every month at midnight UTC # Rate expressions rate(5 minutes) rate(1 hour) rate(7 days)

GitHub Actions

GitHub Actions reads five-field POSIX-style cron in the schedule trigger. Day of week runs from 0 to 6 (or SUN to SAT), and @ macros are not supported. Times are UTC unless you add a timezone key with an IANA name such as America/New_York, an option GitHub added in March 2026.

on: schedule: - cron: '0 9 * * 1-5' # Weekdays at 9:00 AM UTC - cron: '30 5 * * 1-5' # Weekdays at 5:30 AM New York time timezone: 'America/New_York'

The shortest interval is once every 5 minutes. Scheduled workflows run only on the default branch, and in a public repository GitHub disables them after 60 days without repository activity. Runs can start late when GitHub is busy, especially at the top of the hour, so avoid minute 0 for jobs that are not time critical.

Kubernetes CronJob

A Kubernetes CronJob takes a standard five-field schedule. Without a timeZone, the schedule follows the local time zone of the kube-controller-manager. Setting .spec.timeZone to an IANA name fixes that, and the field has been stable since Kubernetes v1.27. Kubernetes rejects CRON_TZ or TZ written inside the schedule string. Validate your manifest with our YAML Formatter before you apply it.

apiVersion: batch/v1 kind: CronJob metadata: name: db-backup spec: schedule: "30 2 * * *" # Daily at 2:30 AM in the time zone below timeZone: "America/New_York" # Stable since Kubernetes v1.27 concurrencyPolicy: Forbid # Skip a run if the previous one is still active jobTemplate: spec: template: spec: containers: - name: backup image: postgres:18 # Prints the dump to the job log; write it to a volume or bucket in real use command: ["pg_dump", "-h", "db", "-U", "app", "mydb"] restartPolicy: OnFailure

Quartz and Spring (Java)

Quartz and Spring put seconds first. Quartz accepts an optional year as a seventh field, while Spring always has six. Spring numbers days of week 0 to 7 (0 and 7 are Sunday) and Quartz numbers them 1 to 7 (1 is Sunday). 0 0 9 ? * MON-FRI means 9:00:00 AM on weekdays in both.

Common Pitfalls and How to Avoid Them

1. Time zone confusion

Check which time zone your scheduler uses before you write the hour field. Linux cron uses the system's local time zone. Cronie (the cron on Fedora, RHEL and related distributions) also honors a CRON_TZ line in the crontab, but Debian's and Ubuntu's cron does not support per-crontab time zones and ignores it. EventBridge scheduled rules always use UTC, while EventBridge Scheduler lets you choose a zone. GitHub Actions uses UTC unless you set timezone. A Kubernetes CronJob follows the kube-controller-manager unless you set .spec.timeZone.

Fix: do not hard-code a UTC offset for a local-time job, because it is wrong for half the year. 9 AM Eastern is 14:00 UTC in winter (UTC-5) but 13:00 UTC in summer (UTC-4), so 0 14 * * * fires at 10 AM local time all summer. Set a time zone instead where your scheduler allows it, and use the Time Zone Converter to see what a local time is in UTC on a given date:

# cronie (Fedora, RHEL): set the time zone at the top of the file CRON_TZ=America/New_York 0 9 * * * /scripts/morning-report.sh

On Debian and Ubuntu, set the server's time zone, or use a systemd timer, whose OnCalendar accepts a zone (for example OnCalendar=Mon..Fri 09:00 America/New_York).

2. Overlapping runs

If a job takes 10 minutes but is scheduled every 5 minutes, runs pile up. That can corrupt data, exhaust memory or deadlock a database.

Fix: take a lock so a new run exits when the last one is still going:

# Linux: flock -n fails immediately instead of waiting for the lock */5 * * * * /usr/bin/flock -n /tmp/myjob.lock /scripts/long-running-job.sh # Kubernetes: skip the new run while the previous Job is active spec: concurrencyPolicy: Forbid

3. Day of month and day of week together

In standard cron, when both day-of-month and day-of-week are restricted (neither is *), the job runs when either field matches. POSIX spells this out: 0 0 1,15 * 1 runs on the 1st and 15th of each month as well as on every Monday. It does not mean "the 1st or 15th, but only if it is a Monday."

# Runs on the 1st of every month and also on every Monday 0 0 1 * 1 # To run only on the 1st when it is a Monday, test the day in the command 0 0 1 * * [ "$(date +\%u)" = "1" ] && /scripts/job.sh

AWS and Quartz avoid the surprise by refusing the combination: one day field must be ?.

4. Lost output

When a job writes output that you do not redirect, cron mails it to the crontab's owner, which only helps if the machine has a working mail system. On many servers and containers it does not, so errors disappear. Send output to a log file, or set MAILTO when mail is configured. If your logs record Unix timestamps, the Timestamp Converter turns them into readable dates.

# Silent failures if no mail system exists 0 2 * * * /scripts/backup.sh # Log standard output and errors 0 2 * * * /scripts/backup.sh >> /var/log/backup.log 2>&1 # Or mail the output to an address [email protected] 0 2 * * * /scripts/backup.sh

5. A different environment

Cron starts jobs with a small environment: SHELL is /bin/sh, HOME and LOGNAME come from your account, and PATH is only guaranteed to find the standard utilities. Your .bashrc and .profile are not loaded, which is why a script that works in your terminal can fail under cron. Use absolute paths, set variables at the top of the crontab, and do not depend on the working directory.

PATH=/usr/local/bin:/usr/bin:/bin NODE_ENV=production 0 9 * * * /usr/local/bin/node /home/user/app/send-report.js

6. An unescaped percent sign

In a crontab command, an unescaped % is turned into a newline: the command stops at the first one and the rest is passed to it as standard input. A line such as date +%F therefore breaks. Escape each one with a backslash, as in date +\%F.

More helpers for scripts and configuration files are in our developer tools collection.

Frequently Asked Questions

What is the smallest interval I can schedule with cron?

One minute. Cron examines its tables once a minute, so * * * * * is the fastest schedule and a five-field expression cannot run every 30 seconds. For sub-minute jobs, use a systemd timer (for example OnUnitActiveSec=30s, and lower AccuracySec from its one-minute default if you need precise timing) or a long-running process with its own timer. GitHub Actions is stricter: its shortest scheduled interval is once every 5 minutes.

How do I list and manage cron jobs?

Run crontab -l to list your jobs, crontab -e to edit them and crontab -r to remove all of them. Removal does not ask for confirmation unless you add -i. As root, add -u username to work on another user's table. System-wide jobs live in /etc/crontab and /etc/cron.d/, where each line has an extra field naming the user to run as. Many distributions also run scripts placed in /etc/cron.daily/, /etc/cron.weekly/ and /etc/cron.monthly/.

Why does my cron job work in the terminal but fail when scheduled?

Almost always the environment. Cron starts jobs with a small set of variables and does not load your shell profile, so PATH is short and your aliases and exports are missing. Use absolute paths for every command and file (for example /usr/local/bin/python3), set PATH at the top of the crontab, and do not rely on the working directory. Redirect output to a log file so you can read the actual error.

What is the difference between 5-field and 6-field cron?

Five-field cron (Linux crontab, GitHub Actions, Kubernetes) starts at the minute. Quartz and Spring put a seconds field first, which makes six fields, and Quartz accepts an optional seventh for the year. AWS EventBridge also has six fields, but they start at the minutes and end with a year. Day-of-week numbering differs as well: Linux accepts 0 to 7 with Sunday as 0 or 7, Quartz and AWS use 1 to 7 with Sunday as 1, and Spring follows Linux. Day names such as MON avoid the problem. Our Cron Expression Parser reads the standard five-field format.

How do daylight saving time changes affect cron jobs?

It depends on the scheduler, so test yours or avoid the problem. The cron(8) manual page for cronie and Debian's cron says clock changes of under three hours are handled specially for jobs that run at a fixed time or less often than hourly: after a spring-forward change the skipped jobs run right away, and after a fall-back change a job does not run twice. EventBridge Scheduler skips a job that lands in the missing hour and runs a repeated hour only once, and GitHub Actions moves a skipped 2:30 AM job to 3:00 AM when you set a time zone. The safest choice for critical jobs is UTC, or a time outside the 1:00 to 3:00 AM local window.

Sources