---
title: "How to reduce GitHub Actions build times"
description: "Reduce GitHub Actions build times by removing redundant work, measuring cache overhead, shortening the critical path, and testing matched runners."
url: https://9apes.com/blog/reduce-github-actions-build-times/
---

# How to reduce GitHub Actions build times

The fastest ways to reduce GitHub Actions build times are to cancel superseded pull request runs, remove unnecessary work, reuse dependencies where restoration is cheaper than rebuilding, and shorten the slowest required job chain. Change runner size or provider after identifying whether CPU, storage, network, or queue time limits that chain.

Start with the delay developers experience. A faster compiler can help, but it does not remove slow provisioning, repeated toolchain setup, or a long integration test waiting on another service.

## Measure feedback time before optimizing individual steps

Record two separate outcomes for each representative pull request:

- **Time to the first required check result:** when a required check first reports a result developers can act on.
- **Time to full required-check completion:** when every required check has completed, including whether they passed.

The first result can identify a broken change quickly. Full completion determines when the checks stop delaying a merge. Improving one does not guarantee an improvement in the other.

Explain elapsed time with queueing, provisioning, setup, execution along the critical path, and finalization. The critical path is the dependency chain determining when the last required check can finish. Trace those phases along the actual timeline, including waits before downstream jobs. Do not add durations from jobs that run in parallel and call the result developer wait time.

For a hypothetical illustration, a workflow might spend one minute queueing, one provisioning, two setting up, eight executing required work, and one finalizing. Its elapsed time is thirteen minutes. If compilation occupies only two of those eight execution minutes, halving compilation saves one minute, not half the workflow. These numbers illustrate a time budget; they are not benchmark measurements.

## Cancel superseded pull request runs

When a developer pushes a new commit, an older validation run may no longer provide useful feedback. A concurrency group scoped to the workflow and pull request can cancel superseded CI runs, freeing capacity for current work. GitHub documents this behavior under [workflow concurrency](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency).

Apply cancellation to replaceable validation work. Deployment, migration, and release operations need their own completion and recovery policy. Do not copy a cancellation setting across every workflow without considering what an interrupted operation leaves behind.

## Remove jobs that do not contribute useful coverage

Inspect duplicate triggers, repeated dependency installation, oversized test matrices, and jobs unrelated to the changed code. Keep the coverage your project needs; move appropriate exhaustive checks to a separate scheduled workflow when that matches your release policy.

Check branch protection before adding path filters. GitHub states that a workflow skipped by path or branch filtering can leave associated required checks pending and block merging. A conditionally skipped job behaves differently. Review the [required-check guidance](https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/troubleshooting-required-status-checks) and test documentation-only changes, relevant code changes, and failures before adopting selective execution.

## Count cache transfer and save time

A useful cache avoids more work than it adds. Measure restoration, extraction, the remaining build, and cache saving together. Compare unchanged inputs, ordinary source edits, and dependency updates. A high hit rate on rare unchanged builds can hide poor performance on everyday changes.

Dependency caches, Docker layers, and compiler caches preserve different things. A fresh runner or microVM does not automatically retain a previous job's local cache. Establish which store persists the data and how the next job restores it. GitHub's [dependency caching overview](https://docs.github.com/en/actions/concepts/workflows-and-actions/dependency-caching) explains reuse across runs; Docker documents separate handling for [cache mounts in Actions](https://docs.docker.com/build/ci/github-actions/cache/).

For Docker builds, copy dependency manifests before frequently changing source files, and keep unrelated files out of the build context. Use the measured [Docker layer caching guide](/blog/docker-layer-caching-github-actions/) for the workflow and logs. Its small fixture demonstrates why a valid cache hit and a useful time saving are separate findings.

## Shorten the bottleneck, then pilot matched runners

Read the slowest required job's logs before buying more CPU. Parallel compilation may benefit from additional cores. Downloads, serialized tasks, and external service waits require different fixes. Splitting a job can also repeat setup and increase queueing, so measure the complete result.

For C++ workloads, the [C++ and Bazel runner guide](/blog/github-actions-runners-cpp-bazel-builds/) separates compilation, linking, memory, and cache behavior.

For a runner pilot, hold the commit, commands, architecture, toolchain, cache policy, and vCPU count constant. Repeat representative changes on both offerings. Record queue and startup time, complete job duration, required-check completion, failures, and cost. Keep raw observations so a median does not hide a slow tail.

The [9apes benchmarks](/benchmarks/) publish build-step measurements for compile-heavy workloads, with scope and limitations. They do not measure your pull request feedback time. To test your own job, follow the [quick start](/docs/quick-start/) and compare the current [runner pricing](/pricing/) against complete billed work. For a provider decision, compare operational responsibilities and isolation with the [managed runner selection guide](/blog/managed-github-actions-runners/). For the spend side of this process, read [how to cut your GitHub Actions bill](/blog/cut-your-github-actions-bill/).

## Why is GitHub Actions slow even with caching?

The cache may miss expensive work, take too long to transfer, or leave queueing and setup untouched. Check the complete timeline and the first expensive step that executes again.

## Will a larger runner always reduce build time?

No. Extra CPU helps only when the workload can use it. Test the bottleneck and compare complete required-check completion before changing your default runner size.

## What should I optimize first in a slow workflow?

Remove superseded and redundant work, then measure the critical path. Fix the largest avoidable delay and repeat the same comparison before choosing the next change.
