Scheduled GitHub Actions: Run Workflows on a Cron Schedule
How to run a GitHub Actions workflow on a schedule with on.schedule and cron syntax: examples, the new timezone option, why scheduled runs start late or get skipped, the 60-day inactivity rule, running on demand with workflow_dispatch, and what to use instead when timing really matters.
GitHub Actions can run a workflow on a schedule — nightly tests, a weekly dependency report, a daily data sync, a cleanup job. You define it with a cron expression under on.schedule.
The basics
# .github/workflows/nightly.yml
name: Nightly
on:
schedule:
- cron: '17 3 * * *' # every day at 03:17 UTC
workflow_dispatch: # also allow manual runs
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm test
The five cron fields are minute, hour, day of month, month, and day of week. (Cron Expressions Explained)
| Cron | Runs |
|---|---|
*/15 * * * * |
Every 15 minutes |
0 * * * * |
Every hour |
30 6 * * 1-5 |
Weekdays at 06:30 |
0 9 * * 1 |
Mondays at 09:00 |
0 0 1 * * |
First of every month |
Timezones
Schedules run in UTC by default. GitHub now supports an optional IANA timezone per schedule:
on:
schedule:
- cron: '30 5 * * 1-5'
timezone: "America/New_York"
With daylight-saving zones, times in a skipped "spring forward" hour move to the next valid time. If you stay on UTC, remember your local run time shifts by an hour twice a year.
The gotchas
1. Minimum interval is 5 minutes. Anything more frequent isn't allowed.
2. Runs can start late — or be dropped. Scheduled jobs are queued and can be delayed during busy periods, especially at the top of the hour when everyone schedules 0 * * * *. Under heavy load, some queued runs may be skipped. Pick an odd minute (17, 43) to reduce the chance.
3. They only run on the default branch. The workflow file must exist on main (or your default branch), and it runs against the latest commit there. A schedule added on a feature branch does nothing.
4. Inactive public repos stop running them. In public repositories, scheduled workflows are disabled automatically after 60 days without repository activity. You'll need to re-enable them in the Actions tab.
5. They use your Actions minutes on private repositories. A job every 5 minutes adds up. (GitHub Actions CI Basics)
Run it now with workflow_dispatch
Always add workflow_dispatch: alongside schedule. Then you can test the workflow from the Actions tab ("Run workflow") or the CLI without waiting for the next tick:
gh workflow run nightly.yml
You can add inputs to workflow_dispatch for manual parameters.
Know when it fails
A scheduled job that fails silently is worse than none. GitHub emails the person who last modified the cron line about failures, which may not be you. Add an explicit notification step for failures (Slack, email, an issue) using if: failure(). (Know When Your App Is Down)
When not to use Actions schedules
- Exact timing matters (billing at midnight, reminders at 9:00 sharp) — use a scheduler on your own server or a job queue. (Background Jobs and Cron, systemd Timers vs Cron)
- The job needs your production database or private network — don't open your database to GitHub's runners; run it next to the database.
- AI-agent tasks on a schedule — Claude Code routines can run on a schedule in the cloud with your repos and connectors. (Claude Code Routines)
The summary
on.schedulewith a cron expression; UTC unless you settimezone.- Expect delays, use odd minutes, and remember the 60-day rule for public repos.
- Add
workflow_dispatchto test, and alert on failures.
EasySpawn gives your app a persistent server where scheduled jobs run next to your database at the exact time you set, with Claude Code to write them. See how it works or join the waitlist.
Related: GitHub Actions Caching · GitHub Actions Secrets · Dependabot Explained · What Is CI/CD?
Keep reading
GitHub Actions Caching: Faster CI With setup-node, actions/cache and cache-mode
How to make GitHub Actions workflows faster with caching: the built-in cache option in setup-node and setup-python, actions/cache for anything else, good cache keys, Docker layer caching, the new cache-mode setting that limits cache access to prevent cache poisoning, and why caches miss.
Deploy to a VPS With GitHub Actions: Push to Main, Ship to Production
Automate deploys to your own server: run tests in GitHub Actions, then SSH in and run your deploy script — or build a Docker image and pull it. Deploy keys, secrets, known_hosts, environments with approval, rollbacks, and keeping the SSH key's power limited.