All posts
cron7 min read

The Cron Expression Cheatsheet Every Developer Needs

A no-nonsense reference for cron syntax — field meanings, special characters, real examples, and the timezone traps that have burned us all at 3am.

Marco

Marco

The Cron Expression Cheatsheet Every Developer Needs

Cron expressions are one of those things where you think you know them until you're staring at 0 5 * * 1-5 at 11pm trying to figure out why your backup job fired on a Sunday.

This is the reference I keep open. Bookmark it, share it with your team, print it out — whatever works.


The Anatomy of a Cron Expression

Standard cron uses 5 fields. Some systems (like many job schedulers) add a 6th for seconds.

┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12 or JAN–DEC)
│ │ │ │ ┌─ day of week (0–7, Sun=0 or 7, or SUN–SAT)
│ │ │ │ │
* * * * *

If your scheduler supports 6 fields with seconds:

┌─────────── second (0–59)
│ ┌─────── minute (0–59)
│ │ ┌───── hour (0–23)
│ │ │ ┌─── day of month (1–31)
│ │ │ │ ┌─ month (1–12)
│ │ │ │ │ ┌ day of week (0–7)
│ │ │ │ │ │
* * * * * *

Systems like Kubernetes CronJobs, node-cron, and AWS EventBridge use 5 fields. Systems like Quartz Scheduler (Java) or some cloud functions use 6.


Special Characters

CharacterMeaningExample
*Any / every value* * * * * — runs every minute
,List of values0 9,17 * * * — runs at 9am and 5pm
-Range0 9-17 * * * — runs every hour from 9am to 5pm
/Step / interval*/15 * * * * — every 15 minutes
?No specific value (day fields only)0 0 1 * ? — midnight on 1st, any weekday
LLast (day fields, some systems only)0 0 L * * — last day of month
WNearest weekday (some systems only)0 0 15W * * — nearest weekday to 15th
#Nth weekday (some systems only)0 0 * * 1#2 — 2nd Monday of month

The Cheatsheet

Every N Minutes/Hours

*/5 * * * *        # Every 5 minutes
*/15 * * * *       # Every 15 minutes
*/30 * * * *       # Every 30 minutes
0 */2 * * *        # Every 2 hours (on the hour)
0 */6 * * *        # Every 6 hours

Daily Jobs

0 0 * * *          # Midnight (00:00) every day
0 2 * * *          # 2am every day (classic backup time)
0 8 * * *          # 8am every day
0 8,20 * * *       # 8am and 8pm every day
30 23 * * *        # 11:30pm every day

Weekday vs Weekend

0 9 * * 1-5        # 9am Monday through Friday
0 9 * * MON-FRI    # Same thing, using names
0 10 * * 6,0       # 10am on Saturday and Sunday
0 10 * * SAT,SUN   # Same thing
0 9 * * 1          # 9am every Monday

Heads up: Day-of-week 0 and 7 both mean Sunday in most implementations. Don't rely on 7 being universally supported.

Monthly and Quarterly

0 0 1 * *          # Midnight on the 1st of every month
0 0 1,15 * *       # Midnight on 1st and 15th
0 0 L * *          # Last day of month (if supported)
0 0 1 */3 *        # Quarterly: Jan, Apr, Jul, Oct
0 0 1 1,4,7,10 *   # Same thing but explicit

Yearly

0 0 1 1 *          # Midnight on January 1st (Happy New Year jobs)
0 0 * * *          # Not yearly, don't confuse this — it's daily midnight

Every N Seconds (6-field syntax)

*/10 * * * * *     # Every 10 seconds (node-cron, Quartz)
*/30 * * * * *     # Every 30 seconds
0 * * * * *        # Every minute at second 0

Named Shortcuts

Most cron implementations support these shortcuts:

ShortcutEquivalentMeaning
@yearly / @annually0 0 1 1 *Once a year, January 1st, midnight
@monthly0 0 1 * *Once a month, first day, midnight
@weekly0 0 * * 0Once a week, Sunday midnight
@daily / @midnight0 0 * * *Once a day, midnight
@hourly0 * * * *Once an hour, on the hour
@rebootRun once at startup (crontab only)

Month and Day Name Aliases

You can use 3-letter names instead of numbers:

Months: JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC

Days: SUN MON TUE WED THU FRI SAT

0 9 * JAN-MAR MON-FRI    # 9am weekdays in Q1
0 0 25 DEC *              # Christmas midnight

Real-World Examples

These are patterns I've actually used in production:

# Database backup every night at 2am
0 2 * * *

# Clear temp files every Sunday at 3am
0 3 * * SUN

# Send weekly digest emails Monday morning
0 8 * * MON

# Check stock prices every 5 minutes on weekdays, market hours only
*/5 9-16 * * 1-5

# Monthly invoice generation (1st of month, 7am)
0 7 1 * *

# Expire old sessions every 15 minutes
*/15 * * * *

# Generate sitemap nightly
0 1 * * *

# Healthcheck ping to external service every minute
* * * * *

# Rotate logs at midnight, keep 30 days of history
0 0 * * *

The Timezone Trap

This is where most production incidents live. Cron runs in the system timezone of the machine running it unless you specify otherwise.

The classic failure: your job is configured as 0 2 * * * thinking "2am server time" but the server is in UTC+3. It runs at 5am local time. Or you deployed to a new region, timezone changed, and suddenly your "daily midnight" job runs at 9am.

Always declare timezone explicitly when the schedule matters to end users:

# On Linux (user crontab or system cron)
CRON_TZ="America/Sao_Paulo"
0 2 * * *   /opt/scripts/backup.sh

# Or per-job (some crond implementations)
0 2 * * *  TZ="America/New_York" /opt/scripts/report.sh

For Kubernetes CronJobs, use the .spec.timeZone field (K8s 1.25+):

spec:
  schedule: "0 2 * * *"
  timeZone: "America/Sao_Paulo"

For GitHub Actions:

on:
  schedule:
    - cron: "0 5 * * *" # This is always UTC. Always.

GitHub Actions cron is always UTC. Always. I've been burned by this twice.


Testing Your Expressions

Before you deploy, test your expression:

  • Cronping Cron Expression Generator — validate expressions and see next execution times
  • python3 -c "import croniter; c = croniter.croniter('*/5 9-17 * * 1-5'); print([c.get_next() for _ in range(5)])" — programmatic
  • node -e "const cron = require('cron-parser'); console.log(cron.parseExpression('*/5 * * * *').next().toDate())" — Node.js

Most importantly: run it with monitoring from day one. If a cron job doesn't report in, you want to know.


Connecting to Cronping

Once you've got your expression right, wrap your jobs with Cronping to make sure they actually run — and that you know when they don't.

#!/bin/bash
# db-backup.sh

PING_URL="https://ping.cronping.com/YOUR_PING_KEY"

# Signal start
curl -fsS -m 10 --retry 5 -o /dev/null "$PING_URL/start"

# Run the job
pg_dump -h localhost mydb | gzip > /backups/mydb_$(date +%F).sql.gz
EXIT_CODE=$?

# Signal completion with exit code (0=success, anything else=fail)
curl -fsS -m 10 --retry 5 -o /dev/null "$PING_URL/$EXIT_CODE"

Cronping knows your cron expression, so if a ping doesn't arrive when expected, it alerts you. No more "the backup hasn't run in 3 weeks and we only noticed when we needed to restore."


Quick Reference Card

Save this part:

*  *  *  *  *
│  │  │  │  └─ day of week (0=Sun, 7=Sun, 1-6=Mon-Sat)
│  │  │  └──── month (1-12, or JAN-DEC)
│  │  └─────── day of month (1-31)
│  └────────── hour (0-23)
└───────────── minute (0-59)

Special chars: * (any)  , (list)  - (range)  / (step)

Every minute:    * * * * *
Every 5 min:     */5 * * * *
At midnight:     0 0 * * *
At 2am daily:    0 2 * * *
Weekdays 9am:    0 9 * * 1-5
Sundays 3am:     0 3 * * 0
1st of month:    0 0 1 * *

That's it. Print it, paste it in your team wiki, or just keep this tab open — your call.