skip to content

In GitHub Actions, what are the rules for a schedule: cron trigger?

level: middleimportance: should knowfreq 52%

answer

  1. Five fields, one fixed clock
  2. There is no timezone key
  3. Five minutes is the floor
  4. One branch decides what runs
  5. Public repos go quiet after two months idle

basics

~20 s

Five-field POSIX cron, always interpreted in UTC, with a shortest usable interval of five minutes. Scheduled runs always use the workflow file and latest commit on the default branch, and start times are best-effort, not guaranteed.

solid answer

~50 s

`schedule` takes a list of `- cron: '...'` entries using standard five-field POSIX cron syntax — minute, hour, day-of-month, month, day-of-week. The expression is **always UTC**; there is no timezone key, so daylight-saving shifts move your job by an hour twice a year unless you account for it. The shortest interval GitHub honours is five minutes. A scheduled run is not tied to any branch you choose: GitHub uses the workflow file and the latest commit on the **default branch**, which is why a cron added on a feature branch never fires. Start times are best-effort and can be delayed during peak load, so avoid `0 * * * *` and offset to an odd minute. Scheduled workflows in a public repository are disabled automatically after 60 days without repository activity, and are disabled in forks by default.

code

yaml · 14 lines
yaml
on:
  schedule:
    - cron: '17 3 * * 1-5'   # 03:17 UTC, Mon-Fri (offset off the hour)
    - cron: '0 */6 * * *'    # every six hours
  workflow_dispatch:          # reproduce a nightly failure on demand

jobs:
  nightly:
    if: github.repository == 'acme/service'   # do not run in forks
    runs-on: ubuntu-latest
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4
      - run: ./gradlew integrationTest

go deeper

for a junior

Recall the five-field cron shape, that the entry must be quoted in YAML, and that GitHub Actions interprets the expression in UTC with no timezone option.

for a middle

Explain the mechanics that bite: the five-minute floor, the default-branch rule for which file and commit run, and why schedule accepts no branch or path filters.

for a senior

Show operational judgment — offsetting off the hour, never assuming exact start times, monitoring for a nightly job that has stopped, and guarding forks with a repository check.

for a principal

Own the strategy: which work belongs on a scheduler at all versus event-driven or external triggers, how many scheduled workflows an org can run before contention and cost matter, and how silent-failure detection is built.

## The syntax ``` on: schedule: - cron: '17 3 * * 1-5' - cron: '0 */6 * * *' ``` `schedule` is a **list**, so a workflow can carry several cron expressions. Each entry is a standard five-field POSIX cron string: minute (0–59), hour (0–23), day-of-month (1–31), month (1–12 or names), day-of-week (0–6 or names, where both 0 and 7 are Sunday). The operators are `*` (any), `,` (list), `-` (range) and `/` (step). The value must be quoted, because a bare `*` starts a YAML alias and will not parse. There is no `timezone` key. GitHub Actions evaluates every cron expression in **UTC**, full stop. A job you want at 09:00 in a European office is `0 8 * * *` in winter and `0 7 * * *` in summer — pick one and accept the hour of drift, or schedule twice and gate on the date, or simply schedule at a time where the shift does not matter. Candidates who claim they set a timezone are remembering a different CI system. ## Interval floor The shortest interval GitHub honours is **five minutes**. `*/1 * * * *` does not give you a run every minute; it is throttled. If you need sub-five-minute reactions you are outside what the scheduler is for — use an external trigger that dispatches the workflow, or a long-running process outside Actions. ## Which code runs This is the mechanic most people get wrong. A scheduled run has no ref you can choose: GitHub runs the workflow **as defined on the default branch**, against the **latest commit of the default branch**. Consequences: - Adding a `schedule:` block on a feature branch produces nothing until it is merged. - Editing the cron on a feature branch changes nothing about the live schedule. - `github.ref` inside a scheduled run is the default branch's ref. - You cannot schedule a job "for the release branch" through the trigger; you would check that branch out explicitly inside the job, or use `workflow_dispatch` for ref-specific runs. `schedule` also accepts no `branches`, `tags` or `paths` filters — there is no ref or diff to filter on. ## Timing is best-effort A scheduled workflow is queued when capacity allows, not at a guaranteed instant. Delays are common at the top of the hour, when a very large share of the world's crons fire simultaneously; a run scheduled for `0 * * * *` may start noticeably late, and under heavy load a scheduled run can be dropped rather than merely delayed. Two practical rules follow: never write a workflow that assumes exact start time (compute windows from an actual timestamp, not from the cron), and offset your minute — `17 3 * * *` rather than `0 3 * * *` — which measurably improves punctuality by avoiding the crowd. ## Automatic disabling GitHub disables scheduled workflows in a **public** repository after **60 days** with no repository activity, to stop abandoned repos consuming shared capacity. The Actions tab shows the workflow as disabled with a button to re-enable it; a commit or other activity also revives it. Separately, when a public repository is **forked**, scheduled workflows are disabled in the fork by default — this is why a fork does not start emailing its owner about your nightly job. In a private repository the 60-day rule does not apply the same way, but the practical lesson stands: a silent nightly job is one you should monitor, because "it stopped running" is a failure mode that produces no failure notification. ## Guarding forks and pairing with dispatch Two idioms belong on nearly every scheduled workflow. First, a fork guard, so a fork that re-enables Actions does not run your maintenance job against your assumptions: ``` jobs: nightly: if: github.repository == 'acme/service' ``` Second, a companion `workflow_dispatch:` under the same `on:` block, so you can reproduce a nightly failure immediately instead of waiting a day. Both are cheap and both come up in interviews as "what would you add to this file". ## What schedule is good for Nightly integration suites too slow for a pull request, dependency-freshness or licence audits, cache warm-ups, stale-issue sweeps, expiring-credential checks, and reporting. What it is *not* good for: anything requiring exact timing, anything needing sub-five-minute frequency, and anything that must run against a non-default branch. Recognising those three boundaries is the substance of the question.

  • You added a schedule to a workflow on a feature branch and it never fired. Why?
    Scheduled runs always execute the workflow file as it exists on the repository's **default branch**, against that branch's latest commit. A cron on any other branch is inert until merged. There is no way to point `schedule` at a different ref; if you need a specific branch, check it out inside the job or use `workflow_dispatch`, which lets the caller choose the ref.
  • Why do experienced teams avoid scheduling at 0 * * * * in GitHub Actions?
    Scheduled start times are best-effort. An enormous share of crons fire on the hour, so runs scheduled at minute zero queue behind the crowd and start late — occasionally late enough to matter, and under heavy load a run can be skipped entirely. Offsetting to an arbitrary minute such as `17` avoids the peak, and workflows should compute time windows from an actual timestamp rather than assuming the nominal one.
  • How would you keep a nightly workflow from running in forks of a public repository?
    GitHub already disables scheduled workflows in forks by default, but a fork owner can re-enable them. Add a repository guard on the job — `if: github.repository == 'owner/repo'` — so even an enabled fork exits immediately. This matters most for jobs that post comments, open issues, or touch shared external systems.

saying these in an interview costs you the question

  • Says you can set a timezone on the cron entry
  • Expects every-minute scheduling to work
  • Assumes the run uses the branch where the cron was added
  • Treats the scheduled start time as exact
  • Never notices a scheduled workflow that silently stopped

context