skip to main content

-- Break a cron expression down field by field and see its next five runs --

-- fields are read as UTC, the way cron, GitHub Actions and Vercel do --

valid expression
minute
at 0
hour
at 9
day of month
every value
month
every value
day of week
at 1, 2, 3, 4, 5

    How to use it

    Type a five-field expression. The tool validates each field, points at the one that is wrong when there is one, and computes the next five runs.

    The five fields

    ┌───────────── minute (0-59)
    │ ┌─────────── hour (0-23)
    │ │ ┌───────── day of month (1-31)
    │ │ │ ┌─────── month (1-12 or JAN-DEC)
    │ │ │ │ ┌───── day of week (0-6 or SUN-SAT, 0 = Sunday)
    │ │ │ │ │
    * * * * *
    
    SyntaxMeaning
    *every value
    5only 5
    1-51 through 5 inclusive
    */15every 15 units, starting at the minimum
    5/10from 5 onwards, then every 10
    1,15,30exactly these values

    7 is accepted as an alias for Sunday alongside 0: crontab(5) documents it explicitly.

    The day-of-month and day-of-week trap

    This is the source of cron bugs. When both day fields are restricted — neither is * — cron fires as soon as either one matches, not when both do.

    0 0 1 * 1
    

    It reads naturally as "the first Monday of the month". In reality: every Monday, plus the 1st of every month. To get an actual first Monday you have to test the date inside the script:

    # 1st Monday of the month: cron fires every Monday, the script filters
    0 0 * * 1 [ "$(date +%d)" -le 07 ] && /usr/local/bin/my-script

    As soon as only one of the two fields is restricted, the rule becomes intuitive again: 0 9 * * 1-5 does mean "at 9am, Monday to Friday".

    Time zone

    This tool reads the fields as UTC, the way system cron, GitHub Actions and Vercel do. A scheduler that reads them in local time raises two questions with no good answer: when clocks spring forward, a task at 02:30 does not exist; when they fall back, it exists twice.

    Hence a simple rule: schedule in UTC and convert for display. A task at 9am Paris time is 0 8 * * * in winter and 0 7 * * * in summer — or, better, is scheduled at an hour where the offset does not matter.

    Valid expressions that never fire

    0 0 30 2 *
    

    The 30th of February is syntactically correct and will never happen. The tool detects it and says so, instead of searching forever.

    0 0 29 2 * is subtler: it does fire, but only in leap years — so once every four years.

    Checking a cron in a project

    // GitHub Actions: exact minutes are discouraged, the scheduler is
    // congested at :00 and can drift by several minutes
    on:
      schedule:
        - cron: "17 3 * * *" // rather than "0 3 * * *"

    A GitHub Actions cron is not guaranteed to the minute: it is a shared queue. For a task that genuinely has to start on time, you need a dedicated scheduler.

    topics covered

    related reading