If you have ever needed to run a backup at 2 AM every night, send a weekly report every Monday morning, or poll an API every five minutes, you have needed Cron. It is the backbone of scheduled automation on Unix/Linux systems — and the Cron expression syntax it uses has spread far beyond the server to GitHub Actions, AWS Lambda, Kubernetes, and every major web framework.

This guide covers everything: the five-field syntax, the special characters, common patterns, how Cron works in cloud environments, and how to debug when jobs silently fail.

What is Cron?

Cron is a time-based job scheduler built into Unix and Linux operating systems. A background process called the cron daemon wakes up every minute, reads the crontab (cron table) configuration file, and executes any commands whose scheduled time has arrived.

Common use cases include:

  • Database backups — dump the database nightly and upload to object storage
  • Email digests — send a daily or weekly summary email to users
  • Log rotation — compress and archive logs weekly
  • Cache warming — regenerate aggregated data every 30 minutes
  • Health checks — ping a status endpoint every 5 minutes

Cron Expression Syntax

A standard Cron expression consists of five space-separated fields:

┌──────── minute (0-59)
│ ┌────── hour (0-23)
│ │ ┌──── day of month (1-31)
│ │ │ ┌── month (1-12)
│ │ │ │ ┌ day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
FieldRangeNotes
Minute0–590 = the top of each hour
Hour0–230 = midnight
Day of month1–31Maximum varies by month
Month1–12 (or Jan–Dec)
Day of week0–7 (0 and 7 = Sunday)1=Mon, 5=Fri, 6=Sat

Special Characters

SymbolMeaningExample
*Every value* * * * * — every minute
*/nEvery n steps*/5 * * * * — every 5 minutes
a-bRange from a to b1-5 in weekday = Mon–Fri
a,b,cList of values0,15,30,45 — every 15 min
a-b/nRange with step0-12/2 — even hours 0–12

Common Cron Expression Patterns

Basic Patterns

# Every minute
* * * * *

# Every 5 minutes
*/5 * * * *

# Every 15 minutes
0,15,30,45 * * * *   # or: */15 * * * *

# Every hour (at :00)
0 * * * *

# Every day at midnight
0 0 * * *

# Every day at 2 AM (good for backups)
0 2 * * *

# Every Monday at 9 AM
0 9 * * 1

# First day of every month at midnight
0 0 1 * *

# Every January 1st at midnight
0 0 1 1 *

Business-Hour Patterns

# Weekdays (Mon–Fri) at 9 AM
0 9 * * 1-5

# Every hour during business hours on weekdays
0 9-18 * * 1-5

# Weekday nightly batch at 2 AM
0 2 * * 1-5

# Friday at 6 PM (weekly report)
0 18 * * 5

The Day + Weekday OR Trap

# WARNING: specifying both day-of-month and day-of-week acts as OR, not AND
# This runs on the 15th of every month AND every Sunday
0 0 15 * 0

When you specify non-wildcard values in both the day-of-month and day-of-week fields, most Cron implementations treat them as OR — not AND. There is no standard way to express "only if the 15th is a Sunday."

Using crontab

Basic Commands

# Edit your crontab (opens in $EDITOR)
crontab -e

# List current crontab
crontab -l

# Remove all cron jobs (irreversible without a backup!)
crontab -r

# Edit another user's crontab (requires root)
crontab -u username -e

Writing Reliable Crontab Entries

# Always use full paths — cron has a minimal PATH
0 2 * * * /usr/bin/python3 /home/user/backup.py

# Redirect output to a log file
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Set environment variables at the top of crontab
MAILTO=admin@example.com
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * /usr/local/bin/myapp --backup

Why full paths? The cron environment has a very limited PATH. Commands that work fine in your shell may silently fail in cron because the binary cannot be found.

Cron in Cloud Environments

GitHub Actions

Use on.schedule with a standard Cron expression. All schedules run in UTC.

on:
  schedule:
    - cron: '0 0 * * *'   # UTC 00:00 = EST 19:00 (previous day)

jobs:
  nightly:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./scripts/nightly.sh

GitHub Actions schedules may be delayed by several minutes during high-load periods. For time-critical workloads, a dedicated server cron is more reliable.

AWS EventBridge (formerly CloudWatch Events)

AWS uses a 6-field Cron expression (year field added, ? required in either day-of-month or day-of-week):

# Rate expression (simple interval)
rate(5 minutes)
rate(1 hour)

# Cron expression: cron(minute hour day month weekday year)
cron(0 2 * * ? *)        # Every day at UTC 02:00
cron(0 9 ? * MON-FRI *)  # Weekdays at UTC 09:00

Laravel Task Scheduler

# One line in crontab — Laravel handles the rest
* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
    $schedule->command('backup:run')->dailyAt('02:00');
    $schedule->command('report:weekly')->weeklyOn(5, '18:00');
    $schedule->cron('0 9 * * 1-5')->command('send:digest');
}

Kubernetes CronJob

apiVersion: batch/v1
kind: CronJob
metadata:
  name: backup-job
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: my-backup:latest
          restartPolicy: OnFailure

Timezone Gotchas

The time a Cron job runs depends entirely on the server's configured timezone. Cloud servers default to UTC, so "9 AM" in your crontab is UTC 9 AM — not your local time.

# Check server timezone
date
timedatectl

# Set timezone per-crontab (GNU cron)
CRON_TZ=America/New_York
0 9 * * * /usr/bin/morning-report.sh

Services that always use UTC: GitHub Actions, AWS EventBridge, Google Cloud Scheduler (configurable but UTC default). Always document the assumed timezone in comments next to your Cron expressions.

Debugging Cron Jobs

Checklist: Why Is My Cron Job Not Running?

  • Wrong path: Use full paths for all commands. Run which python3 to find the binary path
  • No execute permission: Run chmod +x script.sh on your script
  • Windows line endings: Scripts created on Windows may have \r\n — run dos2unix script.sh
  • Silent errors: Redirect stderr: command >> /var/log/app.log 2>&1
  • Check syslog: grep CRON /var/log/syslog | tail -20
  • Missing environment: Source your profile in the script: source /home/user/.bashrc

Viewing Cron Logs

# View recent cron activity (Debian/Ubuntu)
grep CRON /var/log/syslog | tail -30

# systemd-based systems
journalctl -u cron --since "1 hour ago"

# macOS
log show --predicate 'process == "cron"' --last 1h

FAQ

Can I run a Cron job every 30 seconds?
Standard Cron has a 1-minute minimum resolution. The usual workaround is to run a job every minute and have the script sleep for 30 seconds before executing the second run: command; sleep 30; command. For sub-minute scheduling, consider Systemd timers or an application-level scheduler.
What is the difference between */1 and *?
They are identical. */1 means "every 1 step across all values" which equals "every value" — the same as *. Use * for clarity.
Can I schedule a job for the last day of the month?
Standard 5-field Cron cannot natively express "last day of month." Workarounds: use day range 28-31 and check in the script if it is truly the last day, or use a cron implementation that supports the L modifier (like Quartz Scheduler).
How often does GitHub Actions run scheduled workflows?
The minimum interval is 5 minutes (*/5 * * * *). Actual execution may be delayed by a few minutes under load. Scheduled workflows on forked repositories or inactive repos may be disabled by GitHub automatically.
I accidentally ran crontab -r. Can I recover?
Not without a backup. Always keep a backup: crontab -l > ~/crontab.bak. Some systems keep crontab files under /var/spool/cron/crontabs/ — check if a recent version exists there before concluding it is gone.

Summary

  • A Cron expression is five space-separated fields: minute hour day-of-month month day-of-week.
  • Use * for "every", */n for step intervals, a-b for ranges, a,b,c for lists.
  • Always use full paths in crontab and redirect output to a log file.
  • Cloud environments (GitHub Actions, AWS EventBridge) use UTC — account for timezone offsets.
  • Combining day-of-month and day-of-week produces OR behavior, not AND.
  • Use the Cron Generator to build expressions visually and preview the next 5 run times.