How Many GitHub Actions Jobs Can Run at Once?
Three ceilings decide it: the concurrency key you wrote, the concurrent job limit on your GitHub plan of 20 to 500, and the runner capacity behind runs-on.
Last verified:
Answer
There is no single number, because three separate ceilings sit between a queued job and a running one: the concurrency key you wrote in workflow YAML, the concurrent job limit attached to your GitHub plan, and the capacity of the runner pool behind your runs-on labels. On GitHub-hosted runners the binding number is usually the plan ceiling, which runs from 20 concurrent jobs on Free to 500 on Enterprise, while jobs on managed or self-hosted labels sit outside that count and are limited only by how many matching runners can come online.
Take the three in order, because the fix for each is different.
1. The concurrency key you wrote. This is a throttle you opted into. A group holds one running item and one pending item; a third arrival cancels the pending one. Set at the workflow level it governs whole runs, and set inside a job it governs that job across runs. It never makes anything start sooner, so its symptoms are runs marked Waiting or Canceled rather than a long queue of jobs. The concurrency limit glossary entry covers the term itself.
2. The concurrent job ceiling on your GitHub plan. Standard GitHub-hosted runners draw from shared pools with a fixed ceiling per account, and macOS carries a lower sub-ceiling. Figures below come from GitHub's usage limits reference, checked on 2026-08-13.
| GitHub plan | Concurrent jobs | Concurrent macOS jobs |
|---|---|---|
| Free | 20 | 5 |
| Pro | 40 | 5 |
| Team | 60 | 5 |
| Enterprise | 500 | 50 |
The ceiling is account wide, so one repository fanning out a large matrix delays workflows in every other repository on the account. A matrix also has its own cap: 256 jobs per workflow run, from the same reference.
3. Runner capacity behind your labels. Jobs targeting self-hosted or managed labels are outside the plan ceiling. Their queue depends on how many matching runners can come online: the count of registered machines for a static fleet, the maximum runner setting for an autoscaled fleet such as an ARC scale set, or whatever the managed provider holds behind the label.
That third ceiling is the one a runner provider changes. Run as many jobs as your workflows need. Generally available Linux and Windows runners do not have plan-level concurrency caps, and capacity adjusts as workflows fan out. That statement carries the scope in the cloud runners documentation: features that are Generally Available support unlimited concurrency on Linux and Windows runners, while features in beta may carry limits. macOS sits outside it. macOS concurrency runs on a per-organization quota, so teams that need high macOS concurrency arrange the quota with support at [email protected]. Teams fanning out simulator and device test jobs should read the guide to running iOS jobs in parallel on GitHub Actions before sizing a macOS matrix.
Detail
Match the symptom to the ceiling before changing YAML
Each ceiling produces a different failure signature. Reading the signature costs a minute and saves a sprint of edits to the wrong file.
| What you see | Ceiling responsible |
|---|---|
| A run shows a Waiting banner naming a concurrency group | The concurrency key |
| A queued run flips to Canceled when a newer commit lands | The concurrency key |
| Running job count plateaus at 20, 40, 60, or 500 | GitHub plan ceiling |
| macOS jobs queue while Linux jobs on the same account start | GitHub plan ceiling, macOS sub-limit |
| Jobs on one label queue while other labels start on time | Runner capacity for that label |
| Jobs on a label never start and produce no logs | Label mismatch or runner group permissions |
A job whose labels match zero online runners waits forever, which reads as a capacity problem and is a routing problem. Check the label spelling against the catalog before adding capacity. The full symptom to mechanism walkthrough lives in the guide to GitHub Actions concurrency limits.
A workflow that throttles runs and fans out jobs
The two halves of concurrency control belong in the same file. The concurrency block below keeps one lane per branch, and the matrix underneath fans a test suite across 24 parallel jobs on two WarpBuild labels.
name: test-suite
on:
pull_request:
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
test:
strategy:
fail-fast: false
matrix:
runner:
- warp-ubuntu-latest-x64-4x
- warp-ubuntu-latest-arm64-4x
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm test -- --shard=${{ matrix.shard }}/12
package:
needs: test
runs-on: warp-windows-latest-x64-4x
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run packageFour details in that file decide how many jobs run at once:
- The
groupexpression builds one lane per workflow per ref, so a push to a feature branch never parks a run onmain. cancel-in-progresscancels superseded pull request runs and letsmainruns finish, which keeps deploy gates honest.- The matrix produces 2 labels by 12 shards, so 24 jobs start together. Both
runs-onvalues are managed labels, so those 24 jobs do not consume the plan ceiling. fail-fast: falsekeeps the other 23 shards running when one fails. Addmax-parallelunderstrategywhen a downstream dependency cannot absorb the fan-out.
The machines those labels resolve to
Every rate and shape below comes from the cloud runners documentation, checked on 2026-08-13.
| runs-on label | OS | vCPU | RAM | Storage | Rate per minute |
|---|---|---|---|---|---|
| warp-ubuntu-latest-x64-4x | Ubuntu 24.04 | 4 | 16GB | 150GB SSD | $0.008 |
| warp-ubuntu-latest-arm64-4x | Ubuntu 24.04 | 4 | 16GB | 150GB SSD | $0.006 |
| warp-windows-latest-x64-4x | Windows Server 2022 | 4 | 16GB | 256GB SSD | $0.016 |
| warp-macos-latest-arm64-6x | macOS | 6 | 22GB | 120GB SSD | $0.08 |
Linux sizes run from 2 vCPU to 32 vCPU on both architectures, Windows from 4 vCPU to 32 vCPU, and macOS at 6 or 12 vCPU. Rates scale with the size, so a 32 vCPU Linux x64 runner bills at $0.064 per minute. Full per-minute rates by runner type are on the pricing page.
Fan-out buys back wall clock time and leaves billed minutes alone
This is the part teams get wrong when they plan around a ceiling. Running 24 shards at once and running them 6 at a time burn the same compute. Concurrency buys back wall clock and leaves the invoice alone.
Assumptions, stated up front: the 24 shards from the workflow above, 12 on each label, each shard taking exactly 6 minutes, no retries, and no queue wait once a slot frees. Total compute is 24 jobs at 6 minutes each, so 144 job-minutes per run in every scenario.
| Scenario | Slots available | Waves | Wall clock per run | Billed job-minutes |
|---|---|---|---|---|
| Squeezed behind a busy account ceiling | 6 | 4 | 24 min | 144 |
| Room on a mid-tier plan ceiling | 12 | 2 | 12 min | 144 |
| Uncapped managed labels | 24 | 1 | 6 min | 144 |
Now price the 144 minutes at catalog rates, using 72 minutes on each of the two labels:
- 12 shards at 6 minutes on
warp-ubuntu-latest-x64-4x: 72 minutes at $0.008 equals $0.576. - 12 shards at 6 minutes on
warp-ubuntu-latest-arm64-4x: 72 minutes at $0.006 equals $0.432. - Total per run: $1.008, identical in all three rows of the table.
At 400 runs a month, that suite bills 57,600 job-minutes for $403.20. Moving from the 6-slot row to the uncapped row returns 18 minutes of wall clock per run, which is 7,200 minutes a month, or 120 hours of engineers waiting on a pull request check. The dollar column does not move.
Measure the ceiling instead of guessing at it
Queue wait per label is the measurement that separates the three ceilings, and it is the number to watch after a change. CI observability is part of the WarpBuild product surface alongside snapshot runners, remote Docker builders, an MCP server, and the Action Debugger.
The reports documentation covers three places the number appears:
- Queue Timings. P75 and P90 queue wait per runner label and stack, plus run count, a daily average chart, and CSV export.
- Jobs. Per-job queue time at P75 and P90 next to duration, CPU, and memory, aggregated per repository, workflow, and job name.
- Billing. Per-job cost with execution time and billed time, so the cost model above can be checked against real rows rather than assumptions.
Read the shape of the curve. A P90 that spikes during working hours and settles overnight points at contention for a shared ceiling. A P90 that stays high on one label around the clock points at capacity for that label. A P90 near zero on every label while pull requests still feel slow means the queue is fine and job duration is the problem. The guide to GitHub Actions queue times walks through each of those readings and the fix that matches it.
Related Questions
Does the concurrency key control how many jobs run in parallel?
No. The concurrency key holds one running item and one pending item per named group, so it throttles workflow runs, or single jobs when set at the job level. The number of jobs running in parallel inside one run comes from your GitHub plan ceiling and from runner capacity behind the runs-on labels. The guide to GitHub Actions concurrency limits shows the group shapes that cause accidental serialization.
How do I tell which ceiling is holding my jobs?
Measure queue wait per runner label before changing anything. The Queue Timings report breaks queue wait into P75 and P90 per runner label and stack with CSV export, documented in the reports documentation. A label whose P90 climbs while other labels start on time points at capacity for that label rather than at an account ceiling. The guide to GitHub Actions queue times maps each reading to a fix.
Do GitHub plan ceilings apply to jobs on WarpBuild runners?
The per-plan concurrent job ceilings count jobs on GitHub-hosted runners. Jobs that target warp- labels queue on pool capacity instead, and generally available Linux and Windows runners do not have plan-level concurrency caps for generally available features on Linux and Windows runners. macOS concurrency runs on a per-organization quota, so raise it with support at [email protected].
How many jobs can a single matrix create?
A matrix generates at most 256 jobs per workflow run, checked on 2026-08-13 against GitHub's usage limits reference. Past that, split the suite across workflows or collapse a matrix dimension, and use max-parallel when a downstream dependency cannot take the fan-out.
Price your own fan-out against the per-minute rates on the pricing page, then confirm the change with the Queue Timings numbers in the reports documentation.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.