apprun.icuCron reader

Reference · Scheduling

Cron expression explained, field by field, with the next run times

Paste a crontab line and get a sentence a human can read, every value each field matches, and the next five or ten times the job will fire. It reads Vixie cron, the @ shorthands, and the Quartz L, W, # and ? extensions, labelled as Quartz so nobody mistakes them for standard cron. No server, no account, nothing leaves the tab.

Free no accountPrivate runs in your browserHonest says when a schedule never runsCopy explanation, run list, share link

Read a cron expression

Type or paste an expression, or load a preset. Everything below updates as you type: the sentence, the expanded fields, the next run times and any problem with the expression itself.

Expression

Vixie cron
Five fields, or an @ shortcut. Tabs and runs of spaces are fine.
Presets
A plain wall-clock reading with no zone attached. cron never stores a timezone in an entry, so neither does this reader.

Crontab line

with the header

                  

A crontab entry is a schedule plus a command. % is special: cron ends the command at the first unescaped % and treats the rest as stdin, so a date format has to be written +\%Y.

—
next run
—
runs per day
—
which days
—
dialect read

What it means

—

    Field by field

    every value it matches
    FieldYou wroteExpandedIn words

    Next runs

    from now
    #WhenFrom the assumed time

    The five fields

    Read them left to right. Each one answers a single question about the local wall clock: which minute, which hour, which day of the month, which month, which day of the week. A run happens on a minute that satisfies all of them at once. The two day fields do not stack cleanly, which is why they get their own section below.

    #FieldAllowed valuesNamesWhat it decides
    1Minute0-59—Which minute past the hour the job starts. 0 is the top of the hour.
    2Hour0-23—Which hour of the day, 24-hour clock. The day runs 0:00 to 23:59, so 24 is not a value you can write.
    3Day of month1-31—Which calendar date. Months with fewer days simply have no match: 31 in a 30-day month is skipped, not moved.
    4Month1-12JAN-DECWhich month of the year.
    5Day of week0-7SUN-SATWhich weekday. 0 and 7 both mean Sunday, so SUN, 0 and 7 are the same day.
    6Year (Debian)1970-2099—Optional sixth field. Not in POSIX cron; Debian's cron and cronie accept it to run a job only in listed years.

    Months and days accept three-letter names in any case (JAN, jan, January), and 7 is an accepted spelling of Sunday in the day-of-week field alongside 0. Full names are accepted for readability, though three letters is the portable habit. There is no seconds field in cron: a schedule that needs one has to wrap the command, or move to a systemd timer.

    Cron special characters

    Four operators belong to Vixie cron, the implementation man 5 crontab documents and most servers run: *, -, , and /. The rest are Quartz extensions. Parsers disagree about which set they accept, so this table names the dialect rather than pretending the two are the same thing.

    CharacterNameWhat it doesExampleDialect
    *AnyEvery value in the field's range.* in the hour field is every hour of every day.Vixie
    -RangeAn inclusive range, endpoints included. Names work too.1-5, MON-FRI, jan,dec in a list.Vixie
    ,ListSeveral values or ranges in one field.0,15,30,45, MON,WED,FRIVixie
    /StepEvery nth value across the range or inside a sub-range.*/15 = 0, 15, 30, 45. 1-5/2 = 1, 3, 5. 0 is not a step.Vixie
    ?No specific valueSays explicitly that a day field does not constrain the date. Used with the Quartz <code>#</code> and <code>L</code> forms.0 9 ? * 5#3Quartz
    LLastLast day of the month in the day-of-month field; in the day-of-week field, the last matching weekday of the month.0 9 L * * (last day of the month), 0 9 ? * 6L (last Friday)Quartz
    WNearest weekdayThe weekday nearest to a date, without crossing into the previous or next month. <code>1W</code> on a Saturday is the Monday after, because the 1st is the first of the month.0 9 15W * *Quartz
    #Nth weekdayThe nth occurrence of a weekday in the month. Only 1 to 5.0 9 ? * 5#3 is the third Friday of the month; 6#1 is the first Monday.Quartz
    @ShortcutOne-word schedules: <code>@yearly</code>, <code>@monthly</code>, <code>@weekly</code>, <code>@daily</code>, <code>@hourly</code>, <code>@reboot</code> and their aliases.@daily is exactly 0 0 * * *Vixie (and friends)

    The Vixie behaviour worth memorising: a step always restarts at the bottom of the field's range, so */15 in the minute field is 0, 15, 30, 45 and never lines up with the previous run, while 0-59/15 is the same four values written explicitly. Steps compose with ranges and inside lists, so 1-5/2 is 1, 3, 5 and 0,30 is a two-value step-free list. A step of 0 is invalid rather than "every value": */0 would never advance past the start of the field.

    The @ shorthands

    One word instead of five fields. Every one of them except @reboot is exactly the entry in the second column, which is why a reader that expands fields explains them correctly.

    ShortcutSame thing written outWhat it does
    @yearly, @annually0 0 1 1 *At 00:00 on 1 January, once a year.
    @monthly0 0 1 * *At 00:00 on the first day of every month.
    @weekly0 0 * * 0At 00:00 every Sunday.
    @daily, @midnight0 0 * * *At 00:00 every day.
    @hourly0 * * * *At minute 0 of every hour.
    @rebootnoneRuns once at boot, not on a schedule: cron starts the job when the daemon itself starts, and never repeats it on a timer.

    @reboot is not a schedule. It runs once at boot, not on a schedule: cron starts the job when the daemon itself starts, which on most machines is boot, and never repeats it on a timer. It has no next run time, it does not wait for anything to come up, and it is the wrong hook for anything that must happen repeatedly. Its real use is starting a service the machine needs after boot; if you need ordering or a dependency, that is a systemd unit's job.

    Day of month vs day of week: it is OR, not AND

    This single rule causes more broken crontabs than anything else in the file, so it is worth stating plainly.

    The Vixie specification says it in one line: if both fields are restricted (that is, neither contains the * character), the command will be run when either field matches the current time. So in 30 4 1,15 * 5, the job runs at 04:30 on the 1st and the 15th of every month and on every Friday — a union of about eight runs a month, not two. Set one of the two fields to * and the other one alone decides, which is what people almost always mean.

    ExpressionWhat actually runsWhat people usually meant
    30 4 1,15 * 504:30 on the 1st and 15th, plus every Friday04:30 on the 1st and 15th only
    30 4 1,15 * *04:30 on the 1st and 15th onlythe same thing, written unambiguously
    30 4 * * 1-504:30 on weekdaysthe same thing
    30 4 1 * 104:30 on the 1st, plus every Monday04:30 on the first Monday of the month

    Quartz answers the same design problem differently: ? says "this day field carries no constraint" explicitly, which is why 5#3 and L always appear next to a ?. Vixie cron has no such marker and rejects ?, so the cleanest fix in a plain crontab is to delete one of the two day fields.

    How to calculate the next cron run

    And why the honest answer is usually "the next matching minute, found by walking forward".

    cron has no next-run function to call. The daemon sleeps until the top of the next minute, reads the local clock and tests the five fields; if they all match, it forks the command. The next run is therefore the next minute that satisfies the expression, and the only way to know it is to search forward. The algorithm the reader above uses is the plain one: step one minute at a time, test the month first because it rejects the most candidates, then the hour, then the minute, then the two day fields with the either-or rule applied.

    That search has to be capped, because some schedules can never match. 30 2 30 2 * asks for 02:30 on 30 February, and there is no such date in any year, leap or otherwise. An uncapped minute-by-minute walk never returns, so the reader stops after five years of days and reports the reason rather than freezing: it checks whether the largest day asked for can exist in any month the schedule names. The same trap has quieter versions — 0 0 31 2 *, or a day-of-month list that only ever asks for dates the chosen months do not have.

    On a real machine, do not take a browser's word for it. systemd-analyze calendar '0 9 * * 1-5' prints the next elapses for a systemd calendar spec and tells you if the expression is nonsense. Debian's cron can validate a crontab file without installing it with crontab -T /tmp/test.crontab. To see what actually happened, read crontab -l for the entry and the syslog or journal for the daemon's own lines: Debian logs CRON[1234]: (user) CMD (command) at the moment each job starts. Note that cron does not stop a second run while the first is still going, so a slow job overlapping itself is a scheduling problem of its own.

    Cron vs a systemd timer

    Same job, different machine. On a host that already runs systemd, the timer is usually the better tool.

    A systemd timer is the modern answer to the same problem and it is not a drop-in rewrite: OnCalendar= takes a calendar expression close to cron's (with seconds, which cron lacks), while OnUnitActiveSec= takes a real interval measured from the last activation. That difference matters more than it looks — a calendar timer repeats the same DST caveats cron has, while a monotonic timer cannot drift, cannot double across a clock change and cannot silently skip an hour. Persistent=true catches up a run missed while the machine was off, which plain cron never does, and AccuracySec= lets systemd batch several wake-ups into one instead of waking the machine every minute.

     crontabsystemd timer
    Schedule syntaxFive fields, plus an optional sixth year field on Debian's cron.OnCalendar= takes a calendar expression close to cron's; OnUnitActiveSec= takes a real interval.
    AnchoringWall clock: the job runs when the clock says so, never earlier.Calendar timers are anchored the same way. OnUnitActiveSec= is anchored to the last activation, so intervals do not drift.
    Runs missed while the machine was offNever run. A laptop that is asleep at 03:00 misses the 03:00 job and cron does not care.Persistent=true runs the job once at the next boot if it was missed.
    Daylight savingLocal wall clock: a job in the skipped hour never arrives, one in the repeated hour can run twice.Calendar timers inherit the same DST caveat; monotonic timers such as OnUnitActiveSec= are immune.
    AccuracyChecked once a minute, so at most a minute late.AccuracySec= defaults to one minute, which lets systemd coalesce several wake-ups into one.
    Spreading loadNone. Several heavy jobs on the same minute all start together.RandomizedDelaySec= adds a random delay up to the value you set.
    Inspecting itcrontab -l, then the syslog or journal entries from the cron daemon.systemctl list-timers, systemctl cat foo.timer, journalctl -u foo.timer
    Overlapping runsAllowed: cron starts a new run every minute whether or not the last one finished.Not allowed: systemd will not start a unit that is still running.

    Neither one wins everywhere. cron is still the shortest path on a minimal VPS image, on macOS, on the BSDs, and whenever you want a schedule that reads the same on three different machines. A systemd timer is the better answer when the job is a unit with a lifecycle you want systemd to manage, when a missed run matters, or when the interval must survive a clock change.

    Time zones and daylight saving

    The part of cron that quietly breaks twice a year.

    cron evaluates every entry in the server's local time — whatever /etc/localtime and the daemon's TZ say — and never in UTC. An entry has no timezone of its own, so the same crontab means different minutes in two offices. This reader assumes the same thing: it reads schedules as a naive wall clock, applies no timezone you did not type, and tells you which clock it used.

    Across a DST transition the local clock moves, and cron follows it literally. In spring, an hour of local time does not exist, so a job scheduled inside that hour never arrives — not late, never. In autumn, an hour happens twice, so a job scheduled inside it can run twice. Implementations differ in the details, so treat both as unspecified behaviour and never put a job that must run exactly once on a boundary that moves. Two ways out: schedule in UTC (Debian's cron and cronie accept a CRON_TZ= line; systemd calendar timers take a UTC suffix), or use a monotonic systemd timer that does not care what the wall clock does.

    Two more operational notes that belong in the same breath. Cron does not wait for a service to be up, so a job that depends on a database started at boot fails unless you make the dependency explicit. And cron's environment is minimal: SHELL, USER, HOME, LOGNAME, PATH and PWD, with PATH usually limited to a few directories — which is why the crontab generator above puts SHELL and PATH at the top of the block, and why full paths in commands are the habit that saves an afternoon.

    Questions

    What does */15 mean in a cron expression?

    */15 in the minute field means minutes 0, 15, 30 and 45: four runs an hour, anchored to the top of the hour. The step always restarts at the start of the field's range, so the sequence never drifts based on the previous run. The same applies to every step: */5 is 0, 5, 10 … 55 (twelve runs an hour) and */30 is 0 and 30 (two runs an hour, not a 30-minute timer that starts counting from the last run). To say 'every 15 minutes since the job started' you need a different tool, such as a systemd timer with OnUnitActiveSec=15min.

    What do the five fields of a cron expression mean?

    A cron entry is five whitespace-separated fields read left to right: minute (0-59), hour (0-23), day of month (1-31), month (1-12 or JAN-DEC) and day of week (0-7 or SUN-SAT, where 0 and 7 both mean Sunday). The command runs on every minute that matches all five fields at once. Each field accepts * for every value, ranges such as 1-5 or MON-FRI, lists such as 1,2,5, steps such as */15 or 1-5/2, and any combination of them. Debian's cron also accepts an optional sixth year field.

    How do I calculate the next cron run?

    cron has no 'next run' function: the daemon wakes every minute and tests the current local wall clock against the five fields, so the next run is simply the next minute that matches. To work it out yourself, walk forward one minute at a time and test the month first, then the hour, then the minute, applying the day-of-month / day-of-week rule (if both day fields are restricted, a match in either one is enough). Cap the walk at five years: if no minute matches, the schedule is impossible, which is what happens with something like 30 2 30 2 * - 30 February never arrives. On a real box, systemd-analyze calendar '0 9 * * 1-5' prints the next elapses for a systemd calendar spec, and Debian's cron validates a file with crontab -T.

    If both the day-of-month and day-of-week fields are set, is it AND or OR?

    It is OR, and it is the most misunderstood rule in cron. It comes from the Vixie crontab(5) specification: when both day fields are restricted (that is, neither one is a bare *), the command runs when either field matches. So 30 4 1,15 * 5 runs at 04:30 on the 1st and 15th of every month and on every Friday. If either field is *, only the other one applies. If you want the two day fields to mean a single condition, you almost always want one of them to be *.

    What are the special characters used in cron expressions?

    Vixie cron, the implementation most servers run and which crontab(5) documents, has four operators: * for every value in the field's range, - for an inclusive range (1-5, MON-FRI), , for a list (1,2,5) and / for a step (*/15, 1-5/2). The characters L, W, # and ? are Quartz extensions, not part of Vixie cron or POSIX: L is the last day of the month or the last matching weekday, W the nearest weekday to a date, # an nth weekday of the month (5#3 is the third Friday) and ? means 'no specific value' in a day field. Vixie cron rejects all four.

    How does cron handle time zones and daylight saving time?

    cron evaluates every schedule in the server's local time - whatever /etc/localtime and the daemon's TZ say - and never in UTC. On a spring-forward night a job scheduled inside the skipped hour never arrives, because that local time does not happen; on a fall-back night a job in the repeated hour can run twice. Implementations differ in the details, so treat both as unspecified. If you need a job to fire exactly once at a fixed moment, schedule it in UTC: Debian's cron and cronie support a CRON_TZ= line, and systemd calendar timers accept a UTC suffix.

    Is cron better than a systemd timer?

    On a host that already runs systemd a timer is usually the better tool. OnCalendar= takes a calendar expression close to cron's, OnUnitActiveSec= measures a real interval so it neither drifts nor doubles across a DST change, Persistent=true catches up a run missed while the machine was off, and RandomizedDelaySec= spreads load. cron still wins where systemd is not in play: a minimal VPS image, macOS, the BSDs, and any schedule you want readable in one line that behaves the same everywhere.

    What do @yearly, @monthly, @weekly, @daily, @hourly and @reboot do?

    They are one-word shorthands for the five-field entries they replace: @yearly and @annually are 0 0 1 1 *, @monthly is 0 0 1 * *, @weekly is 0 0 * * 0 (Sunday), @daily and @midnight are 0 0 * * *, and @hourly is 0 * * * *. @reboot is the odd one out: it runs the job once when the cron daemon itself starts, not on a schedule at all, so it has no next run time and its only real use is starting services the machine needs after boot.