Backend Engineering

GitHub Actions Cron: Time Zones, Delays and Missed Runs

GitHub Actions cron runs in UTC unless you set the timezone key. How to schedule in local time, and why scheduled runs are delayed or skipped.

Muhammad Yahya, authorMuhammad Yahya6 min read

GitHub Actions cron schedules run in UTC by default, but you no longer have to convert by hand: since March 2026, GitHub accepts a timezone key next to each cron line, set to an IANA name such as America/New_York. If a scheduled workflow runs late or not at all, the usual causes are high load at the start of the hour, a workflow file that is not on the default branch, or a public repository with no activity for 60 days, which switches schedules off automatically.

The short answer

  • Time zone: cron is read as UTC unless you add timezone. GitHub announced it in the March 19, 2026 changelog, so older answers saying time zones are not supported are out of date.
  • Delays: runs can start late under high load, especially at the start of every hour, and some queued jobs may be dropped.
  • Missed runs: schedules run only from the default branch, at most every 5 minutes, and stop in quiet public repositories after 60 days.

How GitHub Actions cron works

A schedule lives under on.schedule as a list of cron entries. Each uses POSIX cron syntax with five fields:

minute  hour  day-of-month  month  day-of-week
  15     4         *          *        1-5

That example means 04:15 UTC, Monday to Friday. Fields accept * for any value, , for lists, - for ranges and / for steps. Months can be 1 to 12 or JAN to DEC, and days of the week 0 to 6 or SUN to SAT.

A few rules catch people out:

  • Shortcuts such as @daily and @hourly are not supported.
  • The shortest interval is once every 5 minutes.
  • Without a timezone key, every field is evaluated in UTC.
  • A workflow can have several cron entries, and github.event.schedule tells you which one fired.

The free cron expression generator on this site previews the next run times for an expression and converts a schedule into GitHub Actions YAML.

Setting a time zone

Add timezone alongside cron in the same list item. The value is an IANA time zone name such as America/Chicago or Europe/London, not an abbreviation like EST:

name: Weekday report

on:
  schedule:
    - cron: "15 9 * * 1-5"
      timezone: "America/New_York"
  workflow_dispatch:

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: python3 scripts/weekday_report.py

This runs at 9:15 AM New York time on weekdays, all year, with no edits when the clocks change. The key belongs to the individual cron entry, not the whole workflow.

What happens at daylight saving time

GitHub documents one edge case: on the spring-forward night, a schedule inside the skipped hour advances to the next valid time, so a 2:30 AM job runs at 3:00 AM. The docs do not describe the fall-back night, when 1:00 to 1:59 AM happens twice in US time zones. The simple fix is to keep important jobs out of the 1:00 to 3:00 AM local window.

If you keep the schedule in UTC

Without timezone, you convert local time yourself, and daylight saving time changes the answer. For a report due at 9:15 AM New York time:

  • From March 8 to October 31, 2026, New York is on EDT (UTC-4), so the cron is 15 13 * * 1-5.
  • From November 1, 2026, New York is on EST (UTC-5), so the cron becomes 15 14 * * 1-5.

So a UTC cron drifts by an hour in local time twice a year unless someone edits it. Setting timezone removes that chore.

Why scheduled runs are delayed or skipped

These causes come from GitHub's documentation:

  • Load at the start of the hour. The schedule event can be delayed during high load, and the start of every hour is one such time. If the load is high enough, some queued jobs may be dropped, so that run never happens.
  • The file is not on the default branch. Scheduled workflows run only on the default branch, using its latest commit. A schedule added on a feature branch does nothing until it is merged.
  • 60 days without activity. In a public repository, scheduled workflows are disabled automatically when no repository activity has occurred in 60 days.
  • Forks. When a public repository is forked, scheduled workflows in the fork are disabled by default.
  • Unsupported syntax. @daily style shortcuts do not work, and nothing runs more often than every 5 minutes.
  • An inactive actor. With Enterprise Managed Users, scheduled workflows stop if the user account tied to the workflow is suspended, deleted or deprovisioned.

Every scheduled run is attributed to a user, the actor. Notifications go to the user who created the workflow, then to whoever last changed the cron syntax or re-enabled a disabled workflow. If that person leaves, failure emails may reach an inbox nobody reads.

A debugging checklist

  1. 1Confirm the workflow file is on the default branch, and that you edited that copy.
  2. 2Run gh workflow list --all or open the Actions tab to see whether the workflow was disabled. Re-enable it with gh workflow enable.
  3. 3Run gh run list --workflow report.yml --event schedule to see whether runs started late or never started.
  4. 4Read the cron fields in order: minute, hour, day of month, month, day of week.
  5. 5Without a timezone key, convert the expected local time to UTC before deciding a run is late.
  6. 6Check that timezone is a valid IANA name, indented at the same level as cron.
  7. 7Trigger the workflow by hand with gh workflow run report.yml (this needs workflow_dispatch) to separate scheduling problems from job failures.
  8. 8If the run starts but fails, it is not a cron problem. Read the job logs.

Good practices for scheduled workflows

  • Avoid minute 0. GitHub suggests a different time of the hour to reduce delays. 17 6 * * * is safer than 0 6 * * *.
  • Add a manual trigger. workflow_dispatch gives you a Run workflow button for testing.
  • Prevent overlaps. With concurrency and a fixed group name, only one run is in progress at a time. A new run waits as pending, and a newer one replaces it.
  • Set a timeout. The default job timeout is 360 minutes, so set timeout-minutes to something realistic.
  • Alert on failure. Turn on Actions notifications for failed runs, or add a final step with if: failure() that posts to your team chat.
  • Watch for runs that never start. A failure alert cannot fire for a dropped run. For jobs that matter, ping a heartbeat monitor that alerts you when the ping is missing.

Put together:

name: Nightly sync

on:
  schedule:
    - cron: "17 6 * * *"
      timezone: "Europe/London"
  workflow_dispatch:

concurrency:
  group: nightly-sync
  cancel-in-progress: false

jobs:
  sync:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v6
      - name: Run the sync
        run: python3 scripts/sync.py
      - name: Report failure
        if: failure()
        run: python3 scripts/notify_failure.py

Here notify_failure.py stands in for whatever posts to your chat, with its webhook URL passed in from a repository secret.

When to move scheduling elsewhere

GitHub Actions cron suits repository chores: nightly tests, dependency checks and small reports. It is a weaker fit when a job must start on the minute or more often than every 5 minutes, when a missed run causes real damage such as unsent invoices, or when the job needs your production database or private network.

  • Server cron is the simplest option, running next to the code and data it needs.
  • Celery beat suits apps that already run Celery workers, with a time zone setting and task retries. See these Celery crontab examples.
  • APScheduler fits a single Python service that needs in-process scheduling, with cron-style triggers and time zone support.

Many teams use both: GitHub Actions for CI, as in this CI/CD pipeline for Python backends, and an application scheduler for business jobs such as a scheduled Excel report.

Next steps

Set the time zone explicitly, avoid minute 0, keep a manual trigger and alert on both failures and missing runs. If your scheduled jobs have grown into business processes that need retries and monitoring, my business process automation page explains how I build them to run reliably.

Frequently asked questions

Does GitHub Actions cron support time zones?

Yes. GitHub added time zone support for scheduled workflows in March 2026. Add a timezone key next to the cron line with an IANA time zone name such as America/New_York. Without that key, every schedule runs in UTC.

Why is my GitHub Actions scheduled workflow not running?

Check four things first: the workflow file must be on the default branch, the workflow may have been disabled automatically after 60 days without activity in a public repository, schedules are disabled by default in forks of public repositories, and shortcuts such as @daily are not supported. Adding workflow_dispatch lets you run it by hand to separate a scheduling problem from a failing job.

Why does my GitHub Actions cron job start late?

The schedule event can be delayed when GitHub Actions is under high load, and the start of every hour is a high-load time. If the load is high enough, some queued jobs may be dropped. Scheduling at a less common minute, such as 17 past the hour, reduces the chance of delay.

What is the shortest interval for a GitHub Actions schedule?

Once every 5 minutes. If you need jobs that run every minute or must start at an exact time, use a scheduler that runs next to your application instead, such as server cron, Celery beat or APScheduler.

Related reading

Muhammad Yahya

Written by Muhammad Yahya

Python Automation & Backend Developer. Top Rated on Upwork with a 100% Job Success Score and 5+ years building automations, AI workflows and production backends.

Hire Me on Upwork

More articles