---
topic: tooling
author: Crashtech Editorial
published: Nov 13, 2026 · read: 3 min
---

Cron Expressions and the Gotchas That Bite in Production

Five fields, four special characters, and a handful of traps: the OR rule, the tiny environment, silent output, overlapping runs and time zones.

– –

Everyone can write */5 * * * *. Fewer people can say why their backup job ran on the wrong days, or why a script that works in the shell does nothing under cron. Here is the syntax in one screen, then the traps.

The format

# ┌───────── minute        (0-59)
# │ ┌─────── hour          (0-23)
# │ │ ┌───── day of month  (1-31)
# │ │ │ ┌─── month         (1-12)
# │ │ │ │ ┌─ day of week   (0-7, 0 and 7 are Sunday)
# │ │ │ │ │
# * * * * *  command
CharacterMeaningExample
*Every value* * * * * every minute
,List0 9,17 * * * at 09:00 and 17:00
-Range0 9 * * 1-5 09:00 on weekdays
/Step*/15 * * * * every 15 minutes

Common examples:

30 2 * * *        02:30 every day
0 0 1 * *         midnight on the 1st of each month
*/10 8-18 * * 1-5 every 10 minutes, 08:00 to 18:59, weekdays

Many implementations also accept shortcuts such as @reboot, @hourly, @daily, @weekly, @monthly.

Gotcha 1: day-of-month OR day-of-week

If you restrict both day fields, cron runs the job when either matches, not both. 0 9 13 * 5 does not mean “09:00 on Friday the 13th”. It means “09:00 on the 13th of every month, and also every Friday”. For “the first Monday” style schedules, run daily and test the date inside the script.

Gotcha 2: the tiny environment

Cron starts jobs with a minimal environment: a short PATH, no login shell, often a different working directory. A script that calls node or python may not find it.

Fix it in the crontab, not by hoping

Use absolute paths for commands and files, set PATH at the top of the crontab, and cd into the right directory inside the job. Test by running the command with a stripped environment (env -i) before you trust it.

Gotcha 3: silent failures

By default, output may be mailed to a local user nobody reads, or dropped. Redirect it explicitly, and make failures visible:

*/5 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1

Better still, have the job report success or failure to a monitor (a heartbeat URL or a metrics push), so a job that stops running triggers an alert instead of silence.

Advertisement

Gotcha 4: overlapping runs

Cron does not care that the last run is still going. A job that sometimes takes 12 minutes on a 10-minute schedule will start a second copy and then a third. Guard it with a lock:

*/10 * * * * flock -n /tmp/sync.lock /usr/local/bin/sync.sh

With -n, a second instance exits immediately when the lock is held.

Gotcha 5: time zones and daylight saving

Cron uses the system’s time zone. Around daylight saving changes, a job scheduled in the skipped hour may not run, and one in the repeated hour may run twice. For anything important, run servers in UTC and convert in your head once, not twice a year.

When cron is the wrong tool

NeedBetter fit
Retry, backoff, dependencies between jobsA workflow or job queue system
Many machines, run exactly onceA distributed scheduler with locking
Event-driven workTriggers, queues, webhooks
Sub-minute schedulesAn in-process scheduler or a worker loop

Do

Treat every cron job as production code: absolute paths, logged output, a lock for long runs, UTC, and a monitor that alerts when it stops.

Don't

Paste a schedule you cannot read back. Explain it in a comment, and check it with a schedule previewer before deploying.

Takeaway

The syntax is the easy part. Cron’s real contract is: run this command at matching times, with a bare environment, and tell nobody what happened. Everything that goes wrong lives in what it does not do for you, so supply those pieces yourself.

Advertisement

Frequently asked questions

What are the five fields of a cron expression?

In order: minute (0 to 59), hour (0 to 23), day of month (1 to 31), month (1 to 12), and day of week (0 to 7, where 0 and 7 are Sunday). A command runs when the current time matches all the fields, with the day-of-month and day-of-week caveat described in the crontab manual.

What does */5 mean in cron?

A step value: every 5th unit of that field. In the minute field, */5 means minutes 0, 5, 10 and so on, every five minutes. The slash sets the step, the asterisk is the full range, and you can combine with a range such as 10-30/5.

Why does my script work in the terminal but fail in cron?

Cron runs jobs with a minimal environment: a short PATH, no interactive shell setup, and a different working directory. Use absolute paths for commands and files, set PATH and variables explicitly in the crontab, and test with the same minimal environment.

What happens if a cron job is still running when the next one starts?

Cron starts another instance anyway. Overlapping runs can corrupt data or pile up. Guard long jobs with a lock, for example flock around the command, so a second run exits or waits instead of running concurrently.

Sources & further reading

/* Comments */