How to Set Up Cron Jobs in GitHub Actions (Scheduled Workflows)
Learn how to create scheduled workflows in GitHub Actions using cron expressions. Covers syntax, limitations, timezone handling, debugging, and common pitfalls.
Prerequisites
- A GitHub repository
- Basic understanding of GitHub Actions YAML
In this guide
Scheduled Workflows in GitHub Actions
GitHub Actions supports the schedule event trigger, which runs workflows on a cron schedule. This is useful for recurring tasks like dependency updates, report generation, data syncing, or cleanup.
Scheduled workflows run on the default branch (usually main) and use UTC time. They may be delayed by up to several minutes during high-load periods.
Quick Start
Create .github/workflows/scheduled.yml:
name: Scheduled Task
on:
schedule:
- cron: '0 9 * * 1-5' # Weekdays at 9 AM UTC
workflow_dispatch: # Allow manual trigger for testing
jobs:
run-task:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run scheduled task
run: echo "Running at $(date)"Important: Always add workflow_dispatch so you can manually trigger the workflow for testing.
Schedule Syntax and Limits
GitHub Actions uses standard POSIX 5-field cron syntax:
minute hour day-of-month month day-of-weekLimitations:
- Minimum interval is 5 minutes (
*/5 * * * *is the shortest) - Schedules run in UTC only — no timezone configuration
- Workflows may be delayed during peak times (not guaranteed to run exactly on time)
- Scheduled workflows are disabled after 60 days of repository inactivity
- You can define multiple schedules for a single workflow
on:
schedule:
- cron: '0 9 * * 1-5' # Weekdays at 9 AM UTC
- cron: '0 0 1 * *' # First day of month at midnight UTCCommon Cron Expressions
All times are in UTC:
*/5 * * * *— Every 5 minutes (minimum interval)0 * * * *— Every hour0 0 * * *— Daily at midnight UTC0 9 * * 1-5— Weekdays at 9 AM UTC0 0 * * 0— Every Sunday at midnight UTC0 0 1 * *— First day of every month
Timezone conversion tip: If you want 9 AM Eastern (UTC-5), use 0 14 * * * (9 AM + 5 hours = 2 PM UTC). Remember to adjust for daylight saving time.
Every weekday at 9:00 AM
Next runs (UTC):
Mon, May 18, 2026 09:00
Tue, May 19, 2026 09:00
Wed, May 20, 2026 09:00
Working with Timezones
GitHub Actions schedules are always in UTC. If you need timezone-aware scheduling, you have two options:
Option 1: Convert to UTC manually
9 AM US Eastern = 2 PM UTC (EST) or 1 PM UTC (EDT)
Option 2: Check timezone in the workflow
jobs:
run-task:
runs-on: ubuntu-latest
steps:
- name: Check if it's business hours in EST
run: |
HOUR=$(TZ=America/New_York date +%H)
if [ "$HOUR" -lt 9 ] || [ "$HOUR" -gt 17 ]; then
echo "Outside business hours, skipping"
exit 0
fi
- name: Run task
run: echo "Running during business hours"Debugging Scheduled Workflows
Workflow not running?
- Check the Actions tab for any disabled workflows
- Scheduled workflows only run on the default branch — merging a schedule change to a feature branch won't work
- After 60 days of no repository activity, GitHub disables scheduled workflows. Push a commit or manually re-enable them
- Check for YAML syntax errors in the cron expression
Manual trigger for testing:
Add workflow_dispatch to your trigger:
on:
schedule:
- cron: '0 9 * * *'
workflow_dispatch:Then trigger manually from the Actions tab or:
gh workflow run scheduled.ymlCheck recent runs:
gh run list --workflow=scheduled.ymlCommon Patterns
Dependency updates:
name: Update Dependencies
on:
schedule:
- cron: '0 6 * * 1' # Mondays at 6 AM UTC
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm update
- uses: peter-evans/create-pull-request@v6
with:
title: 'chore: update dependencies'
branch: deps/weekly-updateStale issue cleanup:
name: Close Stale Issues
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-stale: 60
days-before-close: 7Production Tips
Don't rely on exact timing. GitHub Actions schedules can be delayed by minutes during high load. Don't use them for time-critical operations.
Idempotent tasks: Since scheduled runs may be delayed or run twice (rare), make your tasks idempotent — safe to run multiple times.
Concurrency control:
concurrency:
group: scheduled-task
cancel-in-progress: trueNotifications on failure:
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: '{"text":"Scheduled workflow failed!"}'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}