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

  1. Open the run

    On GitHub, open the Actions tab and click a slow run.

  2. Open the job

    Click the slow job. Each step shows how long it took.

  3. Check the wait

    Compare when the run was queued with when the job started. The gap is time spent in line.

  4. 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

  1. Turn on the cache option in your setup action, as shown above.
  2. Keep checkout shallow. The default fetches one commit. Only set fetch-depth: 0 when you need history.
  3. Cancel old runs when you push again to the same branch:
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
      cancel-in-progress: true
  4. Skip runs when only docs change:
    on:
      push:
        paths:
          - 'src/**'
          - 'package.json'
  5. 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.

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.