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 monthCommon Schedule Patterns
| Schedule | Cron |
|---|---|
| Every day at midnight UTC | 0 0 * * * |
| Every weekday at 9 AM UTC | 0 9 * * 1-5 |
| Every 15 minutes | */15 * * * * |
| Every Sunday at 2 AM | 0 2 * * 0 |
| First of the month at midnight | 0 0 1 * * |
| Every 6 hours | 0 */6 * * * |
| Monday at 8 AM UTC (4 AM ET) | 0 8 * * 1 |
| Twice daily: midnight + noon | 0 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 manuallyGo 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.