Why is GitHub Actions slow?
GitHub Actions is slow when jobs start cold, wait in a queue, or run on small 2-core runners. Find the slow step first, then fix that one cause.
The short answer
If GitHub Actions is slow for you, the time usually goes to one of a few places. Jobs download the same files each run. They start on a new, empty machine. They run on a small runner. Or they wait in line before they start. Each cause has its own fix, and some take five minutes.
Find where the time goes
Open the run
On GitHub, open the Actions tab and click a slow run.
Open the job
Click the slow job. Each step shows how long it took.
Check the wait
Compare when the run was queued with when the job started. The gap is time spent in line.
Time big steps
If one step runs many commands, time each one.
- name: Build run: | time npm ci time npm run build
Write the numbers down. You need them to see if a fix worked.
Cold caches and downloads
A GitHub-hosted runner starts empty for each job. So each job downloads tools, packages and your repo again. That can take minutes before real work starts. Most setup actions can cache for you:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
Caching helps a lot. But a restore is still a download. Our guide to the GitHub Actions cache shows what to cache and what to skip.
Small 2-core runners
GitHub's standard Linux runner has 2 cores. Big builds, long test runs and Docker builds run short of CPU and memory there. Your laptop may have 8 cores or more. That is why a job can be slower on GitHub than at your desk. GitHub also sells larger runners. They cost more per minute, so check GitHub's pricing docs first.
Waiting in the queue
A job can wait before it starts. This happens when you hit your plan's limit on jobs at once. It happens more on macOS, where hosted Macs are scarce. Read why macOS jobs are slow if that is you. On your own runners, jobs wait when every runner is busy.
Fixes you can make today
- Turn on the cache option in your setup action, as shown above.
- Keep checkout shallow. The default fetches one commit. Only set
fetch-depth: 0when you need history. - Cancel old runs when you push again to the same branch:
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - Skip runs when only docs change:
on: push: paths: - 'src/**' - 'package.json' - Split a long test suite across a matrix, so parts run at once.
Run jobs on a faster machine you own
For fast GitHub builds, a warm machine beats an empty one. Its tools are already installed, and its caches stay between jobs. Codemany runs your GitHub Actions jobs on your own Mac or Linux machine. You change one line:
runs-on: [self-hosted, codemany]
Each job gets a fresh single-use runner. Downloads from actions/setup-* steps stay in a local cache between jobs. Go modules come through a local package mirror. On Codemany's public benchmark, jobs ran 1.5× to 4.3× faster on private code than on GitHub's 2-core runners. You can read the test code at github.com/codemanyhq/bench. For a faster GitHub setup, follow Get started.
Questions
Why is my workflow slower than on my laptop?
Hosted runners are small and start cold every time. Your laptop has more cores, and its tools and caches are already there.
How much faster is my own machine?
On Codemany's public benchmark, 1.5x to 4.3x faster than GitHub's 2-core runners on private code. Your result depends on your machine and your jobs.
Will caching alone fix it?
Often partly. A warm machine also skips setup steps and downloads.
Related
Read next: benchmarks, local cache, the GitHub Actions cache and slow macOS jobs.
Start a 14-day trial $20 a month for 2 machines. No card needed.