How to Secure Self-Hosted Runners at Scale
The two non-negotiable rules: never run self-hosted runners on public repos, and make every runner ephemeral.
GitHub itself warns against self-hosted runners on public repositories, because a fork PR can run arbitrary code on your infrastructure. As you scale, ephemeral runners are the key defense: a runner that is destroyed after one job cannot be poisoned to attack the next job. Combine with runner groups and least-privilege credentials.
Steps
- Never attach self-hosted runners to public repos; keep them on private repos and trusted forks only.
- Make every runner ephemeral so a compromised job cannot persist onto later jobs.
- Scope credentials tightly (short-lived tokens, per-fleet runner groups, no host socket).
Ephemeral by default
Terminal
# Ephemeral registration is the core hardening step at scale
./config.sh --url https://github.com/my-org \
--token "$RUNNER_TOKEN" --ephemeral --labels self-hosted,linux
# For fork PRs, require approval before workflows run (repo Actions settings).Gotchas
- A non-ephemeral runner that runs an untrusted job can be left with a backdoor for the next job (poisoning).
- Require approval for fork PR workflows so untrusted code cannot start on your runners automatically.
Related guides
How to Run Ephemeral Self-Hosted Runners (One Job Per Runner)Configure a self-hosted GitHub Actions runner as ephemeral with the --ephemeral flag so it runs exactly one j…
How to Use Runner Groups to Control Access at ScaleOrganize self-hosted runners into runner groups to control which repositories and workflows can use which fle…