Cron Jobs in GitHub Actions: Complete Tutorial

GitHub Actions supports scheduled workflows using the schedule trigger and standard cron expressions. This lets you automate tasks like nightly builds, weekly dependency audits, daily data syncs, or monthly reports — all within your repository, with no external scheduler needed.

Basic scheduled workflow

Create a file at .github/workflows/scheduled.yml:

name: Nightly build

on:
  schedule:
    - cron: '0 2 * * *'   # every day at 2:00 AM UTC

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: npm test

Multiple schedules on one workflow

You can define multiple cron triggers in the same workflow:

on:
  schedule:
    - cron: '0 8 * * 1-5'   # weekdays at 8 AM UTC
    - cron: '0 0 * * 0'     # Sundays at midnight UTC

Practical schedule examples

  • '*/30 * * * *' — run every 30 minutes (minimum reliable interval is 5 min)
  • '0 6 * * *' — daily report at 6 AM UTC
  • '0 9 * * 1' — weekly summary every Monday at 9 AM
  • '0 0 1 * *' — monthly cleanup on the 1st at midnight
  • '0 12 * * 1-5' — weekday noon deployment

Important limitations and gotchas

  • UTC only — GitHub Actions cron always runs in UTC. Convert your local time before writing the expression.
  • Minimum interval is 5 minutes — GitHub enforces this; more frequent triggers will be throttled.
  • Delays on free plans — scheduled workflows on free/shared runners can be delayed by up to 15–30 minutes during high load.
  • Inactive repos — GitHub disables scheduled workflows on repos with no activity for 60 days.
  • No seconds field — GitHub Actions uses standard 5-field cron, not 6-field (no seconds).

Checking when a workflow last ran

Go to your repo → Actions tab → click the workflow name → you will see all past runs with their trigger time. You can also manually trigger a scheduled workflow using workflow_dispatch for testing:

on:
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:     # allows manual trigger from the UI

Skipping runs when nothing changed

Add a check at the start of your job to exit early if there is nothing to do — this saves runner minutes:

steps:
  - uses: actions/checkout@v4
  - name: Check for changes
    id: check
    run: |
      git fetch origin main
      CHANGES=$(git diff HEAD origin/main --name-only | wc -l)
      echo "changes=$CHANGES" >> $GITHUB_OUTPUT
  - name: Run build
    if: steps.check.outputs.changes > 0
    run: npm run build

Build your cron expression

Not sure how to write the cron string for your schedule? Use the Dev Brains AI Cron Expression Generator — type your schedule in plain English and get the correct expression with a field-by-field breakdown.