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) │ │ │ │ │ * * * * *
| Field | Range | Notes |
|---|---|---|
| Minute | 0–59 | 0 = the top of each hour |
| Hour | 0–23 | 0 = midnight |
| Day of month | 1–31 | Maximum varies by month |
| Month | 1–12 (or Jan–Dec) | |
| Day of week | 0–7 (0 and 7 = Sunday) | 1=Mon, 5=Fri, 6=Sat |
Special Characters
| Symbol | Meaning | Example |
|---|---|---|
* | Every value | * * * * * — every minute |
*/n | Every n steps | */5 * * * * — every 5 minutes |
a-b | Range from a to b | 1-5 in weekday = Mon–Fri |
a,b,c | List of values | 0,15,30,45 — every 15 min |
a-b/n | Range with step | 0-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 python3to find the binary path - No execute permission: Run
chmod +x script.shon your script - Windows line endings: Scripts created on Windows may have
\r\n— rundos2unix 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.
*/1means "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-31and check in the script if it is truly the last day, or use a cron implementation that supports theLmodifier (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",*/nfor step intervals,a-bfor ranges,a,b,cfor 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.