Skip to content

Blog

Are self-hosted GitHub Actions runners free, and what do they really cost?

·

Not in a way that shows up on a GitHub invoice. GitHub does not charge for self-hosted runner minutes today: it announced a $0.002 per-minute platform charge for 1 March 2026, then postponed it with no new date. What you pay for is everything else: machines that sit idle, autoscaling, upgrades, cleanup and on-call.

We run runners for a living, so the operating costs below are ones we have met. The GitHub facts were read from GitHub’s own documentation and changelog on 10 November 2026 and are linked. The cost model near the end states every assumption, so you can replace ours with your own.

What does GitHub charge for self-hosted runner minutes today?

Nothing per minute. GitHub’s billing page lists self-hosted runners among the usage that “remains free independently and does not reduce the included compute usage”, and its runner pricing page has no self-hosted rate.

The charge was real, though. On 16 December 2025 GitHub announced that from 1 March 2026 a $0.002 per-minute platform charge would apply to self-hosted runner usage in private repositories. On 19 December a GitHub reply on the roadmap item said: “We’re postponing the announced billing change for self-hosted GitHub Actions to take time to re-evaluate our approach.” We found no newer date as of 10 November 2026.

A postponement is not a cancellation, so treat the free minutes as a status and not a promise. If the charge returns, it applies to your own fleet. GitHub has not said whether a runner a vendor operates for you counts as “self-hosted runner usage”, so ask any provider, including us. The pricing post has the sourced detail of the announcement and the rates it sat beside.

What does a self-hosted fleet cost beyond the machines?

Six things that appear on no invoice: capacity kept up for the busiest hour, the autoscaler, upgrades, cleanup, isolation and the person who answers when it stops.

Idle capacity. A fleet has to be sized for the busiest hour, not the average one, and a machine costs the same whether a job is running on it or not. The share of its up-time a machine spends running jobs is its utilisation, and it decides what a minute really costs, as the model below shows.

Autoscaling. The usual answer is Actions Runner Controller, which GitHub describes as a Kubernetes operator that scales self-hosted runners. Using it means running a Kubernetes cluster and the controller as well as the runners. GitHub’s self-hosted runner reference also says autoscaling with persistent runners is not recommended and points to ephemeral, just-in-time runners created through the REST API.

Upgrades. The runner application has a deadline: if you do not update it inside a 63-day window after a new version becomes available, GitHub stops assigning it jobs. It also has to be updated across every image and machine you keep.

Cleanup. Disks, Docker layers, workspaces and temporary files accumulate on a machine that outlives its jobs, and nothing removes them unless you wrote the thing that does.

Isolation. GitHub’s hardening guide says self-hosted runners have no guarantee of running in ephemeral clean virtual machines and can be persistently compromised by untrusted code in a workflow. Giving every job a clean machine is work you take on, and what one machine per job rules out is worth reading before you decide how much of it to do.

On-call. When runners go offline, jobs wait, and someone has to notice. That person is rarely free at a convenient hour, and their time is the largest line in most honest fleet budgets.

What breaks in practice with self-hosted runners?

Four things, usually: a runner too old to be given jobs, a disk that fills, a runner that goes offline, and state one job leaves for the next.

The runner version. Runners update themselves by default, and GitHub stops assigning jobs to one that falls outside its 63-day update window. For a critical security update it stops until the runner is updated. GitHub has also set a minimum runner version on GitHub Enterprise Cloud, 2.329.0 to register, with enforcement from 29 September 2026. A fleet built from an image baked months ago is the one that meets these. On our runners each job gets its own single-use runner, so there is no long-lived one for you to keep within the window.

Disks filling. In our experience disks fill slowly and then all at once, and a full disk fails jobs with errors that look unrelated to storage. A cleanup step is a stopgap, not a fix, but it is cheap:

jobs: test: runs-on: [self-hosted, linux, x64] timeout-minutes: 30 steps: - uses: actions/checkout@v7 - run: npm ci && npm test - name: Leave the machine as the next job expects it if: always() run: | df -h / docker system prune --force --filter "until=24h" || true

Runners going offline. GitHub removes a self-hosted runner that has not connected for more than 14 days, and an ephemeral one after more than a day. A job that no online runner can take waits, and one queued for more than 24 hours fails. From the workflow the two look the same, which is why the list of causes of a job that waits starts with the labels and not the machines.

State leaking between jobs. The hardening guide says you can use the REST API to create ephemeral just-in-time runners, and warns that nothing guarantees a self-hosted runner only runs one job. A leaked file, a cached credential or a stale build directory is a bug that appears only sometimes, on one machine, which is the most expensive kind to find.

What does each option cost? A worked model of hosted, self-hosted and managed

At low utilisation a self-hosted fleet costs several times what the same minutes cost on a hosted or managed runner. It wins only when its machines are busy most of the time. Here is the arithmetic, with every input stated.

The assumptions. A slot is one 2-vCPU job at a time, the size GitHub gives a standard runner in a private repository. A month is 730 hours, so a slot has 43,800 minutes to give. A slot costs $50 a month in machine share, an assumption to replace with your own quote. Upkeep is one engineer at $100 an hour: 4 hours a month for a fleet of up to 15 slots, 8 for 20. Slots are sized for the busiest hour. GitHub-hosted is $0.006 a minute, the 2-core Linux rate on GitHub’s pricing page on 10 November 2026, with no included allowance deducted.

Job-minutes a monthSlots kept upUtilisationSelf-hosted a monthPer minute usedGitHub-hosted a month
20,00059%$650$0.0325$120
100,0001515%$1,150$0.0115$600
400,0002046%$1,800$0.0045$2,400
700,0002080%$1,800$0.0026$4,200

Reading it. In the first two rows a minute on the self-hosted fleet costs more than GitHub’s own hosted minute, and that is before the engineer: with upkeep set to zero the first two rows come to $0.0125 and $0.0075 a minute, still above $0.006. At 46 per cent utilisation the fleet is cheaper than hosted, by about $600 a month. At 80 per cent it is cheaper by about $2,400, and that needs a load as flat as most CI queues are not.

Where managed comes in. A managed runner has no idle row and no upkeep row: its bill is job-minutes times a rate, and queued time is not billed on ours. Put the per-minute rate on the pricing page against the “per minute used” column. Wherever the fleet costs more per minute than that rate, managed costs less overall, and in this model that holds through the 15 per cent row. The 46 per cent row is close enough to the line that your own quote for the slot decides it, so put your numbers into the calculator instead of ours.

What the model leaves out. Storage, networking, spare machines, Windows licences, security work, and jobs that need more than two vCPUs. It also ignores the postponed charge: if it returns it adds $0.002 a private-repository minute to the self-hosted rows, $40 a month on the first row, and possibly to a managed runner as well.

When is self-hosting the right choice?

When the machine is the point, or when it is busy nearly all the time. There are four good reasons. Hardware we do not sell: a GPU, macOS, or nested virtualisation, none of which our runners have. Jobs that must run inside your network, next to a database or a registry you cannot expose. Jobs longer than an hour: a job may run for 60 minutes on ours, against six hours on GitHub-hosted runners and five days on self-hosted ones. And steady, high utilisation, roughly half or more (in this model the 20-slot fleet costs less per minute than our rate from about 51 per cent), with a person already employed to run infrastructure.

If none of those is you, the question is whether the upkeep is worth what you would save, and for a team under a few hundred thousand minutes a month the model says it is not. Moving is one line, made by hand:

jobs: test: - runs-on: [self-hosted, linux, x64] + runs-on: runnersio-2vcpu-ubuntu-2404 steps: - uses: actions/checkout@v7 - run: npm ci && npm test

Our label goes in runs-on on its own: beside self-hosted it is refused, because GitHub hands a job only to a runner carrying every label it names. The moving guide has the label mapping. The migration page opens a pull request for workflows that name GitHub-hosted labels, and leaves a [self-hosted, ...] list as written with the reason, so edit those lines yourself; once a job is on our label, its checks run on our runners. Per-minute rates are on the pricing page.

Run your CI and your infrastructure on one bill

Connect GitHub

Card required. You pay only for the minutes your jobs run.