Developer Tools·12 min read·By sourcecodestack Editorial Team

Cron Schedule Examples Explained

Cron Schedule Examples Explained

Cron is one of the most enduring pieces of Unix infrastructure. It has been scheduling tasks reliably since 1975 — predating the web, Linux, and most modern programming languages. If you run anything on a Linux or macOS server, you almost certainly use cron or a system that borrowed its syntax.

This guide explains the cron expression format from scratch, gives you 25+ copy-paste schedule examples grouped by use case, and covers the gotchas that cause real production incidents.

If you want to build and validate cron expressions interactively, the Cron Expression Builder lets you click fields and instantly see the next run times.


What Is Cron?

Cron is a time-based job scheduler built into Unix-like operating systems. A background process called the cron daemon (crond) wakes up every minute, reads a set of schedule definitions called crontabs, and runs any commands whose schedule matches the current time.

The name comes from Chronos, the Greek god of time.

Cron is ideal for:

  • Recurring system maintenance (log rotation, disk cleanup, cache warming)
  • Scheduled data pipelines and ETL jobs
  • Regular report generation and email dispatch
  • Database backups
  • Health checks and watchdog scripts
  • Periodic API polling

The Five-Field Cron Expression

A standard cron expression has exactly five fields separated by whitespace, followed by the command to run:

* * * * * command to execute
│ │ │ │ │
│ │ │ │ └── Day of week (07, where 0 and 7 are both Sunday)
│ │ │ └──── Month (112)
│ │ └────── Day of month (131)
│ └──────── Hour (023)
└────────── Minute (059)

Important: Cron has NO seconds field. The smallest resolution is one minute. Systems that claim "cron-like" syntax with six fields (common in Spring Boot, Kubernetes CronJobs with some controllers, and cloud schedulers) add a seconds field — but this is a non-standard extension. Always check the documentation of the specific system you are using.

Field Ranges at a Glance

Field Position Range Notes
Minute 1st 0–59
Hour 2nd 0–23 24-hour clock
Day of month 3rd 1–31 See OR-gotcha below
Month 4th 1–12 Also accepts JANDEC
Day of week 5th 0–7 0 and 7 = Sunday; also accepts SUNSAT

Special Characters

Five special characters control how each field is interpreted:

`*` — Wildcard (every)

Matches every valid value for that field.

* * * * *   ← runs every minute of every hour of every day

`,` — List (multiple values)

Specifies a list of values.

0 9,17 * * *   ← runs at 09:00 and 17:00 every day

`-` — Range

Specifies a contiguous range of values.

0 9-17 * * *   ← runs at 09:00, 10:00, 11:00, ... 17:00 every day

`/` — Step

Specifies a step interval. */5 means "every 5".

*/5 * * * *    ← runs every 5 minutes
0 */4 * * *   ← runs every 4 hours, on the hour

You can combine step with a range: 10-50/10 means "10, 20, 30, 40, 50".

`?` — No specific value (Quartz / Spring only)

The ? character is used in day-of-month or day-of-week fields in non-standard Quartz Scheduler syntax to mean "I am not specifying a value here." Standard Unix cron does not support ? — use * instead.

Named Shortcuts

Many cron implementations support convenience strings in place of the five fields:

Shortcut Equivalent Meaning
@yearly / @annually 0 0 1 1 * Once a year, midnight Jan 1
@monthly 0 0 1 * * Once a month, midnight on the 1st
@weekly 0 0 * * 0 Once a week, midnight Sunday
@daily / @midnight 0 0 * * * Once a day, midnight
@hourly 0 * * * * Once an hour, at minute 0
@reboot Run once at startup

25+ Cron Schedule Examples

Every-Interval Schedules

Schedule Expression Notes
Every minute * * * * * Rarely useful in production — high overhead
Every 2 minutes */2 * * * *
Every 5 minutes */5 * * * * Common for health checks
Every 10 minutes */10 * * * *
Every 15 minutes */15 * * * *
Every 30 minutes */30 * * * * Or 0,30 * * * *
Every hour 0 * * * * At minute 0 of every hour
Every 2 hours 0 */2 * * *
Every 4 hours 0 */4 * * *
Every 6 hours 0 */6 * * * 00:00, 06:00, 12:00, 18:00
Every 12 hours 0 */12 * * * Midnight and noon

Daily Schedules

Schedule Expression Notes
Every day at midnight 0 0 * * * Or @daily
Every day at 1 AM 0 1 * * *
Every day at 6 AM 0 6 * * * Good for morning reports
Every day at noon 0 12 * * *
Every day at 9:30 AM 30 9 * * *
Every day at 11:45 PM 45 23 * * *
Twice daily (6 AM and 6 PM) 0 6,18 * * *
Three times daily 0 8,12,18 * * *

Weekly and Weekday Schedules

Schedule Expression Notes
Every Sunday at midnight 0 0 * * 0 Or @weekly
Every Monday at 8 AM 0 8 * * 1
Every Friday at 5 PM 0 17 * * 5 End-of-week jobs
Weekdays at 9 AM 0 9 * * 1-5 Monday through Friday
Weekdays at 6 AM and 6 PM 0 6,18 * * 1-5
Weekends only at noon 0 12 * * 6,0 Saturday and Sunday

Monthly and Yearly Schedules

Schedule Expression Notes
1st of every month, midnight 0 0 1 * * Or @monthly
1st and 15th, 6 AM 0 6 1,15 * * Semi-monthly
Last business day of month Not expressible in standard cron Use a wrapper script
Every January 1st, midnight 0 0 1 1 * Or @yearly
Every quarter (Jan, Apr, Jul, Oct) 0 0 1 1,4,7,10 *
Every 6 months (Jan 1 and Jul 1) 0 0 1 1,7 *

The Day-of-Month vs Day-of-Week OR Gotcha

This is the most surprising behavior in standard cron and causes real bugs.

The rule: If both the day-of-month field AND the day-of-week field are restricted (not *), cron treats them as an OR condition, not AND. The job runs when either condition is true.

Most people expect AND behavior — the job should run on the 15th, but only if it's a Monday. Cron does not work that way.

0 9 15 * 1

You might read this as: "Run at 9 AM on the 15th of the month, but only if it's a Monday."

What cron actually does: Run at 9 AM on ANY Monday, and ALSO on every 15th of every month regardless of day of week.

Why Does This Happen?

The POSIX specification defines this behavior explicitly. The rationale is that common scheduling needs — "run on every Monday AND every 15th" — would be cumbersome to express otherwise. But the AND interpretation is far more intuitive and this is a frequent source of confusion.

How to Work Around It

If you need the AND behavior ("the 15th, only if it is a Monday"), do not express it in the cron expression. Instead, use a wildcard in the day-of-week field and add a check inside your script:

# Crontab: run every 15th regardless of day
0 9 15 * * /path/to/wrapper.sh

# wrapper.sh: check if today is Monday
#!/bin/bash
[ $(date +%u) -eq 1 ] && /path/to/actual-script.sh

Alternatively, use a scheduling system that supports compound day logic, such as AWS EventBridge Scheduler or a Kubernetes CronJob with a smarter controller.


Common Cron Mistakes

1. Timezone Confusion

Cron runs in the timezone of the server, which is often UTC in production environments. If your server is UTC and you write:

0 9 * * *   # "Run at 9 AM"

...it runs at 09:00 UTC. For users in New York (UTC-5 in winter), that is 4 AM. This catches teams off guard when a "morning report" arrives at 4 AM local time.

Solutions:

  • Know your server's timezone: timedatectl on systemd systems, date to see the current offset.
  • Set CRON_TZ (or TZ) at the top of your crontab to specify the timezone explicitly. GNU cron supports this; not all cron implementations do.
  • Convert your desired local time to UTC and write the UTC time in the crontab.
  • Use a cloud scheduler (AWS EventBridge, GCP Cloud Scheduler, Azure Logic Apps) that has first-class timezone support.

2. Overlapping Runs

If your job takes longer than the interval between runs, multiple instances will overlap. A backup job that takes 75 minutes scheduled every hour means two instances run simultaneously for 15 minutes every hour — reading and writing the same data.

Solution: Use a lock file or a tool like flock:

*/30 * * * * flock -n /var/lock/myjob.lock /path/to/myjob.sh

flock -n exits immediately if the lock is already held, so overlapping runs skip instead of pile up.

3. Cron Runs in a Minimal Environment

Cron jobs do not inherit your interactive shell environment. The PATH is usually /usr/bin:/bin, and none of your shell profile files (.bashrc, .bash_profile, .zshrc) are sourced. This means:

  • Commands that work in your terminal may fail with "command not found" in cron.
  • Environment variables your app depends on are not set.
  • nvm, rbenv, pyenv, and similar version managers are not initialized.

Solutions:

  • Use full absolute paths: /usr/local/bin/python3 instead of python3.
  • Set the PATH variable at the top of your crontab:
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
  • Source the necessary profile files at the start of your script:
    #!/bin/bash
    source /home/user/.nvm/nvm.sh
    nvm use 20
    node /path/to/app.js
  • Store secrets in a file that your script reads, not in crontab environment variables (they appear in process listings).

4. Output Goes to /dev/null or Generates Email Spam

By default, cron emails any stdout or stderr output to the local system user. On servers without a working mail system, output is silently discarded. On servers with mail, you get a flood of emails.

Solution: Redirect output explicitly:

# Discard all output
0 6 * * * /path/to/script.sh > /dev/null 2>&1

# Log to a file
0 6 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1

# Log with timestamps
0 6 * * * /path/to/script.sh >> /var/log/myjob.log 2>&1 && echo "$(date): success" >> /var/log/myjob.log

Or set MAILTO at the top of the crontab to suppress all emails:

MAILTO=""

5. The No-Seconds Field Mistake

Developers moving from Quartz (Java), Spring, or some cloud schedulers to standard cron sometimes add a sixth field for seconds:

# WRONG for Unix cron — this is interpreted as:
# minute=0, hour=6, day=*, month=*, day-of-week=* with 'command' as extra token
0 0 6 * * * /path/to/script.sh

Unix cron will silently do the wrong thing or throw a parse error. Drop the seconds field.

6. Assuming a Job Runs Exactly On Schedule

Cron does not guarantee sub-minute precision. The daemon wakes up every minute and checks whether anything is due. Under heavy system load, the daemon itself may wake up a few seconds late. Do not write jobs that depend on exact-to-the-second execution.

7. Forgetting to Handle Failures

Cron does not retry failed jobs. If your script exits with a non-zero code at 3 AM, the next run is tomorrow at 3 AM unless you add monitoring. Always:

  • Log exit codes.
  • Set up alerting for critical jobs (PagerDuty, Slack webhook, etc.).
  • Consider tools like cronitor, healthchecks.io, or Dead Man's Snitch that page you if a job does not check in.

Where Cron Runs

User Crontabs

The standard way to manage per-user cron jobs:

crontab -e    # open your crontab in $EDITOR
crontab -l    # list your current crontab
crontab -r    # remove your entire crontab (CAREFUL)
crontab -u alice -l  # view another user's crontab (as root)

User crontabs are stored in /var/spool/cron/crontabs/ (location varies by distro). Do not edit these files directly — always use crontab -e to ensure syntax validation.

System-Wide Crontab: /etc/crontab

The system crontab has a slightly different format — it adds a username field before the command:

# /etc/crontab
MINUTE HOUR DOM MONTH DOW USER COMMAND
0      6    *   *     *   root /usr/sbin/logrotate /etc/logrotate.conf

Drop-In Directories: /etc/cron.d

Packages and applications drop crontab files into /etc/cron.d/. These use the same six-column format as /etc/crontab (with the username field). This is the preferred mechanism for system services because it keeps each application's schedule in its own file.

ls /etc/cron.d/
# apt  certbot  logrotate  php8.2  postfix

Convenience Directories

Directory Default Schedule
/etc/cron.hourly/ Every hour
/etc/cron.daily/ Every day (usually early morning)
/etc/cron.weekly/ Every week (usually Sunday morning)
/etc/cron.monthly/ First of every month

Drop an executable script into any of these directories and run-parts (called from /etc/crontab) will run it on schedule. No crontab syntax needed — just make sure the script is executable (chmod +x) and has no extension (some run-parts versions skip files with dots in the name).


Cron in Cloud and Container Environments

AWS EventBridge Scheduler

AWS uses a six-field cron expression with a years field:

cron(0 6 * * ? *)   # 6 AM UTC every day

The ? is required in either day-of-month or day-of-week (the Quartz convention). The years field is optional but must be present. Supports rate expressions as an alternative: rate(1 day).

GCP Cloud Scheduler

Uses standard five-field Unix cron syntax. Supports timezone selection natively — no more UTC mental arithmetic.

0 6 * * *   (America/New_York)   # 6 AM Eastern

Kubernetes CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 6 * * *"          # Standard 5-field cron
  timeZone: "America/New_York"   # Kubernetes 1.27+ supports native TZ
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: reporter
            image: myapp:latest
          restartPolicy: OnFailure

K8s CronJob gotchas:

  • If the cron controller misses a schedule (e.g., during a cluster outage), it will catch up according to startingDeadlineSeconds. Configure this carefully.
  • Set concurrencyPolicy: Forbid to prevent overlapping runs (equivalent to flock in Linux cron).
  • Resource limits on the Job's pod spec are important — runaway jobs have no cron-level timeout.

GitHub Actions

on:
  schedule:
    - cron: '0 6 * * *'   # 6 AM UTC daily

GitHub Actions cron jobs always run in UTC. The minimum interval is every 5 minutes. GitHub notes that workflows may be delayed during high demand periods.


Debugging Cron Jobs

Cron jobs failing silently is a rite of passage. Here is a systematic debugging approach:

1. Test the Command Manually First

Run the exact command from crontab (with the full path) in a terminal. If it fails there, it will fail in cron.

2. Simulate the Cron Environment

env -i HOME=/root LOGNAME=root USER=root \
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
    SHELL=/bin/sh \
    /bin/sh -c '/path/to/your/command'

This strips your environment variables and tests the script with only what cron provides.

3. Check Cron Logs

# systemd-based systems (most modern Linux distros)
journalctl -u cron -f

# Or with grep for your specific job
journalctl -u cron | grep myjob

# Older systems
tail -f /var/log/cron
tail -f /var/log/syslog | grep cron

4. Log Output from the Script

Add logging directly to your crontab line while debugging:

*/5 * * * * /path/to/script.sh >> /tmp/cron-debug.log 2>&1

Check /tmp/cron-debug.log after the next scheduled run.

5. Verify the Crontab Syntax

crontab -l   # review what is actually installed

A common mistake is editing the crontab file with nano /var/spool/cron/crontabs/user directly, which can corrupt permissions and cause cron to silently ignore the file.


Cron Alternatives

Cron is not always the right tool:

Alternative When to Use It
systemd timers Modern Linux systems — better logging, transient units, dependency management
Airflow / Prefect Complex data pipelines with dependencies, retries, and observability
Celery Beat Django/Python apps that already use Celery
node-cron / node-schedule Node.js apps that need in-process scheduling
APScheduler (Python) Python apps needing in-process scheduling
Cloud schedulers Serverless functions, managed infrastructure without persistent servers
Kubernetes CronJob Containerized workloads in a K8s cluster

Systemd timers deserve a special mention. On any system running systemd, timers are a more robust alternative to cron for system-level jobs. They log to the journal, support OnCalendar expressions (similar to cron but with readable syntax like daily or Mon *-*-* 09:00:00), and integrate with service dependencies.


Quick Reference Card

# Cron expression format:
# MIN  HOUR  DOM  MON  DOW  COMMAND

# Special characters:
# *     every valid value
# ,     list: 1,3,5
# -     range: 1-5
# /     step: */15 (every 15)

# Examples:
* * * * *           every minute
*/5 * * * *         every 5 minutes
0 * * * *           top of every hour
0 6 * * *           6 AM daily
30 9 * * 1-5        9:30 AM weekdays
0 0 1 * *           midnight on 1st of month
0 0 1 1 *           midnight on Jan 1 (yearly)
0 6,18 * * *        6 AM and 6 PM daily
0 9 * * 1           9 AM every Monday
*/15 9-17 * * 1-5   every 15 min, 9-5, weekdays

Conclusion

Cron is deceptively simple on the surface — five fields — but full of subtleties that have tripped up every developer who works with it seriously. The OR behavior of day-of-month and day-of-week, the minimal environment, timezone handling, and overlapping runs are the four areas that cause most production issues.

The good news: once you internalize those rules and adopt good habits (full paths, explicit output redirection, a lock file for long-running jobs, monitoring), cron is a remarkably reliable workhorse. It has been running without drama on Unix systems for five decades and will continue to do so.

For a visual way to build and validate cron expressions without memorizing the syntax, try the Cron Expression Builder. Paste any expression and it shows the next 10 scheduled run times, making it easy to verify your schedule before deploying it.

You might also like