You can automate any script to run on a schedule without managing a server. GitHub Actions executes code at specified intervals using a YAML file with cron syntax — commit the workflow to your repository, and GitHub runs it in the cloud. The question most tutorials skip: when does this actually make sense, and when does it quietly fail you?
Key Takeaways
- Workflows use cron syntax to trigger scripts at specific times, stored in a
.github/workflowsdirectory - Python, Node.js, shell scripts, or any executable runs directly in GitHub's cloud environment
- Execution logs and errors appear in the Actions tab — debugging is built in
Before You Start
GitHub Actions runs workflows inside GitHub's cloud infrastructure. No server required, but you do need a repository with code to execute. Scheduled workflows are free for public repositories. Private repositories include 2,000 minutes per month in the free tier. A script running for 5 minutes daily consumes 150 minutes monthly. If your script makes API calls, verify rate limits and authentication methods before scheduling — silent failures are common when auth tokens expire mid-month.
What You Need
- A GitHub account with repository creation access
- A script file already written and tested locally — Python, JavaScript, shell, or other executable code
- Basic familiarity with YAML syntax (indentation matters; tabs break workflows)
- API keys or secrets your script requires, ready to store as GitHub repository secrets
Step 1: Create the Workflow Directory Structure
Navigate to your repository's root and create the required folder. GitHub only recognizes workflows in .github/workflows/ — the exact path matters. Run mkdir -p .github/workflows from your terminal. This hidden folder tells GitHub where to find automation instructions. Wrong capitalization or a singular workflow folder breaks detection entirely.
Step 2: Write the Workflow YAML File
Inside .github/workflows/, create scheduled-script.yml. The filename can be anything ending in .yml or .yaml — choose something descriptive. Open the file and add this structure:
name: Run Script on Schedule
on:
schedule:
- cron: '0 9 * * *'
jobs:
run-script:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Execute script
run: python script.py
This workflow checks out your code, installs Python 3.11, installs dependencies from requirements.txt, and runs script.py. The cron: '0 9 * * *' line schedules execution at 9:00 AM UTC daily. Cron uses five fields: minute, hour, day of month, month, day of week. Adjust to match your needs, but remember: GitHub Actions always uses UTC time, which trips up most first-time users.
Step 3: Configure the Cron Schedule
Cron syntax is compact but unforgiving. The expression '30 14 * * 1' runs every Monday at 2:30 PM UTC. The expression '0 */6 * * *' runs every 6 hours. GitHub Actions uses standard cron format but operates on UTC only — if you schedule '0 14 * * *' expecting 2:00 PM EST, you'll get 2:00 PM UTC, which is 9:00 AM EST. Convert your local time zone before committing.
The shortest interval GitHub allows is every 5 minutes using '*/5 * * * *'. Frequent runs consume your minute quota rapidly. Test your cron expression at crontab.guru to verify timing before deployment.
Step 4: Add Environment Variables and Secrets
If your script requires API keys, database credentials, or configuration values, store them as repository secrets. Navigate to Settings → Secrets and variables → Actions. Click New repository secret, name it (for example, API_KEY), and paste the value. In your workflow file, reference secrets using the env block:
- name: Execute script
env:
API_KEY: ${{ secrets.API_KEY }}
run: python script.py
Your script reads API_KEY from environment variables using os.getenv('API_KEY') in Python or process.env.API_KEY in Node.js. Secrets never appear in logs, even when workflows fail. If you accidentally commit a secret as plain text, rotate the credential immediately — GitHub does not redact hardcoded secrets from public repositories.
Step 5: Commit and Push the Workflow
Stage your workflow file with git add .github/workflows/scheduled-script.yml, commit with git commit -m "Add scheduled workflow", and push to GitHub using git push origin main. GitHub detects the workflow within seconds. Navigate to the Actions tab — you should see your workflow listed under "All workflows" even if it hasn't run yet. The next scheduled time appears in the workflow details.
GitHub does not execute the workflow immediately upon commit. It waits for the next cron interval.
Step 6: Verify Execution and Read Logs
After the scheduled time passes, click the Actions tab and select your workflow name. Each run creates a new entry with a timestamp and status badge — green for success, red for failure, yellow for in-progress. Click a specific run to expand the job steps. Each step shows command output, error messages, and execution time. If your script writes to stdout, that output appears in the "Execute script" step logs.
Failed runs remain visible for 90 days. You can debug what went wrong weeks after the fact.
Step 7: Adjust for Different Script Types
For Node.js scripts, replace the Python setup steps with:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Execute script
run: node script.js
For shell scripts, skip language setup entirely: run: bash script.sh. For compiled languages like Go, add a build step before execution. The runs-on parameter supports ubuntu-latest, windows-latest, and macos-latest — you can test platform-specific behavior without separate infrastructure.
"GitHub Actions replaces cron jobs for most teams because logs, secrets management, and environment reproducibility are built in." — GitHub Docs, Workflow Syntax Reference
Common Problems
Workflow does not appear in the Actions tab: Confirm the .github/workflows/ path is exact. Extra spaces, wrong capitalization, or placing the file in .github/workflow/ (singular) breaks detection. Push a trivial change to the workflow file to force GitHub to re-index.
Cron schedule triggers at the wrong time: GitHub Actions uses UTC exclusively. Convert your desired local time to UTC before setting the cron expression. This is the most common mistake developers make when setting up scheduled workflows for the first time.
Script fails with "command not found" errors: The workflow environment is a fresh Ubuntu container with minimal packages. Install dependencies explicitly — if your script needs curl, jq, or system libraries, add run: sudo apt-get install -y curl jq before execution. Do not assume tools available on your local machine exist in the runner.
Best Practices
- Add a manual trigger using
workflow_dispatchin theonblock so you can test immediately without waiting for the next cron interval - Set a timeout with
timeout-minutes: 10at the job level to prevent runaway scripts from consuming your entire monthly quota - Use
actions/cacheto cache dependencies between runs — this reduces execution time and quota usage for workflows that reinstall packages every run - Enable email notifications in Settings → Notifications → Actions so failed scheduled runs alert you immediately rather than failing silently for days
- Pin action versions to specific tags (for example,
actions/checkout@v4) instead of using@main, which can introduce breaking changes without warning
When Not to Use This
Here's where most coverage stops, and where the interesting constraints begin. GitHub Actions scheduled workflows work well for daily data syncs, weekly report generation, or hourly status checks. They fail quietly in scenarios that look similar but have different operational requirements.
The shortest interval is 5 minutes. If you need second-level precision or sub-minute scheduling, use AWS EventBridge or a traditional cron server. GitHub does not guarantee exact execution times — during high load periods, scheduled workflows can delay by several minutes. For time-critical operations like trading algorithms or real-time monitoring, this unpredictability breaks the system.
Workflows consuming more than 6 hours are terminated automatically. Long-running data processing jobs, video encoding, or large ETL pipelines should run on dedicated compute. If your script requires persistent storage between runs, GitHub Actions is not designed for this — the runner filesystem is ephemeral. Any files created during execution disappear when the job completes.
What most developers discover after a month: the free tier works until it doesn't. A handful of workflows running multiple times daily consumes the 2,000-minute quota faster than expected, especially when dependency installation takes 2-3 minutes per run.
FAQ
How do I set up a cron schedule in GitHub Actions?
Add a schedule block under on in your workflow YAML with a cron expression. The format is cron: 'minute hour day month weekday' using UTC time. For example, cron: '0 3 * * *' runs at 3:00 AM UTC daily. GitHub validates the syntax when you commit — invalid expressions prevent the workflow from saving.
How do I run a Python script daily with GitHub Actions?
Create a workflow file with schedule: - cron: '0 0 * * *' to run at midnight UTC. Add steps to check out your code, install Python using actions/setup-python, install dependencies with pip install -r requirements.txt, and execute your script with run: python your_script.py. Commit the workflow file to .github/workflows/ and GitHub handles the rest.
How do I trigger a GitHub Actions workflow automatically?
Workflows trigger automatically when you define an event in the on block. Use schedule for time-based triggers, push for code commits, pull_request for PR activity, or workflow_dispatch for manual runs from the Actions tab. Multiple triggers can coexist — a workflow with both schedule and workflow_dispatch runs on a timer and allows on-demand execution.
Can I see when my scheduled workflow last ran?
Yes. Navigate to the Actions tab and select your workflow. The run history shows every execution with timestamps, duration, and status. Runs are retained for 90 days by default. If your workflow has never run, GitHub displays the next scheduled execution time in the workflow overview.
```