If you have ever needed to run a script at midnight, send a report every Monday morning, or rotate log files on the first day of each month, you have encountered cron. It is one of the oldest and most reliable scheduling utilities in Unix and Linux, and its compact five-field syntax is the standard that nearly every modern scheduler—from Kubernetes CronJobs to GitHub Actions—borrows from.
The problem? That compact syntax can look like hieroglyphics the first time you see it. 0 */4 1-15 * 1,3,5 is not exactly self-documenting. This guide will break down every symbol, every field, and every edge case so you never have to guess again.
Want to skip the theory? Jump straight into NexTool's free Crontab Generator to build and validate cron expressions visually in your browser.
What Is Cron?
Cron is a time-based job scheduler found on virtually every Unix-like operating system. The cron daemon (crond or simply cron) runs in the background, waking up every minute to check a set of schedule tables called crontabs. When the current date and time match a rule in the crontab, cron executes the associated command.
There are two places cron jobs can live:
- User crontabs — edited with
crontab -e, stored in/var/spool/cron/crontabs/. Each user has their own file. Jobs run as that user. - System crontab — the file
/etc/crontabplus drop-in files in/etc/cron.d/. These include an extra field for the user name and are managed by root.
For the rest of this article, we focus on the user crontab format since that is what you will work with 90% of the time.
The 5-Field Cron Syntax — Anatomy of an Expression
Every cron schedule is defined by five fields followed by the command to execute. Here is the structure laid out visually:
+---------------- minute (0 - 59)
| +------------- hour (0 - 23)
| | +---------- day of month (1 - 31)
| | | +------- month (1 - 12) OR jan,feb,...,dec
| | | | +---- day of week (0 - 7) OR sun,mon,...,sat
| | | | | (0 and 7 both mean Sunday)
| | | | |
* * * * * command-to-execute
Each field accepts a value, a range, a list, a step, or the wildcard *. Let us walk through every field in detail.
Field 1: Minute (0–59)
The minute within the hour at which the job should fire. A value of 0 means the top of the hour. A value of 30 means half past. The wildcard * means every minute.
Field 2: Hour (0–23)
Uses 24-hour time. 0 is midnight, 13 is 1:00 PM, 23 is 11:00 PM. Combine with the minute field: 30 14 means 2:30 PM.
Field 3: Day of Month (1–31)
The calendar date. Be careful with values like 31—months with fewer days will simply skip that run. There is no built-in "last day of the month" value (more on workarounds later).
Field 4: Month (1–12)
Numeric or three-letter abbreviation. 1 or jan for January, 12 or dec for December. Case does not matter for abbreviations.
Field 5: Day of Week (0–7)
Both 0 and 7 represent Sunday. 1 is Monday through 6 for Saturday. You can also use three-letter abbreviations: mon, tue, wed, etc.
Important nuance: When both day-of-month and day-of-week are specified (not *), the job runs when either condition is true, not when both are true. This catches many people off guard. For example, 0 9 15 * 1 runs at 9:00 AM on the 15th of every month and on every Monday—not only on Mondays that fall on the 15th.
Operators — The Building Blocks
Cron provides four operators that give you fine-grained control over scheduling:
Wildcard: *
Matches every possible value for the field. * * * * * fires every single minute.
List: ,
Specifies multiple discrete values. 0,15,30,45 in the minute field fires at those four minutes. mon,wed,fri in the day-of-week field fires on those three days.
Range: -
Defines a contiguous range. 9-17 in the hour field means every hour from 9 AM to 5 PM inclusive. 1-5 in the day-of-week field means Monday through Friday.
Step: /
Used with * or a range to skip values. */10 in the minute field means every 10 minutes (0, 10, 20, 30, 40, 50). 1-30/2 means every odd minute from 1 to 29.
These operators can be combined. For instance, 0-30/5 in the minute field fires at 0, 5, 10, 15, 20, 25, and 30.
15+ Practical Crontab Examples
Theory only gets you so far. Here are real-world cron expressions you can copy, paste, and adapt. Each one is explained in plain English.
1. Every Minute
* * * * * /usr/local/bin/health-check.sh
Runs the health check script once per minute, every minute. Useful for uptime monitoring but be mindful of resource usage.
2. Every 5 Minutes
*/5 * * * * /opt/scripts/sync-data.sh
Fires at :00, :05, :10, :15 and so on. One of the most commonly used intervals for data synchronization and queue processing.
3. Every 15 Minutes
*/15 * * * * /usr/bin/python3 /opt/app/process_queue.py
Runs four times per hour. A balanced interval for tasks like processing email queues or pulling API data.
4. Every Hour (on the Hour)
0 * * * * /opt/scripts/hourly-report.sh
Fires at the top of every hour. Note the 0 in the minute field—without it, you would run every minute.
5. Every Day at Midnight
0 0 * * * /opt/scripts/daily-backup.sh
The classic daily job. Runs at 00:00 system time. This is equivalent to the @daily shorthand.
6. Twice a Day (9 AM and 6 PM)
0 9,18 * * * /opt/scripts/generate-report.sh
Uses a comma-separated list in the hour field. The report generates at the start of the workday and end of the workday.
7. Every Weekday at 8:30 AM
30 8 * * 1-5 /opt/scripts/morning-standup-reminder.sh
The 1-5 in the day-of-week field means Monday through Friday. Perfect for business-hours automation.
8. Every Monday at 6 AM
0 6 * * 1 /opt/scripts/weekly-digest.sh
Runs once per week. 1 represents Monday. You could also write mon for readability.
9. First Day of Every Month at Midnight
0 0 1 * * /opt/scripts/monthly-invoice.sh
The 1 in the day-of-month field targets the first calendar day. Ideal for billing cycles and monthly reports.
10. Every Quarter (Jan, Apr, Jul, Oct) on the 1st
0 0 1 1,4,7,10 * /opt/scripts/quarterly-review.sh
Runs four times per year. The month field uses a comma-separated list to target the start of each quarter.
11. First Monday of Every Month
0 9 1-7 * 1 /opt/scripts/team-meeting.sh
This is a classic trick. It targets Mondays (1) that fall within the first 7 days of the month. However, remember the day-of-month/day-of-week OR behavior—this actually runs on every Monday and on days 1 through 7. For a true "first Monday only" solution, you need a wrapper script that checks the date.
First Monday workaround: Use a wrapper: 0 9 * * 1 [ $(date +\%d) -le 7 ] && /opt/scripts/team-meeting.sh. This runs every Monday but the date check ensures the script only executes during the first week.
12. Last Day of Every Month
0 23 28-31 * * [ $(date -d tomorrow +\%d) -eq 1 ] && /opt/scripts/month-end-close.sh
Cron has no native "last day" syntax. This workaround runs on days 28–31 and checks whether tomorrow is the 1st. If it is, today must be the last day of the month.
13. Every 30 Minutes During Business Hours
*/30 9-17 * * 1-5 /opt/scripts/check-tickets.sh
Combines a step operator with hour and day-of-week ranges. Fires at :00 and :30 from 9 AM to 5 PM, Monday through Friday.
14. At Reboot
@reboot /opt/scripts/start-services.sh
Not a traditional five-field expression, but a special cron string that triggers once when the system boots. Useful for starting background daemons.
15. Every Sunday at 2 AM (Log Rotation)
0 2 * * 0 /usr/sbin/logrotate /etc/logrotate.conf
Weekly log rotation at a low-traffic hour. Using 0 or 7 or sun all target Sunday.
16. Every 10 Minutes Between 6 PM and Midnight
*/10 18-23 * * * /opt/scripts/evening-batch.sh
Targets evening hours specifically. Runs 36 times per night (6 per hour for 6 hours).
17. January 1st at Noon (New Year)
0 12 1 1 * /opt/scripts/happy-new-year.sh
Runs once per year. The ultimate "set it and forget it" schedule.
📅 Generate Any Cron Schedule Instantly →Special Strings — Shortcuts for Common Schedules
Most modern cron implementations support predefined shorthand strings that replace the five-field syntax entirely. These are easier to read and harder to get wrong.
String Equivalent Meaning
------ ---------- -------
@yearly 0 0 1 1 * Once a year (Jan 1, midnight)
@annually 0 0 1 1 * Same as @yearly
@monthly 0 0 1 * * First day of every month, midnight
@weekly 0 0 * * 0 Every Sunday at midnight
@daily 0 0 * * * Every day at midnight
@midnight 0 0 * * * Same as @daily
@hourly 0 * * * * Every hour, on the hour
@reboot (none) Once at system startup
These strings go in place of the five time fields. For example:
@daily /opt/scripts/daily-backup.sh
@weekly /opt/scripts/weekly-report.sh
@reboot /opt/scripts/start-daemon.sh
While convenient, not all cron implementations support these—older versions of some systems may not recognize them. When in doubt, use the explicit five-field format.
Environment Variables in Crontab
You can set environment variables at the top of your crontab file. These apply to all jobs defined below them:
# Set the shell (default is /bin/sh)
SHELL=/bin/bash
# Set the PATH so scripts can find commands
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Email output to this address (leave empty to disable)
MAILTO=admin@example.com
# Override timezone (not supported everywhere)
CRON_TZ=America/New_York
# --- Jobs below inherit these variables ---
0 6 * * * /opt/scripts/morning-report.sh
0 0 * * * /opt/scripts/nightly-cleanup.sh
Setting PATH and SHELL at the top of your crontab prevents the most common class of "it works in my terminal but not in cron" bugs.
Common Mistakes (and How to Avoid Them)
Cron is deceptively simple. Here are the pitfalls that trip up even experienced sysadmins.
1. Forgetting That Cron Has a Minimal Environment
When you open a terminal, your shell loads ~/.bashrc, ~/.profile, and your full PATH. Cron does none of this. The default PATH in cron is often just /usr/bin:/bin. If your script calls node, python3, docker, or any command installed elsewhere, cron will not find it.
Fix: Always use absolute paths in cron commands, or set PATH at the top of your crontab.
# Bad
*/5 * * * * python3 process.py
# Good
*/5 * * * * /usr/bin/python3 /opt/app/process.py
2. Timezone Confusion
Cron uses the system timezone by default. If your server is set to UTC but you are thinking in your local time, your 9 AM job might fire at 2 AM. Always verify with timedatectl or date.
Fix: Use CRON_TZ if supported, or convert all times to your server's timezone mentally before writing the expression.
3. Silently Discarding Output
By default, cron emails the output of every job to the user. If the system has no mail configured (common on modern servers), output vanishes. Your script could be failing every run and you would never know.
Fix: Redirect output explicitly:
# Log stdout and stderr to a file
0 0 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# Discard output intentionally (only when you truly don't need it)
0 0 * * * /opt/scripts/cleanup.sh > /dev/null 2>&1
4. Percent Signs in Commands
In crontab, the % character is special—it is interpreted as a newline, and everything after the first % is fed as stdin to the command. This silently breaks commands that use date +%Y formatting.
Fix: Escape percent signs with a backslash:
# Bad (silently fails)
0 0 * * * /opt/scripts/archive.sh $(date +%Y-%m-%d)
# Good
0 0 * * * /opt/scripts/archive.sh $(date +\%Y-\%m-\%d)
5. Missing Execute Permissions
Your script runs fine when you call it manually because you are invoking it with bash script.sh. But if the cron entry just has /opt/scripts/script.sh, the file needs the execute bit set.
Fix:
chmod +x /opt/scripts/script.sh
6. Overlapping Jobs
If a job takes longer than its scheduled interval, multiple instances can pile up. A backup script scheduled every 5 minutes that takes 8 minutes to complete will have overlapping runs.
Fix: Use flock to prevent concurrent execution:
*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /opt/scripts/sync-data.sh
🕑 Convert Timestamps for Timezone Debugging →
Managing Your Crontab
Here are the essential commands for working with crontab on any Linux system:
# Edit your crontab (opens in $EDITOR)
crontab -e
# List your current crontab entries
crontab -l
# Remove your entire crontab (use with caution!)
crontab -r
# Edit another user's crontab (requires root)
sudo crontab -u username -e
# View cron logs (Debian/Ubuntu)
grep CRON /var/log/syslog
# View cron logs (RHEL/CentOS)
grep CRON /var/log/cron
Pro tip: Before making changes, always back up your crontab first: crontab -l > ~/crontab-backup-$(date +%F).txt. One accidental crontab -r and everything is gone.
Cron in the Modern Stack
While the five-field syntax was invented in the 1970s, it has become the universal language for expressing time schedules. You will find it in:
- Kubernetes CronJobs — uses the same five-field format in the
schedulefield of your YAML manifest. - GitHub Actions — the
crontrigger underon.scheduletakes a standard cron expression (always in UTC). - AWS CloudWatch Events / EventBridge — uses a six-field variant (adds seconds or year depending on the service).
- systemd Timers — the modern Linux alternative to cron, though it uses its own calendar syntax rather than cron's five fields.
- CI/CD Pipelines — Jenkins, GitLab CI, CircleCI, and others all support cron expressions for scheduled builds.
Learning cron syntax once gives you scheduling literacy across almost every platform in the DevOps ecosystem.
Quick Reference Cheat Sheet
Expression Description
---------- -----------
* * * * * Every minute
*/5 * * * * Every 5 minutes
0 * * * * Every hour
0 0 * * * Daily at midnight
0 9 * * 1-5 Weekdays at 9 AM
0 0 1 * * First of every month
0 0 * * 0 Every Sunday at midnight
0 */4 * * * Every 4 hours
30 9 * * 1 Mondays at 9:30 AM
0 0 1 1,4,7,10 * Quarterly (Jan, Apr, Jul, Oct)
0 22 * * 1-5 Weeknights at 10 PM
*/15 9-17 * * * Every 15 min during business hours
0 6,12,18 * * * Three times a day (6 AM, noon, 6 PM)
0 0 15 * * 15th of every month at midnight
0 3 * * 6 Saturdays at 3 AM
⚙ Open the Free Crontab Generator →
Testing and Debugging Cron Jobs
Before deploying a cron job to production, follow this testing checklist:
- Test the command manually — run the exact command from the crontab entry in your terminal first. If it fails interactively, it will definitely fail in cron.
- Simulate the cron environment — run your command with a minimal environment to catch PATH issues:
env -i /bin/sh -c '/opt/scripts/myscript.sh' - Use a near-future schedule — instead of testing with
0 0 * * *and waiting until midnight, temporarily set the job to*/1 * * * *(every minute) to verify it triggers. - Check the cron log —
grep CRON /var/log/syslogwill show you whether cron attempted to run the job. - Redirect output to a file — append
>> /tmp/test-cron.log 2>&1to see exactly what happened. - Verify the daemon is running —
systemctl status cron(orcrondon RHEL-based systems).
Wrapping Up
Cron's five-field syntax is one of those fundamental pieces of systems knowledge that pays dividends for your entire career. Once the pattern clicks—minute, hour, day-of-month, month, day-of-week—you can schedule anything from a quick health check to a complex quarterly reporting pipeline.
The key takeaways:
- Master the five fields and the four operators (
*,,,-,/) and you can express any schedule. - Use special strings like
@dailyand@weeklyfor common cases—they are more readable. - Always use absolute paths, redirect output, and set
PATHandSHELLin your crontab. - Escape percent signs. This single issue causes more silent cron failures than anything else.
- Test with a short interval before deploying the real schedule.
If you want to skip the mental math entirely, use NexTool's Crontab Generator or the Cron Expression Builder to construct, validate, and understand cron expressions visually. They are free, run entirely in your browser, and require no sign-up.