GitHub charges nothing for a self-hosted runner, and that is the least useful thing to know about one. A standard Linux minute on a GitHub-hosted runner costs USD 0.006, so the bill almost never settles self-hosted vs GitHub-hosted runners. What settles it is whether your job has to open a connection into something that refuses the internet. After that, it is where in Kenya the machine sits when the power goes.
CloudSpinx builds delivery pipelines for engineering teams in Kenya and across East Africa through our DevOps and CI/CD practice, and where the runners live is one of the first things we settle on any of them. The minutes arithmetic already has a home in what CI/CD costs per minute, so this post skips it and takes reach, power and upkeep instead, with two numbers we counted from GitHub's own API.
Self-hosted vs GitHub-hosted: does the job have to reach in?
If it does not, use GitHub-hosted runners. Building, testing, scanning and pushing an image are all outbound connections, and a hosted runner makes those from a clean, ephemeral virtual machine built from an image GitHub maintains.
The trouble starts at deploy. An SSH session into a server in a Nairobi datacenter whose firewall admits known addresses only. A migration against a database with no public address. A kubectl apply against a cluster API that sits behind the office VPN. Each of those needs an address the other end trusts, and the reflex is to allowlist GitHub's runners.
Why not allowlist GitHub's IP ranges?
Because there are thousands of them, and GitHub says not to. GitHub publishes its hosted runners' address ranges in the actions list of its meta API, and the day we pulled it the list held 7,038 ranges, 5,510 of them IPv4 and 1,528 IPv6. The IPv4 ranges alone cover about 28 million addresses. They are Azure's, because the Ubuntu and Windows runners run in Azure, and GitHub updates the list once a week. GitHub's runner reference does not mince it: "we do not recommend that you use these as allowlists for your internal resources." A firewall rule that size opens your server to whatever else runs in those Azure ranges, not only to your pipeline.
Both zeros hold by construction: a self-hosted runner needs only outbound connections, on port 443, and a pulling agent fetches its release outward too.
So there are three honest ways to keep the deploy, and we take them in this order.
Pull instead of push. The hosted runner builds the image and pushes it to a registry, and something already inside your network pulls it. For Kubernetes that is a GitOps controller, and our Argo CD sync-policy tests show what that changes for a team. Nothing has to reach in, so the runner stays hosted and the question goes away instead of moving somewhere else.
A larger runner with a static IP. GitHub will give a pool of larger runners a fixed address range you can allowlist, at no extra charge for the addresses. The catch is the plan. Larger runners are available on Team or Enterprise Cloud, but the static addresses need Enterprise Cloud. A Linux 4-core one is USD 0.012 a minute, and GitHub's pricing reference says included minutes cannot be spent on them, so every minute is billed, though five minutes of deploys a day still comes to under two dollars a month.
A self-hosted runner inside, or beside, the network. The answer when the target cannot pull, you are not on Enterprise Cloud, or the job needs hardware GitHub does not rent. This is the one with a power problem.
What a blackout does to a self-hosted runner
On the evening of Wednesday 29 July 2026 a disturbance on the national grid blacked out Nairobi, the Coast, Mount Kenya and parts of the Central Rift. Kenya Power's statement afterwards: "power supply was fully restored to all affected customers by 2:00am today," as reported by Kenyans.co.ke. Put a runner on a shelf in an office that night and GitHub's own rules say what happens next.
The runner shows Offline, which GitHub defines as not connected, whatever the reason. A job GitHub hands to a runner that does not pick it up within 60 seconds goes back in the queue. Jobs with no online runner to go to wait, and a job still queued after 24 hours fails. A night like that one is well inside 24 hours, so everything pushed during the blackout runs once the machine is back, provided it comes back by itself. The box has to power on when the electricity returns, a firmware setting worth checking on any office machine, and the runner has to start with it, which is what installing it as a service does:
cd ~/actions-runner
sudo ./svc.sh install
sudo ./svc.sh start
The job that was running when the power went does not resume. It fails with this line in its log, the one people search for, with your runner's name in place of runner01:
The self-hosted runner: runner01 lost communication with the server. Verify the machine is running and has a healthy network connection. Anything in your workflow that terminates the runner process, starves it for CPU/Memory, or blocks its network access can cause this error.
GitHub does not document how long it waits before it marks that job failed, and we did not register a runner to time it, so we will not give you a number. What matters more is where the job was. A build that dies can simply be rerun. A deploy that dies stopped wherever the power caught it: half the servers on the new release, or the migration applied and the restart never sent.
- runner offline
- jobs run
The 24 hours run from when each job was queued, so if the dark lasts, the oldest job in the queue is the first to fail.
There is a slower clock too. A runner that does not connect for more than 14 days is removed from GitHub and has to be registered again, and an ephemeral one goes after a day. A machine left switched off for more than two weeks over a holiday comes back to a repository that has forgotten it.
If a runner in your office deploys to production today, send us that workflow file and we will tell you what a power cut halfway through it leaves behind.
The upkeep GitHub hands you with the machine
GitHub's description of self-hosted runners says they "don't need to have a clean instance for every job execution." Read that as: the last job's files, caches and any credential it wrote down may still be on disk when the next job starts. Register the runner with --ephemeral so it takes one job and leaves, and give each job a fresh VM or container. Never point one at a public repository. GitHub's hardening guidance says self-hosted runners "should almost never be used for public repositories," because any pull request is code running on your hardware.
Then there are updates. The runner updates itself by default, and a runner left that way meets GitHub's version rule without help, as long as it can reach the update service. For ephemeral runners in containers that means an update on every start after each release, so GitHub lets you turn it off and update the image on your own schedule. With it off, the rule is that you install each new version within 30 days or GitHub stops sending you jobs, and its changelog is blunt about it: "A runner pinned to 2.329.0 that never updates again will not pick up jobs."
We counted the runner releases from v2.330.0 to v2.338.0: eleven in 321 days, never more than 51 days apart, twice with a patch inside nine days. On that cadence, a runner image nobody rebuilds for three months stops receiving jobs. Somebody has to own that rebuild, which is a line in a job description rather than a feature.
Where the runner should live in Kenya
Not under a desk. An office runner shares the office's power, its internet link and its opening hours. Put it where its targets are: a VM inside the cloud estate it deploys to, or a dedicated server in a Nairobi datacenter on the same private network, where the facility's UPS and generators are built to carry it through a blackout like the one above. When the answer is a dedicated box for runners, we provision it and hand it over in about 24 hours.

And if your jobs only build, test and push, or your deploys can be pulled, you need no self-hosted runner at all, and no help from us for this. The 2,000 minutes GitHub Free includes on private repositories is a ten-minute Linux pipeline run 200 times a month.
If any job does have to reach in, send us the workflow files and, for each deploy job, what it touches: the host, the database, the cluster API, and whether a firewall or VPN sits in front of it. Use the DevOps scoping form or WhatsApp us on +254 713 403 044. We come back with every job marked hosted, pull, static IP or self-hosted, and where any self-hosted one should run. If every job comes back hosted, that note is the whole answer, and it costs you nothing.