X Xerobit

GitHub Actions Scheduled Workflows — cron Syntax and Examples

Schedule GitHub Actions workflows with cron expressions using the schedule trigger. Learn the cron syntax, timezone behavior (UTC only), avoiding missed runs, and examples for...

Mian Ali Khalid · · 4 min read
Use the tool
Cron Builder
Build and parse cron expressions with human-readable explanations.
Open Cron Builder →

GitHub Actions schedule trigger uses standard cron syntax but runs in UTC only. Workflows can miss their scheduled time during high load — design them to be idempotent.

Build cron expressions with the Cron Builder.

Basic schedule trigger

# .github/workflows/nightly.yml
name: Nightly Build

on:
  schedule:
    - cron: '0 2 * * *'    # 2:00 AM UTC every day
  workflow_dispatch:         # Allow manual trigger

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run nightly build
        run: npm ci && npm run build

Important: Always add workflow_dispatch alongside schedule so you can test and trigger manually.

Cron syntax for GitHub Actions

# Format: minute hour day-of-month month day-of-week
#          0-59   0-23      1-31    1-12     0-6 (0=Sun)

on:
  schedule:
    # Every hour:
    - cron: '0 * * * *'
    
    # Every day at midnight UTC:
    - cron: '0 0 * * *'
    
    # Every Monday at 9 AM UTC:
    - cron: '0 9 * * 1'
    
    # First day of every month at 6 AM:
    - cron: '0 6 1 * *'
    
    # Every 15 minutes:
    - cron: '*/15 * * * *'
    
    # Weekdays at 8 AM UTC (Mon-Fri):
    - cron: '0 8 * * 1-5'

Note: GitHub Actions minimum schedule interval is 5 minutes.

Multiple schedules

on:
  schedule:
    - cron: '0 8 * * 1-5'   # Weekday morning run
    - cron: '0 0 * * 0'     # Sunday midnight cleanup

Timezone handling (UTC only)

GitHub Actions schedule only supports UTC. To convert:

# If your team is in Eastern time (UTC-5 or UTC-4 with DST):
# "9 AM New York" = 14:00 UTC (EST) or 13:00 UTC (EDT)
# Use 14:00 UTC to be consistent with EST, or accept 1-hour shift in summer:

on:
  schedule:
    - cron: '0 14 * * 1-5'  # Roughly 9 AM ET (EST)

For precise timezone-aware scheduling, use GitHub Actions Cron with environment variables or use a separate scheduler like AWS EventBridge.

Common scheduled workflow patterns

Dependency updates check

name: Dependency Audit

on:
  schedule:
    - cron: '0 6 * * 1'    # Every Monday at 6 AM UTC

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm audit --audit-level=high
      - name: Check for outdated packages
        run: npx npm-check-updates --errorLevel 2

Database backup

name: Database Backup

on:
  schedule:
    - cron: '0 1 * * *'    # Daily at 1 AM UTC

jobs:
  backup:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    steps:
      - name: Create backup
        run: |
          pg_dump $DATABASE_URL | gzip > backup-$(date +%Y%m%d).sql.gz
      - name: Upload to S3
        run: |
          aws s3 cp backup-*.sql.gz s3://my-backups/db/ \
            --storage-class STANDARD_IA
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Weekly report

name: Weekly Report

on:
  schedule:
    - cron: '0 9 * * 1'    # Every Monday at 9 AM UTC
  workflow_dispatch:

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Generate report
        run: node scripts/generate-report.js
      - name: Send to Slack
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "Weekly report ready: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Check if run was triggered by schedule

steps:
  - name: Different behavior for scheduled vs manual
    run: |
      if [ "${{ github.event_name }}" == "schedule" ]; then
        echo "Running scheduled job with extra cleanup"
        npm run scheduled-cleanup
      else
        echo "Manual trigger"
      fi

Prevent concurrent runs

name: Scheduled Job

on:
  schedule:
    - cron: '0 * * * *'

concurrency:
  group: scheduled-job
  cancel-in-progress: false  # Don't cancel — wait for previous to finish

Related posts

Related tool

Cron Builder

Build and parse cron expressions with human-readable explanations.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.