Common Cron Expression Mistakes and How to Prevent Devastating Production Scheduling Outages
Cron is one of the oldest, most reliable background automation daemons in computing history. Yet despite fifty years of continuous production deployment, cron scheduling remains a fertile breeding ground for catastrophic production outages. Because cron jobs run unattended at unsociable hours, a subtle syntax flaw or misunderstood step operator rarely causes an immediate noisy exception during deployment; instead, it strikes silently at 02:00 on Sunday morning when customer traffic surges or financial billing batches lock up your database.
Author Note: One easy mistake with cron expressions is confusing the positions of the fields or assuming every scheduler uses exactly the same syntax. Another is forgetting to consider which timezone the scheduler uses. I recommend checking the scheduler's documentation and testing the expression before relying on it for an important recurring task.
1. Dialect Pitfall: Standard Unix 5-Field vs Quartz & AWS 6-Field
The most ubiquitous source of confusion in modern cloud engineering is the clash of cron dialects. A developer copying a schedule string from an AWS Lambda EventBridge tutorial into a Linux crontab will frequently crash their automation:
| Dialect / Platform | Field Count | Structure (Left to Right) | Key Idiosyncrasy |
|---|---|---|---|
| Standard Unix (Vixie Cron) | 5 Fields | Min Hour DOM Month DOW | DOM and DOW are evaluated with OR logic when both are specified. |
| Quartz / Spring Scheduler | 6 Fields | Sec Min Hour DOM Month DOW | Requires ? (no specific value) in either DOM or DOW. |
| AWS EventBridge | 6 Fields | Min Hour DOM Month DOW Year | Does not support seconds; mandatory ? in day field. |
2. The "Every 5 Minutes" Step Operator Trap
Engineers frequently write 5 * * * * when they intend a task to trigger every five minutes.
WRONG: 5 * * * *
This expression does not trigger every five minutes. It triggers exactly once per hour, specifically when the clock's minute hand strikes 5 (01:05, 02:05, 03:05...).
CORRECT: */5 * * * *
The slash (/) step operator indicates an interval: "every minute divisible by 5" (00, 05, 10, 15, 20... 55).
3. The Daylight Saving Time (DST) Nightmare
If your production servers operate on local timezones rather than Universal Time Coordinated (UTC), scheduling cron jobs between 02:00 and 03:00 is an operational gamble:
- Spring Forward: When clocks advance from 01:59:59 directly to 03:00:00, the entire 2:00 AM hour is skipped. Any job scheduled for
30 2 * * *will never execute on that date. - Fall Back: When clocks retreat from 02:59:59 back to 02:00:00, the 2:00 AM hour occurs twice. A payroll or invoice batch scheduled for
15 2 * * *will run twice, charging customers double or corrupting ledger balances.
Golden Rule of Infrastructure: Always configure production host operating systems, container runtimes, and crontabs to UTC (timedatectl set-timezone UTC).
4. The Day-of-Month vs Day-of-Week OR Logic Quirk
Standard Unix Vixie cron evaluates fields using an AND relationship—except when both the Day of Month (DOM) and Day of Week (DOW) fields are specified. In that scenario, cron switches from AND logic to OR logic.
# Intended goal: "Run only on Friday the 13th at midnight"
0 0 13 * 5 /app/audit.sh
# ACTUAL BEHAVIOR in Unix Crontab:
Executes on the 13th of the month OR on ANY Friday of the month!
To enforce true AND logic between day-of-month and day-of-week, you must defer the condition to shell evaluation:
0 0 13 * * [ $(date +\%u) -eq 5 ] && /app/audit.sh
5. Concurrency Catastrophe: Overlapping Executions
Cron has no built-in awareness of task duration. If you configure a heavy synchronization script to run every 10 minutes (*/10 * * * *), and a database slowdown causes one run to take 14 minutes, cron will blindly spawn a second instance while the first is still processing tables. Within two hours, fifty zombie instances will contend for database locks, exhausting server memory and inducing a total outage.
# Robust, non-overlapping cron execution using linux flock (file lock):
*/10 * * * * /usr/bin/flock -n /var/lock/sync_db.lock /usr/local/bin/python3 /app/sync.py >> /var/log/sync.log 2>&1
The -n (non-blocking) flag instructs flock to exit immediately if the lockfile is held by a previous execution, preventing concurrency storms entirely.
6. The Cron Environment Isolation Trap
A script that runs flawlessly when you execute it manually from your SSH terminal session will frequently crash when executed by cron. Why? Because cron executes in an extremely stripped-down, non-interactive subshell:
$PATHdefaults to/usr/bin:/binonly. Commands located in/usr/local/bin,~/.nvm,~/.cargo/bin, or Python virtual environments will fail withcommand not found.- Environment variables defined in
~/.bashrc,~/.zshrc, or/etc/profileare never loaded by the cron daemon. - Current working directory (
$PWD) is set to the user's home directory (/rootor/home/user), breaking relative file paths.
7. The Clean Minimal Crontab Production Template
To ensure deterministic behavior across cloud servers, follow this production crontab blueprint:
# 1. Declare explicit shell and environment variables SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin [email protected] # 2. Database Backup: Every day at 03:30 UTC with non-blocking lock 30 3 * * * /usr/bin/flock -n /tmp/backup.lock /usr/local/bin/backup-postgres.sh >> /var/log/backup.log 2>&1 # 3. Cache Pruning: Every Monday morning at 04:00 UTC 0 4 * * 1 /usr/bin/flock -n /tmp/cache_prune.lock /app/scripts/prune-redis.sh >> /var/log/prune.log 2>&1
8. Disk Space Exhaustion: The Unrotated Log Trap
When redirecting cron standard output and standard error to a log file (e.g. >> /var/log/myjob.log 2>&1), that file will grow continuously with every single execution. In high-frequency jobs running every minute (1,440 times per day), a chatty script printing stack traces or API responses will generate gigabytes of logs within weeks, eventually filling the server's root partition (/).
When root disk usage reaches 100%, MySQL and PostgreSQL will panic and enter read-only recovery mode, crashing your application. Always pair crontab logging with a dedicated /etc/logrotate.d/cronjobs configuration specifying rotate 7, daily, and compress.
9. Online Validation & Testing Tools
Before committing any cron expression to your infrastructure repository, verify it using our interactive tools:
Have feedback or questions on this guide?
Our engineering guides are continually refined with real production feedback.