Cron Expression Syntax Cheat Sheet: 15 Real Crontab Examples
The five fields of a cron expression, 15 crontab examples I actually use in production, and the three gotchas (DST, timezone, missing newline) that have bitten me at 3 am.
Cron Expression Syntax Cheat Sheet: 15 Real Crontab Examples
Cron is one of those tools where the syntax looks like line noise for about 20 minutes, then becomes second nature, and then you forget that other people don't read */5 1-5 * * 1-5 as fluently as English. This post is the bridge from line noise to fluent — five fields, 15 patterns I actually use, and three gotchas that have woken me up.
If you want to verify expressions as you read, the cron expression explainer translates any line into plain English in real time.
The Five Fields
A standard cron line has five space-separated fields, plus the command:
┌─────────── minute (0–59)
│ ┌───────── hour (0–23)
│ │ ┌─────── day of month (1–31)
│ │ │ ┌───── month (1–12 or JAN–DEC)
│ │ │ │ ┌─── day of week (0–6, 0 = Sunday, or SUN–SAT)
│ │ │ │ │
* * * * * command
Each field accepts:
*— every value.- A single number — exactly that value.
- A list like
1,3,5— any of these. - A range like
9-17— inclusive both ends. - A step like
*/15— every 15. Combine with ranges:0-30/5is 0, 5, 10, 15, 20, 25, 30.
Two important quirks before the examples:
- Day-of-month and day-of-week are OR, not AND. If both fields are restrictive (not
*), the job runs when either matches.0 12 13 * 5runs at noon on the 13th and every Friday at noon. This catches everyone once. - There is no "every other day" in standard cron.
*/2in the day-of-month field means days 1, 3, 5, … 29, 31 — which gives you back-to-back runs across month boundaries. If you need true every-48-hours, use systemd timers or a wrapper script that checks a state file.
15 Crontab Patterns I Actually Use
# 1. Every minute (smoke tests, health-check pingers)
* * * * * /usr/local/bin/healthcheck.sh
# 2. Every 5 minutes
*/5 * * * * /usr/local/bin/queue-poller.sh
# 3. Every 15 minutes during business hours, weekdays only
*/15 9-17 * * 1-5 /usr/local/bin/sales-sync.sh
# 4. Every hour at the top of the hour
0 * * * * /usr/local/bin/hourly-rollup.sh
# 5. Every hour at :17 (offset to dodge thundering herd)
17 * * * * /usr/local/bin/cache-warm.sh
# 6. Every 6 hours
0 */6 * * * /usr/local/bin/cleanup-temp.sh
# 7. 03:00 every day (nightly batch)
0 3 * * * /usr/local/bin/nightly-report.sh
# 8. 03:00 every weekday, skip weekends
0 3 * * 1-5 /usr/local/bin/business-day-only.sh
# 9. 09:00 every Monday
0 9 * * 1 /usr/local/bin/weekly-digest.sh
# 10. 00:00 on the 1st of every month
0 0 1 * * /usr/local/bin/monthly-invoice.sh
# 11. 00:00 on the last day of the month (with the LAST hack)
55 23 28-31 * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh
# 12. Every quarter (Jan, Apr, Jul, Oct on the 1st at 02:00)
0 2 1 1,4,7,10 * /usr/local/bin/quarterly.sh
# 13. Twice a day — 06:00 and 18:00
0 6,18 * * * /usr/local/bin/twice-daily.sh
# 14. @reboot — run once when the machine comes up
@reboot /usr/local/bin/start-tunnel.sh
# 15. Every 2 minutes, but only between 02:00 and 04:00 (low-traffic window)
*/2 2-3 * * * /usr/local/bin/heavy-job.sh
Pattern 11 — "last day of the month" — is the one I have to look up every time, because vanilla cron has no day-of-month "last." The trick is to run every day from the 28th to the 31st, then have the command itself check whether tomorrow is the 1st. Ugly, but reliable on every cron implementation.
If you'd rather not hand-craft these, the crontab helper is a visual builder — pick frequency, pick range, get the line. I still keep these 15 as a paste-bin because typing is faster than clicking once you know what you want.
The Three Gotchas
Gotcha 1: Daylight Saving Time
Most cron implementations run in local time. On the "spring forward" night when 02:00 jumps to 03:00, a job scheduled for 0 2 * * * will not run that day. On the "fall back" night when 02:00 happens twice, the same job will run twice.
Fixes, in order of preference:
- Schedule batch jobs at 03:00 or later. The DST jumps in most jurisdictions are around 02:00–03:00; staying above that window dodges both.
- Run cron in UTC. Set
CRON_TZ=UTCat the top of the crontab (works on Vixie cron and successors). The job then sees no DST at all. - Use systemd timers, which have explicit
OnCalendar=semantics around DST.
I learned this when a monthly billing report skipped one March. Took two cycles to notice. Now everything financial runs at 04:00 UTC and never moves.
Gotcha 2: PATH and Environment
A cron job runs with a minimal environment — typically just HOME, LOGNAME, PATH=/usr/bin:/bin, and SHELL=/bin/sh. Your ~/.bashrc is not sourced. Your nvm setup is not loaded. Your custom binaries in /usr/local/bin may or may not be on PATH depending on the cron daemon.
Symptoms: works fine when you run it from the shell, silently does nothing from cron. The fix is one of:
- Use absolute paths for every binary in the command.
- Set
PATH=explicitly at the top of the crontab. - Have the cron line call a wrapper script whose first line is
source /home/you/.bashrcor whatever bootstrap you need.
The kind of one-liners that look fine in your terminal but die in cron usually involve awk, sed, find, or xargs invoked without full paths. The awk/sed cheatsheet is what I keep open when debugging these — the syntax is exact in a way that interactive shell forgiveness hides.
Gotcha 3: The Missing Newline
A crontab file must end with a newline. Some cron daemons silently ignore the last line if it doesn't have one. The symptom is "my new job isn't running and the other jobs are fine." I've lost an embarrassing hour to this one.
Fix: always edit with crontab -e (which appends a newline for you if missing) instead of piping in with crontab < file. If you must pipe, end the file with a literal blank line.
Related: the file needs to be readable by the cron daemon and the user running it, the user must be in /etc/cron.allow if that file exists, and SELinux on RHEL-family distros may block scripts in unusual locations. Standard bash debugging patterns apply — start with 2>&1 | logger -t myjob on the command and watch /var/log/syslog (or journalctl -t myjob) while it runs.
A Final Tip: Always Log Output
The single best thing you can do for future-you is redirect every cron job's output somewhere you can read later. Cron will email it to the local user mailbox by default, which on most servers nobody reads. Better:
0 3 * * * /usr/local/bin/nightly-report.sh >> /var/log/nightly-report.log 2>&1
Three months from now when you're debugging why the report stopped, having a log file makes the difference between "two-minute fix" and "two-hour archaeology."
Related tools
Made by Toolora · Updated 2026-05-26