Cron Job Examples: 20 Expressions From Basic to Advanced
Quick answer: A cron expression has 5 fields:minute hour day-of-month month day-of-week. For example,0 3 <em> </em> <em>runs at 3:00 AM daily,</em>/15 <em> </em> <em> </em>runs every 15 minutes, and0 0 <em> </em> 0runs at midnight every Sunday. See the full reference table below with 20 common expressions.
I once set a cron job to <em> </em> <em> </em> <em> thinking it meant "run once." It ran every minute. For 6 hours. Until the server ran out of disk space from log files. The database backup script I'd pointed it at generated a 2GB dump file each run. That's 720GB of backup files before someone noticed.
Cron syntax is compact and powerful, but one wrong character turns a weekly job into a per-minute disaster. Here's every expression you'll realistically need, explained in plain English.
How Cron Syntax Works
A cron expression is 5 fields separated by spaces:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
│ │ │ │ │
* * * * *
Special characters:
| Character | Meaning | Example |
|---|---|---|
</em> | Every value | <em> </em> <em> </em> <em> = every minute |
, | List of values | 1,15 = on the 1st and 15th |
- | Range of values | 1-5 = Monday through Friday |
/ | Step values | </em>/10 = every 10th unit |
@ symbols, no keywords (those are crontab shortcuts, not cron expressions). Let me walk through 20 real examples.
The Big Reference Table
Here are 20 cron expressions sorted from most frequent to least frequent, with the actual use case for each:
| # | Expression | Schedule | Use Case |
|---|---|---|---|
| 1 | <em> </em> <em> </em> <em> | Every minute | Health checks, uptime monitoring |
| 2 | </em>/5 <em> </em> <em> </em> | Every 5 minutes | Queue processing, cache refresh |
| 3 | <em>/15 </em> <em> </em> <em> | Every 15 minutes | API polling, metrics collection |
| 4 | </em>/30 <em> </em> <em> </em> | Every 30 minutes | Report generation, data sync |
| 5 | 0 <em> </em> <em> </em> | Every hour (on the hour) | Log rotation, temp file cleanup |
| 6 | 0 <em>/2 </em> <em> </em> | Every 2 hours | Sitemap regeneration, feed updates |
| 7 | 0 <em>/6 </em> <em> </em> | Every 6 hours | DNS record checks, SSL cert monitoring |
| 8 | 0 0 <em> </em> <em> | Midnight daily | Database backups, daily reports |
| 9 | 0 3 </em> <em> </em> | 3:00 AM daily | Heavy maintenance (low traffic window) |
| 10 | 30 4 <em> </em> <em> | 4:30 AM daily | Log archiving, analytics aggregation |
| 11 | 0 9 </em> <em> 1-5 | 9:00 AM weekdays | Business hour alerts, Slack digests |
| 12 | 0 9,17 </em> <em> 1-5 | 9 AM and 5 PM weekdays | Start/end of business day reports |
| 13 | 0 0 </em> <em> 0 | Midnight Sunday | Weekly database optimization, full backups |
| 14 | 0 2 </em> <em> 1 | 2:00 AM Monday | Weekly log cleanup, report emails |
| 15 | 0 0 1,15 </em> <em> | Midnight on 1st and 15th | Bi-monthly billing, invoice generation |
| 16 | 0 0 1 </em> <em> | Midnight on the 1st | Monthly reports, account renewals |
| 17 | 0 0 1 </em>/3 <em> | Midnight, 1st of every 3rd month | Quarterly reports, compliance audits |
| 18 | 0 0 1 1 </em> | Midnight, January 1st | Annual cleanup, yearly report generation |
| 19 | 0 0 <em> </em> 1-5 | Midnight weekdays only | Weekday-only batch processing |
| 20 | 0 <em>/4 </em> <em> 0,6 | Every 4 hours on weekends | Reduced-frequency weekend monitoring |
Every-Minute and Sub-Hourly Jobs
Every minute: </em> <em> </em> <em> </em>
* * * * * /usr/local/bin/health-check.sh
This runs your command at 00:00, 00:01, 00:02... every single minute. Use this for lightweight scripts only — health checks, queue length monitoring, or checking if a process is still alive. If your script takes more than 60 seconds to run, the next invocation starts before the previous one finishes. That's how you get cascading failures.
Guard against overlap by adding a lock file:
#!/bin/bash
LOCKFILE=/tmp/health-check.lock
if [ -f "$LOCKFILE" ]; then exit 0; fi
touch "$LOCKFILE"
# ... your script ...
rm "$LOCKFILE"
Every 5 minutes: <em>/5 </em> <em> </em> <em>
*/5 * * * * /usr/local/bin/process-queue.sh
The /5 means "every 5th minute" — so at :00, :05, :10, :15, and so on. This is the most common interval for queue workers, cache invalidation, and lightweight data syncing. It runs 288 times per day — frequent enough for near-real-time processing, infrequent enough to not hammer your server.
Every 15 minutes: </em>/15 <em> </em> <em> </em>
*/15 * * * * /usr/local/bin/fetch-metrics.sh
Runs at :00, :15, :30, :45 every hour. Good for API polling when you don't need per-minute granularity. Many third-party APIs have rate limits that align well with 15-minute intervals — 96 requests per day is usually well within free tier limits.
Daily Jobs
Midnight: 0 0 <em> </em> <em>
0 0 * * * /usr/local/bin/daily-backup.sh
The classic daily job. 0 0 means minute 0, hour 0 — midnight. Most teams run backups, report generation, and log aggregation here.
Problem: if every job on your server runs at midnight, they all compete for CPU and disk I/O at the same time. Stagger your jobs. Run backups at midnight, log rotation at 1 AM, report generation at 2 AM. A 30-minute gap between heavy jobs prevents resource contention.
Low-traffic window: 0 3 </em> <em> </em>
0 3 * * * /usr/local/bin/heavy-maintenance.sh
3 AM is the lowest traffic period for US-audience sites (roughly 2-5 AM Eastern). Schedule CPU-intensive tasks here — database vacuuming, full-text search index rebuilds, image optimization batches. If your audience is global, there's no true "low traffic" time, so pick one and monitor the impact.
Business hours only: 0 9 <em> </em> 1-5
0 9 * * 1-5 /usr/local/bin/slack-digest.sh
1-5 in the day-of-week field means Monday through Friday (1=Monday, 5=Friday). This expression runs at 9:00 AM on weekdays only. Perfect for business alerts, daily standup summaries, or Slack notifications that nobody wants on Saturday.
Weekly Jobs
Every Sunday at midnight: 0 0 <em> </em> 0
0 0 * * 0 /usr/local/bin/weekly-optimize.sh
Sunday is day 0 (or 7 on some systems — both work for Sunday). Use this for weekly database optimization (VACUUM ANALYZE on Postgres, OPTIMIZE TABLE on MySQL), full backup rotations, or weekly email digests.
Note on day-of-week numbering: POSIX cron uses 0=Sunday, 6=Saturday. Some implementations also accept 7=Sunday. To avoid confusion, I always use 0 for Sunday. If you need Monday through Friday, use 1-5.
Monday morning: 0 2 <em> </em> 1
0 2 * * 1 /usr/local/bin/weekly-cleanup.sh
2 AM Monday. Good for cleaning up the previous week's temporary files, rotating old logs, and sending weekly summary emails. Running cleanup at the start of the week (rather than the end) means weekend activity is captured in the cleanup window.
Monthly and Quarterly Jobs
First of the month: 0 0 1 <em> </em>
0 0 1 * * /usr/local/bin/monthly-report.sh
Day 1, midnight. Monthly reports, billing cycles, subscription renewals, and data archiving all land here. Same staggering advice as daily jobs — if you have 5 monthly tasks, spread them across the first 5 hours of the month rather than running them all at midnight.
Every quarter: 0 0 1 <em>/3 </em>
0 0 1 */3 * /usr/local/bin/quarterly-audit.sh
<em>/3 in the month field means every 3rd month — January, April, July, October. This runs at midnight on the 1st of each quarter. Compliance audits, quarterly financial reports, and data retention policy enforcement are typical use cases.
Twice a month: 0 0 1,15 </em> <em>
0 0 1,15 * * /usr/local/bin/billing-run.sh
The comma separates a list of values — this runs on the 1st and 15th of every month. Common for bi-monthly payroll processing, invoice generation, and semi-monthly data exports. If you need true "every 2 weeks" (which doesn't align with month boundaries), cron can't express that — you'd need to handle it in the script logic.
Advanced Patterns
Multiple specific times: 0 9,12,17 </em> <em> 1-5
0 9,12,17 * * 1-5 /usr/local/bin/check-deployments.sh
Runs at 9 AM, 12 PM, and 5 PM on weekdays. The comma in the hour field creates a list. Useful for deployment health checks at business-critical hours or sending periodic status updates to Slack.
Last day of the month (workaround)
Cron has no native "last day of the month" syntax. The standard workaround:
0 0 28-31 * * /usr/local/bin/check-last-day.sh
Then in your script, check if tomorrow is the 1st:
#!/bin/bash
if [ "$(date -d tomorrow +\%d)" = "01" ]; then
# It's the last day of the month
/usr/local/bin/month-end-report.sh
fi
This approach runs the cron job on the 28th through 31st, but the script only executes the actual work on the true last day. It handles February (28/29), April (30), and December (31) correctly.
Different schedules for weekdays vs weekends
0 */2 * * 1-5 /usr/local/bin/monitor.sh # Every 2 hours weekdays
0 */4 * * 0,6 /usr/local/bin/monitor.sh # Every 4 hours weekends
You can't express "different intervals for different days" in a single cron expression. Use two separate entries. This pattern is common for monitoring jobs where weekday incidents need faster detection than weekends.
Crontab Shortcuts
Most cron implementations support these shorthand expressions:
| Shorthand | Equivalent | Meaning |
|---|---|---|
@yearly | 0 0 1 1 </em> | Once a year, January 1st |
@monthly | 0 0 1 <em> </em> | Once a month, 1st at midnight |
@weekly | 0 0 <em> </em> 0 | Once a week, Sunday at midnight |
@daily | 0 0 <em> </em> <em> | Once a day at midnight |
@hourly | 0 </em> <em> </em> <em> | Once an hour at minute 0 |
@reboot | — | Once at system startup |
@reboot is the most useful shorthand. It runs your command once when the system boots — perfect for starting background services, mounting drives, or initializing application state. No 5-field equivalent exists for this one.
Common Mistakes
Forgetting the PATH. Cron runs with a minimal environment. python, node, or pg_dump might not be in cron's PATH even though they work in your terminal. Always use absolute paths: /usr/bin/python3, /usr/local/bin/node, /usr/bin/pg_dump.
Not redirecting output. Cron emails stdout and stderr to the system user's mailbox by default. On most modern systems, that mailbox is never checked. Redirect output explicitly:
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Timezone confusion. Cron runs in the system's local timezone unless configured otherwise. A server in UTC running 0 9 </em> <em> </em> fires at 9 AM UTC — that's 4 AM Eastern, 1 AM Pacific. Check timedatectl or date on your server to confirm the timezone before scheduling business-hours jobs.
Day-of-month AND day-of-week together. In standard cron, if both fields are set to non-<em> values, the job runs when EITHER condition matches (OR logic, not AND). 0 0 1 </em> 5 doesn't mean "the 1st that falls on a Friday" — it means "every 1st of the month AND every Friday." This trips up everyone at least once.
FAQ
What's the minimum interval for a cron job?
One minute. The smallest unit in the 5-field cron syntax is minutes. If you need sub-minute execution (every 30 seconds, every 10 seconds), cron can't do it natively. The common workaround is a cron job that runs every minute and calls a script with a sleep loop inside it: run the task, sleep 30 seconds, run it again. For true sub-second scheduling, use systemd timers or a dedicated job scheduler like Celery or Bull.
How do I list all active cron jobs?
Run crontab -l to see the current user's cron jobs. For root jobs: sudo crontab -l. But that's not the full picture — cron jobs can also live in /etc/crontab, /etc/cron.d/, /etc/cron.daily/, /etc/cron.hourly/, /etc/cron.weekly/, and /etc/cron.monthly/. Check all of these locations to get the complete list. On systems using systemd, also check systemctl list-timers for timer-based scheduled tasks that bypass cron entirely.
Can I run a cron job every 2 weeks?
Not directly. Cron has no "every N weeks" syntax. The month and day-of-week fields don't support multi-week intervals. Workaround: set a weekly cron job and check the week number in your script: if [ $(($(date +%V) % 2)) -eq 0 ]; then .... Or use a state file that tracks the last run date and skips execution if fewer than 14 days have passed.
What happens if my cron job takes longer than the interval?
Cron doesn't care — it starts a new instance regardless. If a job scheduled every 5 minutes takes 8 minutes to run, you'll have overlapping executions. After a few cycles, you might have 3-4 instances running simultaneously, competing for CPU, memory, and database connections. Use a lock file (as shown above) or flock to prevent overlap: <em>/5 </em> <em> </em> * /usr/bin/flock -n /tmp/job.lock /usr/local/bin/my-script.sh.
Next Steps
- Build and validate cron expressions visually with the cron generator — it translates your expression to plain English in real time
- Convert timestamps for timezone-aware scheduling with the unix timestamp converter
- Make sure your scheduled scripts have clean, parseable URL slugs if they generate web-accessible reports