A cron expression is five space-separated fields that tell a Unix scheduler when to run a command. Five fields, a handful of special characters, and a few common patterns — that is the entire mental model. This reference covers the syntax, the characters that trip people up, and the schedules developers actually reach for.
The Five Fields
Standard cron uses five positional fields:
* * * * *
│ │ │ │ └─ Day of week (0–7, Sunday = 0 or 7)
│ │ │ └─── Month (1–12)
│ │ └───── Day of month (1–31)
│ └─────── Hour (0–23)
└───────── Minute (0–59)
AWS EventBridge and Java’s Quartz scheduler add a seconds field at the front, making it six fields total. Every other field shifts right. This trips up developers moving between environments — a Quartz expression pasted into a standard crontab will run on the wrong schedule without any warning.
Stick to five-field POSIX cron unless your platform explicitly requires six.
Special Characters
| Character | Meaning | Example |
|---|---|---|
* | Any value — match every unit | * * * * * — every minute |
, | List of values | 0 9,17 * * * — at 9am and 5pm |
- | Range | 0 9-17 * * * — every hour 9am to 5pm |
/ | Step interval | */15 * * * * — every 15 minutes |
? | No specific value (Quartz/AWS only) | 0 0 15 * ? — 15th of each month, any weekday |
L | Last (Quartz/AWS only) | 0 0 L * ? — last day of each month |
W | Nearest weekday (Quartz/AWS only) | 0 0 15W * ? — nearest weekday to the 15th |
Standard crontab only recognises *, ,, -, and /. If you see ?, L, or W in an expression, it was written for Quartz or AWS EventBridge — don’t paste it into a Linux crontab unchanged.
The Reference Table: Schedules Developers Actually Use
This is the part worth bookmarking. Build and validate any of these expressions with the IO Tools Cron Expression Generator.
| Description | Cron Expression | Notes |
|---|---|---|
| Every minute | * * * * * | Rarely appropriate in production |
| Every 5 minutes | */5 * * * * | Health checks, short polling cycles |
| Every 15 minutes | */15 * * * * | Cache warm-ups, feed syncs |
| Every 30 minutes | */30 * * * * | Equivalent to 0,30 * * * * |
| Every hour (on the hour) | 0 * * * * | Runs at :00 of every hour |
| Every 6 hours | 0 */6 * * * | Data sync, incremental exports |
| Daily at midnight UTC | 0 0 * * * | Standard daily batch trigger |
| Daily at 9am UTC | 0 9 * * * | Morning report generation |
| Weekdays at 9am UTC | 0 9 * * 1-5 | Business-hours-only jobs (Mon–Fri) |
| Weekdays at 8:30am UTC | 30 8 * * 1-5 | Pre-standup report delivery |
| Every Sunday at 2am | 0 2 * * 0 | Weekly maintenance, off-peak backups |
| First day of each month | 0 0 1 * * | Monthly billing runs, recurring reports |
| January 1st at midnight | 0 0 1 1 * | Annual resets, year-start jobs |
The Timezone Gotcha
Cron has no timezone awareness. It runs in whatever timezone the server is configured for — on most Linux systems, that is UTC. This is usually fine until you have jobs tied to business hours, or users in multiple regions wondering why the “9am report” arrives at 2pm.
The safest defaults:
- Set your server to UTC. Convert to local time in application logic, not in the cron schedule.
- Comment every cron job with the effective local time, so the next person reading the crontab isn’t guessing.
- When using cloud schedulers (AWS EventBridge, Google Cloud Scheduler), verify the timezone field — most support IANA timezone names directly, which removes the ambiguity.
# Always comment with the effective local time
# Runs daily at midnight UTC (= 8pm EST / 5pm PST)
0 0 * * * /usr/bin/python3 /opt/scripts/daily_report.py
Testing: Calculate Next Runs Before You Deploy
Deploying a cron job to discover it fires every minute instead of every hour is a rite of passage. Skip it.
The IO Tools Cron Next Run Calculator shows exactly when your expression will fire next — paste your expression and get the next ten run times before touching a server.
For command-line validation:
# Install croniter (Python) for quick expression testing
pip install croniter
python3 -c "
from croniter import croniter
from datetime import datetime
cron = croniter('*/15 * * * *', datetime.utcnow())
for _ in range(5):
print(cron.get_next(datetime))
"
Adding a Cron Job on Linux
# Open the crontab editor for the current user
crontab -e
# Format: minute hour day month weekday command
# Run backup script daily at 2:30am UTC
30 2 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1
# Run a Python script every 5 minutes
*/5 * * * * /usr/bin/python3 /home/user/scripts/sync.py
# View current crontab entries
crontab -l
# Edit another user's crontab (requires root)
crontab -u www-data -e
The 2>&1 at the end of the backup line redirects stderr into stdout, so both go into the log file. Without it, cron errors go to the mail spool — and nobody checks that.
GitHub Actions Scheduled Workflow
GitHub Actions uses the same five-field cron syntax, always in UTC. There is no timezone override.
name: Nightly Data Export
on:
schedule:
# Runs at 1:00 AM UTC every weekday
- cron: "0 1 * * 1-5"
workflow_dispatch: # Allow manual trigger
jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run export script
run: python scripts/export.py
One caveat: GitHub Actions scheduled workflows can be delayed up to 15 minutes during high-load periods. Don’t rely on them for anything requiring precise timing.
When Cron Isn’t Enough
Standard cron handles most use cases on a single server. Its limits become problems at scale:
- No retry on failure. If the job crashes, the next run is the next scheduled time — there’s no automatic retry.
- No distributed locking. Multiple servers running the same crontab will fire the same job simultaneously.
- No observability. There’s no built-in dashboard for run history, failure alerts, or duration tracking.
| Problem | Better Alternative |
|---|---|
| Retry logic and task queues | Celery Beat (Python), Sidekiq-Cron (Ruby) |
| Cloud-native scheduling with retries | AWS EventBridge + Lambda, Google Cloud Scheduler |
| CI/CD pipeline triggers | GitHub Actions schedules |
| Observable job orchestration | Airflow, Prefect, Temporal |
For scripts on a single server, cron is still the right tool — it’s simple, reliable, and has zero dependencies. For anything that needs retry guarantees, distributed execution, or failure visibility, a dedicated task queue pays for itself quickly.
Use the Cron Expression Generator to build your next schedule without memorising the syntax, and the Cron Next Run Calculator to verify it fires when you expect.
Install Our Extensions
Add IO tools to your favorite browser for instant access and faster searching
恵 Scoreboard Has Arrived!
Scoreboard is a fun way to keep track of your games, all data is stored in your browser. More features are coming soon!
Must-Try Tools
View All New Arrivals
View AllUpdate: Our latest tool was added on Apr 22, 2026
