GITHUB ACTIONS GUIDE

GitHub Actions
Cron Schedule Examples

Complete guide to scheduled GitHub Actions workflows — 15+ real YAML examples, UTC timezone explained, common mistakes, and production-ready workflow templates.

How GitHub Actions Scheduled Workflows Work

GitHub Actions uses standard 5-field cron syntax in the schedule trigger under on:. Schedules run on the default branch only and always in UTC.

name: Scheduled Job

on:
  schedule:
    - cron: '0 9 * * 1-5'   # 9:00 AM UTC, Monday–Friday
  workflow_dispatch:          # Always add this for manual testing

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run scheduled task
        run: echo "Running at $(date)"
The 5 cron fields in order:
minute(0-59) hour(0-23) day-of-month(1-31) month(1-12) day-of-week(0-7)
Where 0 and 7 both represent Sunday.

GitHub Actions Always Runs in UTC

This is the most common source of confusion with GitHub Actions schedules. All schedule triggers fire in UTC — there is no timezone setting. If your team is in a different timezone, you must manually convert.

Desired Local TimeTimezoneUTC OffsetCron Expression (UTC)
9:00 AM Mon–FriUTC+00 9 * * 1-5
9:00 AM Mon–FriNew York (EST)-50 14 * * 1-5
9:00 AM Mon–FriNew York (EDT)-40 13 * * 1-5
9:00 AM Mon–FriLondon (GMT)+00 9 * * 1-5
9:00 AM Mon–FriLondon (BST)+10 8 * * 1-5
9:00 AM Mon–FriIndia (IST)+5:3030 3 * * 1-5
9:00 AM Mon–FriSingapore (SGT)+80 1 * * 1-5
9:00 AM Mon–FriTokyo (JST)+90 0 * * 1-5
Midnight every daySydney (AEDT)+110 13 * * *
⚠ DST gotcha: UTC offsets change twice a year in most regions due to Daylight Saving Time. A schedule correct in winter may fire an hour off in summer. Use to find the right UTC time for your current offset.

15+ GitHub Actions Cron Examples

Simple intervals

Every 5 minutes
*/5 * * * *
Minimum practical interval. Used for polling, near-real-time checks. Note: GitHub may throttle below 5-minute intervals.
Every 15 minutes
*/15 * * * *
Data syncs, cache invalidation, status page checks
Every hour
0 * * * *
Hourly builds, metrics collection, dependency checks

Daily schedules

Daily at midnight UTC
0 0 * * *
Nightly builds, daily cleanup, snapshot creation
Daily at 2 AM UTC (low-traffic window)
0 2 * * *
Heavy jobs during off-peak: full test suite, large data exports
Daily at 6 AM UTC
0 6 * * *
Pre-work automated checks, morning digest generation

Weekday schedules

Weekdays at 9 AM UTC (Mon–Fri)
0 9 * * 1-5
Business-hours CI jobs, weekday-only notifications
Every 30 minutes, Mon–Fri 9–17 UTC
*/30 9-17 * * 1-5
Business hours polling, working-hours-only checks
Monday morning 8 AM UTC
0 8 * * 1
Weekly dependency update PRs, Monday report generation
Friday 5 PM UTC
0 17 * * 5
End-of-week reports, weekly release tagging

Weekly schedules

Every Sunday at midnight
0 0 * * 0
Weekly full test run, weekly changelog generation
Sunday at 2 AM UTC
0 2 * * 0
Weekly database vacuum, full index rebuild

Monthly schedules

First of every month at midnight
0 0 1 * *
Monthly releases, subscription renewals, billing cycles
First of every quarter (Jan, Apr, Jul, Oct)
0 0 1 1,4,7,10 *
Quarterly reports, quarterly dependency audits
Once a year — Jan 1st midnight
0 0 1 1 *
Annual version bumps, yearly archiving

Full Workflow Templates

Nightly build and test

name: Nightly Build

on:
  schedule:
    - cron: '0 2 * * *'     # 2:00 AM UTC daily
  workflow_dispatch:

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run full test suite
        run: npm run test:full

      - name: Build
        run: npm run build

      - name: Notify on failure
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: 'Nightly build failed',
              body: 'Run: ' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId
            })

Weekly dependency update

name: Weekly Dependency Check

on:
  schedule:
    - cron: '0 8 * * 1'    # Monday 8:00 AM UTC
  workflow_dispatch:

jobs:
  update-deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Check for outdated packages
        run: npm outdated || true

      - name: Run audit
        run: npm audit --audit-level=high

Scheduled release with version bump

name: Monthly Release

on:
  schedule:
    - cron: '0 10 1 * *'   # 1st of every month, 10:00 AM UTC
  workflow_dispatch:
    inputs:
      version_bump:
        description: 'Version bump type'
        required: true
        default: 'patch'
        type: choice
        options: [patch, minor, major]

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Bump version
        run: |
          BUMP="${{ inputs.version_bump || 'patch' }}"
          npm version $BUMP --no-git-tag-version

      - name: Commit and tag
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          VERSION=$(node -p "require('./package.json').version")
          git add package.json
          git commit -m "chore: release v$VERSION"
          git tag "v$VERSION"
          git push && git push --tags

Common Mistakes

Mistake 1: Forgetting UTC
Scheduling 0 9 * * 1-5 expecting 9 AM in your local time. GitHub runs in UTC. For 9 AM IST (UTC+5:30), use 30 3 * * 1-5.
Mistake 2: Schedule on non-default branch
Workflows with schedule: only trigger from the default branch. A schedule in a feature branch PR will never fire.
Mistake 3: No workflow_dispatch
Without workflow_dispatch:, you cannot manually test your scheduled workflow. You'd have to wait for the schedule or change it temporarily. Always add workflow_dispatch:.
Mistake 4: Inactive repo schedule disabled
GitHub automatically disables scheduled workflows on public repositories with no activity (commits, PRs, etc.) for 60 days. You'll receive an email warning first. Re-enable in the Actions tab.
Mistake 5: Expecting precise timing
GitHub's schedule trigger has a documented delay of up to 15–30 minutes during high load. Don't use GitHub Actions schedules for time-critical tasks requiring second-level precision.

Always Include workflow_dispatch

Adding workflow_dispatch alongside your schedule gives you a "Run workflow" button in the GitHub Actions UI. This is essential for:

  • Testing — Run immediately without waiting for the schedule
  • Incident response — Manually trigger when you need the job to run now
  • Debugging — Run with custom inputs to test different scenarios
  • Re-enabling — Quickly test after re-enabling a disabled workflow
on:
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:         # No config needed — just add it
    inputs:                  # Optional: add inputs for parameterised manual runs
      environment:
        description: 'Target environment'
        required: false
        default: 'staging'
        type: choice
        options: [staging, production]

Multiple Schedules in One Workflow

A single workflow can have multiple schedule entries. All of them trigger the same workflow:

on:
  schedule:
    - cron: '0 8 * * 1-5'   # Weekdays at 8 AM UTC
    - cron: '0 12 * * 0'    # Sundays at noon UTC
  workflow_dispatch:

To know which schedule triggered the run, check github.event.schedule in your job steps:

- name: Check which schedule triggered
  run: |
    echo "Triggered by: ${{ github.event.schedule || 'manual' }}"
    if [ "${{ github.event.schedule }}" = "0 8 * * 1-5" ]; then
      echo "Weekday morning run"
    fi

Frequently Asked Questions

What is the minimum cron interval in GitHub Actions?
The minimum supported interval is 5 minutes (*/5 * * * *). Schedules faster than this are technically unsupported and may be throttled or skipped.
Why is my GitHub Actions scheduled workflow not running?
Most likely causes: 1) The workflow file is on a branch that isn't the default — schedules only run from the default branch. 2) The repository has been inactive for 60+ days and GitHub disabled the schedule. 3) Infrastructure delays (GitHub's schedule trigger can lag up to 30 minutes). 4) The cron syntax is invalid.
Can I trigger a scheduled workflow manually?
Yes — add workflow_dispatch: to your on: block. This adds a "Run workflow" button in the Actions UI. Best practice is to include it on every scheduled workflow.
How do I know which schedule triggered the workflow?
Use ${{ github.event.schedule }} in your workflow steps. It contains the cron string that triggered the run, or is empty for manual (workflow_dispatch) runs.
Can I use GitHub Actions as a free GitHub scheduler?
Yes — this is the most common way developers set up a GitHub scheduler without any extra infrastructure. The schedule trigger under on: runs on GitHub's own servers, so there's nothing to host or pay for beyond your normal Actions minutes. It's a solid choice for nightly builds, dependency updates, and periodic API calls, as long as you can tolerate the occasional 15-30 minute delay and the 5-minute minimum interval.
Do scheduled workflows run on forks?
Scheduled workflows are disabled on forked repositories by default. A contributor would need to enable Actions on their fork manually for schedules to run there.
⚡ Validate Your GitHub Actions Cron

🔧 Other Free Developer Tools

⏱ Cron Generator 🕐 Timestamp Converter 🔑 JWT Decoder 📄 YAML ↔ JSON 🔤 Base64 Encoder 🗄 SQL Explainer