A cron expression is five characters away from being perfectly readable — and five characters away from scheduling your backup script to run every second instead of every Sunday. Cron is the standard job scheduler on Linux and Unix systems, and its expression syntax is something most developers memorize through trial and error rather than intention. That's how you end up with a production cron job that fires 1,440 times a day instead of once.
This cheat sheet breaks down cron expression syntax into a reference you can actually use. Every example below is copy-paste ready. If you want to validate your expressions before deploying them, the PinusX Cron Expression Parser shows the next execution times in plain English so you can confirm the schedule does what you think it does — all processed in your browser, no server involved.
Cron Syntax: The Five Fields
A standard cron expression has five fields separated by spaces. Each field represents a time unit, read left to right from smallest to largest:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, where 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
The asterisk (*) means "every value." So * * * * * runs every minute of every hour of every day. Most of the cron expressions you'll write replace one or more of those asterisks with specific values or patterns.
Special Characters
Four special characters let you build schedules beyond simple fixed times:
| Character | Meaning | Example | Result |
|---|---|---|---|
* | Every value | * * * * * | Every minute |
, | List of values | 0 8,12,18 * * * | At 8 AM, 12 PM, and 6 PM |
- | Range of values | 0 9-17 * * * | Every hour from 9 AM to 5 PM |
/ | Step interval | */15 * * * * | Every 15 minutes |
These characters combine freely. 0 9-17/2 * * 1-5 means "at minute 0, every 2 hours from 9 AM through 5 PM, Monday through Friday." That's a business-hours-only schedule with a single expression.
20 Cron Expression Examples You'll Actually Use
These cover the schedules that show up repeatedly in real infrastructure. Copy the expression, verify it with the PinusX Cron Parser, and drop it into your crontab.
| Schedule | Expression | Notes |
|---|---|---|
| Every minute | * * * * * | Health checks, queue polling |
| Every 5 minutes | */5 * * * * | Monitoring, cache refresh |
| Every 15 minutes | */15 * * * * | API syncs, report generation |
| Every 30 minutes | */30 * * * * | Data aggregation |
| Every hour (on the hour) | 0 * * * * | Hourly log rotation |
| Every 6 hours | 0 */6 * * * | DNS refresh, cert checks |
| Daily at midnight | 0 0 * * * | Backups, daily reports |
| Daily at 3 AM | 0 3 * * * | Off-peak maintenance |
| Twice daily (9 AM and 6 PM) | 0 9,18 * * * | Business-hours notifications |
| Every weekday at 8 AM | 0 8 * * 1-5 | Morning deployment checks |
| Every Monday at 9 AM | 0 9 * * 1 | Weekly status reports |
| Every Friday at 5 PM | 0 17 * * 5 | End-of-week summaries |
| Weekends only at noon | 0 12 * * 0,6 | Weekend maintenance windows |
| 1st of every month at midnight | 0 0 1 * * | Monthly billing, reports |
| Every quarter (Jan, Apr, Jul, Oct 1st) | 0 0 1 1,4,7,10 * | Quarterly cleanup |
| Every 10 min during business hours | */10 9-17 * * 1-5 | Business-hours monitoring |
| First Monday of each month at 6 AM | 0 6 1-7 * 1 | Runs when the 1st-7th is a Monday |
| Last day of month (28th, approx) | 0 0 28 * * | Cron has no "last day" — use 28th as safe default |
| Every January 1st at midnight | 0 0 1 1 * | Annual tasks, license renewal |
| Every Sunday at 2 AM | 0 2 * * 0 | Weekly database vacuum |
Cron Gotchas That Catch Everyone
Timezone Confusion
Cron jobs run in the system's timezone by default — not UTC, not your local timezone, but whatever timedatectl reports on the server. If your server is set to UTC but you're thinking in US Eastern time, your "midnight backup" runs at 7 PM or 8 PM Eastern depending on daylight saving time. Always check the server timezone with date or timedatectl before writing cron schedules. Better yet, set all servers to UTC and do the timezone math explicitly.
Day-of-Week Numbering
Sunday is both 0 and 7 in most cron implementations, but not all. Some systems use 1 for Monday through 7 for Sunday. Others start with 0 for Sunday. The POSIX standard says 0 = Sunday, and that's what Linux cron uses. But if you're using a cron library in Node.js or Python, check the documentation — node-cron and cron npm packages differ on this. Use named days (SUN, MON) when your cron implementation supports them to avoid ambiguity.
Day-of-Month vs Day-of-Week: The OR Trap
When you set both the day-of-month and day-of-week fields, cron treats them as an OR condition, not AND. The expression 0 0 15 * 5 doesn't mean "midnight on the 15th if it's a Friday." It means "midnight on the 15th of every month, AND midnight every Friday." That's more executions than you expected. If you need both conditions, you'll need to handle the logic in your script itself.
Overlapping Executions
If your cron job takes 10 minutes to complete but runs every 5 minutes, you'll have overlapping instances fighting over the same resources — writing to the same files, hitting the same API rate limits, or corrupting shared data. Cron doesn't care whether the previous run finished. It fires on schedule regardless.
The standard fix is a lockfile:
#!/bin/bash
LOCKFILE="/tmp/my-job.lock"
if [ -f "$LOCKFILE" ]; then
echo "Previous run still active, skipping"
exit 0
fi
trap "rm -f $LOCKFILE" EXIT
touch "$LOCKFILE"
# Your actual job here
/usr/local/bin/sync-data.sh
Or use flock, which handles edge cases like stale lockfiles from crashed processes:
# In crontab:
*/5 * * * * flock -n /tmp/my-job.lock /usr/local/bin/sync-data.sh
No "Last Day of Month" Syntax
Standard cron has no way to express "the last day of the month." Since months have 28, 29, 30, or 31 days, you can't use a fixed number. The common workaround is to run a daily job that checks whether tomorrow is the 1st:
# Run at 11:55 PM daily, but only execute if tomorrow is the 1st
55 23 * * * [ "$(date -d tomorrow +%d)" = "01" ] && /path/to/monthly-job.sh
Crontab Quick Reference
Managing cron jobs on a Linux system uses the crontab command:
# Edit your user's crontab
crontab -e
# List current cron jobs
crontab -l
# Remove all cron jobs (careful!)
crontab -r
# Edit another user's crontab (requires root)
sudo crontab -u www-data -e
Each line in a crontab file follows the format: expression command. Lines starting with # are comments. You can set environment variables at the top of the file — MAILTO="" suppresses the email output that cron sends by default when a job produces output.
Test Before You Deploy
The single most useful habit for working with cron expressions is to verify them before they hit production. The PinusX Cron Expression Parser takes any cron expression and shows you the next 10 execution times in plain English. Paste in 0 9-17/2 * * 1-5 and see exactly when it fires — no mental arithmetic required, no waiting until the next scheduled run to confirm it worked.
For related DevOps tasks, the PinusX Chmod Calculator helps when you're setting permissions on the scripts your cron jobs execute (a 644 script won't run — you need 755 or at least u+x). And when you're debugging JSON config files that your cron jobs consume, the PinusX JSON Formatter processes everything client-side so your production configs stay off third-party servers.