Runner Registration Token
A runner registration token is the short lived credential a self-hosted runner presents to GitHub to join a repository, organization, or enterprise pool.
A runner registration token is a short lived credential that a self-hosted runner presents to GitHub so that it can join a repository, organization, or enterprise runner pool. GitHub mints the token, the runner uses it once during configuration, and it expires one hour after it was created (GitHub REST reference for self-hosted runners, checked on 2026-08-13).
Most confusion about registration tokens traces back to that one hour window. A token pasted into a machine image, a Terraform variable, or a runbook stops working before anyone gets around to using it, which is why registration is normally scripted to fetch a new token at the moment a machine boots.
Definition
A registration token authorizes exactly one operation: adding a runner to a pool. It grants no ability to read source code, push commits, or approve a deployment. What it grants is membership, and membership is what makes a machine eligible to receive queued jobs.
Three properties describe the credential:
- Scoped. A token is minted for one registration scope. That scope decides which workflows are allowed to queue jobs onto the runner once it has joined.
- Short lived. The token expires one hour after creation. Expiry is absolute rather than idle based, so a token that has been sitting unused for 61 minutes is already dead.
- Single use in practice. The config script trades the token for credentials the agent stores in its own directory, and every later connection uses those stored credentials. Once a runner is online, the registration token has no further role.
Where the token comes from
GitHub prints a fresh token into the setup commands shown under Settings, Actions, Runners in the web UI. The same token is available from the REST API, which is the path automation uses. Endpoints and permissions below are from the GitHub REST reference for self-hosted runners, checked on 2026-08-13.
| Registration scope | REST endpoint (POST) | Permission required | Jobs the runner becomes eligible for |
|---|---|---|---|
| Repository | /repos/{owner}/{repo}/actions/runners/registration-token | Admin on the repository | Workflows in that one repository |
| Organization | /orgs/{org}/actions/runners/registration-token | Organization admin, admin:org scope on a token | Workflows in the repositories the runner group allows |
| Enterprise | /enterprises/{enterprise}/actions/runners/registration-token | Enterprise admin | Workflows in the organizations the runner group allows |
Each scope has a matching remove token endpoint at the same path with remove-token in place of registration-token. That second credential is what a deregistration step presents when a machine leaves the pool, and it follows the same one hour expiry.
Credentials that get confused with it
Three other credentials show up in the same workflows and do different jobs.
GITHUB_TOKEN is the installation token GitHub injects into a running job. Its lifetime is the job, its permissions are set by the workflow, and it plays no part in registering a machine.
A personal access token or a GitHub App installation token is the long lived operator credential that authenticates the API call which mints a registration token. It sits on the side that creates runners rather than on the runner itself, which is why autoscaling controllers keep it in a secrets manager and never copy it onto a runner host.
The agent's stored credentials are written during configuration and live in the runner's own working directory. They are the reason a runner survives a reboot without anyone reissuing a registration token.
Why the expiry is so short
Whoever holds a valid registration token can attach a machine to the pool, and a machine in the pool can be handed jobs that carry repository secrets and deployment credentials. A one hour window caps how long a leaked token stays useful and rules out the pattern of baking a token into a golden image or a configuration management repository.
The same reasoning produced a second path. The just-in-time endpoints at /actions/runners/generate-jitconfig return an encoded configuration for a single ephemeral runner, which the agent consumes at startup, so no registration token is ever written to the machine. Autoscalers pick one of these two paths: mint a registration token per boot, or mint a just-in-time config per queued job.
Example
Registering a self-hosted runner is two steps. First, mint a token with an operator credential from somewhere other than the runner:
RUNNER_TOKEN=$(curl -sS -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_ADMIN_TOKEN" \
https://api.github.com/orgs/$GITHUB_ORG/actions/runners/registration-token \
| jq -r .token)Second, hand that token to the runner agent's config script on the machine itself:
./config.sh \
--url https://github.com/$GITHUB_ORG \
--token "$RUNNER_TOKEN" \
--labels self-hosted,linux,x64,build \
--unattended \
--ephemeral
./run.shThe two steps have to happen within the hour. In an autoscaling group the gap is seconds, because the boot script calls the API and then the config script back to back. In a manual setup the gap is however long it takes to copy a token into a terminal, which is where the expiry usually bites.
After config.sh succeeds, the token has done its work. The runner appears in the pool advertising the four labels it registered with, and a workflow reaches it by naming those labels:
name: build
on:
push:
branches: [main]
jobs:
package:
runs-on: [self-hosted, linux, x64, build]
steps:
- uses: actions/checkout@v4
- run: make packageNo token appears anywhere in that workflow. Routing after registration is a label match, and the registration token is invisible to the jobs it made possible.
Because --ephemeral was passed, the agent accepts one job, deregisters itself, and exits. The next machine in the autoscaling group repeats the whole sequence with a token of its own, which is the reason a fleet fetches thousands of registration tokens a month and stores none of them.
Related Terms
- Self-hosted runners and what registering one commits you to: the machine on the other end of the registration call, and the provisioning, patching, and scaling work that comes with it.
- Runner pools and how a registered machine joins one: what the token buys membership in, and how pool membership decides which jobs reach a machine.
- What a managed runner provider creates inside your cloud account: the resources involved when registration is automated on your behalf.
- WarpBuild runner security documentation: how runner isolation, storage, and secrets handling are documented.
- WarpBuild cloud runners documentation: the runner labels available and how a workflow selects them.
- WarpBuild pricing: per minute rates by runner type.
FAQ
How long does a runner registration token last?
One hour. GitHub's REST reference for self-hosted runners states that the token expires after one hour, which applies to the repository, organization, and enterprise endpoints alike. A token minted at 09:00 is useless at 10:01, so registration scripts fetch a fresh token at the moment a machine boots.
Where does a runner registration token come from?
GitHub mints it. The web UI prints a fresh token into the setup commands under Settings, Actions, Runners, and the same token is available from the REST API at POST /repos/{owner}/{repo}/actions/runners/registration-token, POST /orgs/{org}/actions/runners/registration-token, or the enterprise equivalent. Minting one requires admin permission at that scope.
Does the runner keep using the registration token after setup?
No. The config script exchanges the registration token for credentials that the agent stores in its own directory, and every later connection to GitHub uses those stored credentials. The registration token is presented once and then expires on its own schedule.
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.