Crontab Guru
Translate cron schedule expressions into plain English.
minute hour day(month) month day(week)
Cron expressions, from the five fields to production pitfalls
Cron is the scheduler that ships with practically every Unix-like system. Each line in a crontab pairs a schedule with a command, and the schedule is written as five fields separated by spaces: minute (0 to 59), hour (0 to 23), day of month (1 to 31), month (1 to 12 or JAN to DEC) and day of week (0 to 7, where both 0 and 7 mean Sunday, or SUN to SAT). The daemon wakes every minute, compares the current time with every schedule it knows, and runs whatever matches. The grammar is compact enough to write "9:00 on weekdays" as 0 9 * * 1-5 and cryptic enough that most people cannot read that back without help.
This page translates an expression into plain English as you type, using the cronstrue library inside your browser. It accepts the standard five-field form, the @daily style shortcuts, month and weekday names, and six-field Quartz-style expressions where the first field is seconds. An expression it cannot parse shows "Invalid cron expression". The three presets cover the schedules people ask for most: every five minutes, every midnight and weekday mornings. For a full walkthrough with more examples, read How Crontab Works.
Operators inside a field
| Syntax | Meaning | Example |
|---|---|---|
* | every value | * * * * * runs every minute |
5 | exactly this value | 30 2 * * * runs at 02:30 daily |
1,15 | a list of values | 0 0 1,15 * * runs on the 1st and 15th |
9-17 | an inclusive range | 0 9-17 * * * runs hourly from 09:00 to 17:00 |
*/15 | every Nth value from the start | */15 * * * * runs at :00, :15, :30 and :45 |
1-30/5 | a step inside a range | 1-30/5 * * * * runs at :01, :06, :11 and so on up to :26 |
Steps count from the first value of the range, not from an arbitrary offset. If you need "every 20 minutes starting at :10", write the list explicitly: 10,30,50 * * * *.
The day-of-month versus day-of-week trap
In Vixie cron (the implementation on Debian, Ubuntu and most other distributions) the two day fields are combined with OR whenever both are restricted. 0 0 15 * 1 therefore runs on the 15th of every month and on every Monday, not only on Mondays that fall on the 15th. The English translation above reads as if both conditions apply together, which matches how the expression looks but not how cron behaves, so treat that particular combination with care. Quartz-based schedulers (Java, Spring, Jenkins) avoid the ambiguity by requiring a ? in one of the two fields.
Timezones, IST and daylight saving
Cron uses the system timezone of the machine it runs on, and cloud images almost always ship set to UTC. A schedule of 0 9 * * * on such a server fires at 14:30 IST, not at nine in the morning. Either convert the time yourself (IST is UTC+5:30, so 03:30 UTC is 09:00 IST) or set the timezone explicitly: cronie honours a CRON_TZ=Asia/Kolkata line at the top of the crontab, Kubernetes CronJobs accept a timeZone field, and GitHub Actions schedules run in UTC only. India has no daylight saving, but servers set to a zone that does can skip or repeat an hour twice a year, which is one more reason to keep servers on UTC and do the conversion in your head.
Why the job works in a shell and fails in cron
- Minimal environment. Cron does not load your profile.
PATHis typically just/usr/bin:/bin, so call programs by absolute path or setPATH=at the top of the crontab. - Shell. Commands run under
/bin/sh, not bash, unless you setSHELL=/bin/bash. Bash-only syntax fails silently. - Percent signs. A bare
%in the command is turned into a newline. Escape it as\%, which matters for everydate +\%Fin a backup script. - Output. Anything written to stdout or stderr is emailed to the user if a mail system exists and otherwise discarded. Redirect it:
>> /var/log/myjob.log 2>&1. - Overlap. Cron happily starts a second copy while the first is still running. Wrap long jobs with
flock -n /tmp/myjob.lock /path/to/scriptso the second run exits at once. - Missed runs. If the machine was off at the scheduled minute, plain cron does not catch up. Use anacron or a systemd timer with
Persistent=truefor laptops and intermittently running hosts.
Working with the crontab
crontab -e # edit your own crontab
crontab -l # list it
sudo crontab -u deploy -l # list another user's crontab
sudo crontab -r # WARNING: deletes the crontab with no confirmation
# Example crontab
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=ops@example.com
# min hour dom mon dow command
0 2 * * * /opt/backup/run.sh >> /var/log/backup.log 2>&1
*/5 * * * * flock -n /tmp/sync.lock /opt/sync/pull.sh
0 0 28-31 * * [ "$(date +\%d -d tomorrow)" = "01" ] && /opt/reports/month-end.shFiles in /etc/cron.d/ and the system-wide /etc/crontab use a sixth column for the user the command runs as, placed between the schedule and the command. Copying a line from a personal crontab into /etc/cron.d/ without adding that user field is a classic way to get a job that never runs. The last example above is the standard trick for "last day of the month", which cron cannot express directly.
Shortcuts
@reboot runs once after boot, and @hourly, @daily (or @midnight), @weekly, @monthly and @yearly (or @annually) map to the obvious five-field forms. They are easier to read in a crontab, but the five-field version is more portable to other schedulers.Other schedulers that speak cron
Kubernetes CronJob objects take the same five fields plus concurrencyPolicy and startingDeadlineSeconds to control overlap and late starts. GitHub Actions uses on: schedule: cron: in UTC, and its documentation warns that runs may be delayed when the platform is busy, so do not use it for anything time-critical. Apache Airflow accepts cron strings and presets, but triggers a run at the end of each interval rather than the start, which surprises almost everyone the first time. systemd timers use a different OnCalendar= syntax and give you systemctl list-timers plus journal logging for free, which makes them a good replacement on modern Linux hosts.
How to use
- Type or paste a cron expression into the box; the fields are minute, hour, day of month, month and day of week.
- Read the plain-English translation that appears below it. An unparseable expression shows "Invalid cron expression".
- Use the presets (every 5 minutes, every midnight, weekdays at 9 AM) as starting points and edit the fields.
- Remember that the translation is timezone-neutral: the job fires in the timezone of the server or scheduler, which is usually UTC on cloud machines.
- Copy the expression into crontab -e, a Kubernetes CronJob, a GitHub Actions workflow or any other cron-compatible scheduler.
Common use cases
- Decoding an inherited crontab line before touching it during an on-call incident.
- Scheduling a nightly database dump at a quiet hour, expressed in UTC for an Indian audience.
- Writing a Kubernetes CronJob that cleans up temporary files every six hours.
- Building a GitHub Actions workflow that rebuilds a static site every morning.
- Checking that a "twice a month" report schedule really lands on the 1st and the 15th.
- Reviewing a colleague's pull request that changes a cron string, without running it.
Frequently asked questions
Does this tool support six-field expressions with seconds?
What does */15 * * * * mean?
How do day-of-month and day-of-week interact?
My job runs at the wrong time. Why?
How do I run something on the last day of the month?
Why does the script work from my terminal but not from cron?
How do I stop overlapping runs of a slow job?
What is the difference between @daily and 0 0 * * *?
Advertisement