GitHub ActionsCI/CD8 min read

GitHub Actions Scheduled Workflows (Cron Guide)

GitHub Actions supports cron-based scheduling via on.schedule. This guide covers the syntax, UTC-only timing, common patterns, and the quirks that catch developers off guard.

Basic Syntax

Add on.schedule to your workflow YAML with one or more cron expressions. GitHub Actions uses standard 5-field cron syntax.

on:
  schedule:
    - cron: '0 9 * * 1-5'   # 9:00 AM UTC, Mon–Fri

    # You can add multiple schedules:
    - cron: '0 0 1 * *'     # Midnight UTC, 1st of month
Important: GitHub Actions cron expressions are always UTC. There is no timezone option. All times must be specified in UTC.

Common Schedule Patterns

ScheduleCron
Every day at midnight UTC0 0 * * *
Every weekday at 9 AM UTC0 9 * * 1-5
Every 15 minutes*/15 * * * *
Every Sunday at 2 AM0 2 * * 0
First of the month at midnight0 0 1 * *
Every 6 hours0 */6 * * *
Monday at 8 AM UTC (4 AM ET)0 8 * * 1
Twice daily: midnight + noon0 0,12 * * *

Full Example: Nightly Backup Workflow

A complete workflow that runs nightly, includes a manual trigger, and creates an issue on failure:

name: Nightly Database Backup

on:
  schedule:
    # Every night at 02:00 UTC (= 9 PM ET / 10 PM ET DST)
    - cron: '0 2 * * *'
  # Also allow manual trigger for testing
  workflow_dispatch:

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

      - name: Run backup
        env:
          DB_URL: ${{ secrets.DATABASE_URL }}
        run: |
          echo "Starting backup at $(date -u)"
          ./scripts/backup.sh
          echo "Backup complete"

      - 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 backup failed',
              body: `Run: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`
            })

Gotchas and Limitations

!

GitHub Actions cron is always UTC

There is no way to set a timezone for scheduled workflows. `0 9 * * *` fires at 9:00 AM UTC, not your local time. Convert to UTC explicitly.

!

5-minute minimum interval

GitHub Actions won't run scheduled workflows more often than every 5 minutes. The most frequent allowed schedule is `*/5 * * * *`.

!

Runs are delayed under high load

GitHub queues scheduled runs — they may fire 15-30+ minutes late during busy periods (especially popular times like the top of the hour or midnight). Don't depend on precise timing.

!

Scheduled runs are disabled on inactive repos

GitHub automatically disables scheduled workflows on repositories that have had no activity (pushes, PRs, etc.) for 60 days. You'll receive an email notification. Re-enable manually.

!

Uses the default branch

Scheduled workflows always run on the repository's default branch (usually `main`). You cannot schedule a workflow to run on a feature branch.

Debugging Scheduled Runs

1. Add workflow_dispatch for manual testing

on:
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:   # Add this to test manually

Go to Actions → Select the workflow → Run workflow to trigger it immediately.

2. Log the actual execution time

- name: Check timing
  run: echo "Job started at $(date -u) UTC"

This reveals actual run time vs. scheduled time, helping identify delays.

3. Check if the workflow is disabled

Go to Actions → Select your workflow → Click the three dots (···) menu. If it shows "Enable workflow", it was disabled by GitHub inactivity or manually.

Build and validate your GitHub Actions cron

Use our GitHub Actions Workflow Generator to create validated cron schedules with a downloadable .yml file.