7.2 Self-Hosted Runner Setup, Architecture & Communication

Key Takeaways

  • Self-hosted runners communicate with GitHub exclusively via outbound HTTPS connections over port 443 using long polling and WebSockets, requiring zero inbound ports or public IP addresses.
  • Registration tokens are short-lived credentials valid for exactly 1 hour, used exclusively during `./config.sh` initialization to exchange the token for long-lived cryptographic credentials stored in `.runner` and `.credentials`.
  • The `actions-runner` package is a cross-platform .NET Core application supporting Linux (x64, ARM64, ARM32), Windows (x64, ARM64), and macOS (x64, Apple Silicon).
  • Self-hosted runners can be registered at three hierarchical scopes: Repository level (isolated to one repo), Organization level (shared across org repos), and Enterprise level (shared across multiple orgs).
  • Production self-hosted runners should be installed and managed as continuous OS background daemons using the `./svc.sh` wrapper script to integrate with systemd on Linux, launchd on macOS, or Windows Services.
Last updated: August 2026

Self-Hosted Runner Setup, Architecture & Communication

While GitHub-hosted runners offer maintenance-free execution, organizations often require dedicated compute environments to access on-premises databases, leverage custom hardware architectures (such as ARM64, custom GPUs, or Apple Silicon build farms), utilize persistent local caches, or avoid per-minute hosted billing. In these scenarios, organizations deploy self-hosted runners.

A self-hosted runner is a physical server, virtual machine, or containerized environment that you provision, secure, and maintain, on which you install the official GitHub Actions runner application. Mastering the communication architecture, token authentication lifecycle, and daemon management of self-hosted runners is critical for enterprise platform administration and the GH-200 examination.


1. Outbound HTTPS Communication Model

A common security misconception is that GitHub Actions requires an open inbound firewall port to dispatch jobs to on-premises self-hosted runners. In reality, the GitHub Actions runner architecture operates on an outbound-only communication model.

+-----------------------------------------------------------------------------+
|                  RUNNER POLLING & COMMUNICATION ARCHITECTURE                |
|                                                                             |
|   +--------------------------+             +----------------------------+   |
|   |    GitHub Control Plane  |             |     Corporate Network      |   |
|   |    (api.github.com &     |             |   (On-Premises / VPC)      |   |
|   |    Actions Message Bus)  |             |                            |   |
|   +--------------------------+             +----------------------------+   |
|                ▲                                         │                  |
|                │                                         │                  |
|                │   1. Outbound HTTPS (Port 443) Long Poll│                  |
|                │      "Any jobs matching my labels?"     │                  |
|                ├─────────────────────────────────────────┤                  |
|                │                                         ▼                  |
|                │   2. Job Dispatch Payload (HTTPS 443)  +----------------+  |
|                │◄───────────────────────────────────────┤ Self-Hosted    |  |
|                │                                        | Runner Daemon  |  |
|                │   3. Stream Logs & Status (HTTPS 443)  | (Runner.       |  |
|                │◄───────────────────────────────────────┤  Listener)     |  |
|                                                         +----------------+  |
|   [NO INBOUND PORTS OPEN]                  [OUTBOUND ONLY THROUGH NAT/PROXY]|
+-----------------------------------------------------------------------------+

Network Requirements

  • No Inbound Open Ports: The host machine running the runner daemon does not require any open inbound ports (port 80, 443, and 22 can remain completely closed to ingress traffic).
  • Outbound Protocol: All communication occurs over HTTPS (TCP port 443) or Secure WebSockets (wss://).
  • Long Polling & Message Queue: The Runner.Listener process initiates an outbound HTTPS connection to GitHub's Actions Message Bus, maintaining an active long-poll session. When a workflow job is queued that matches the runner's labels, GitHub delivers the job assignment over the existing established outbound connection.
  • Proxy Support: In corporate environments where direct outbound internet access is restricted, the runner can be routed through an authenticating forward HTTP/HTTPS proxy by configuring .proxy or environment variables (HTTP_PROXY, HTTPS_PROXY, and NO_PROXY).

2. Registration Token Lifecycle & Authentication

To bind a self-hosted runner to a GitHub repository, organization, or enterprise, an administrator must generate a registration token.

   1. Admin requests registration token via UI or API (Valid for 60 minutes)
                               │
                               ▼
   2. Admin executes: ./config.sh --url <url> --token <registration-token>
                               │
                               ▼
   3. Runner daemon exchanges temporary token with GitHub API
      • Receives unique Runner ID
      • Generates local RSA private/public keypair
      • Saves long-lived session credentials into .runner and .credentials
                               │
                               ▼
   4. Registration token expires; runner authenticates via saved RSA credentials

Critical Token Rules

  1. Short-Lived (1-Hour Expiration): Registration tokens generated via the GitHub Web UI or the REST API (POST /repos/{owner}/{repo}/actions/runners/registration-token or /orgs/{org}/...) are valid for exactly 60 minutes.
  2. Exchange Mechanism: The registration token is used only once during the initial execution of ./config.sh. The runner daemon presents the token to GitHub, authenticates the registration, and receives a permanent cryptographic identity.
  3. Local Credential Storage: The permanent credentials and configuration are written to hidden files in the runner directory (.runner containing runner metadata and .credentials containing the cryptographic OAuth tokens/keys). Future daemon restarts use these local credentials directly without needing a new registration token.
  4. Removal Tokens: To decommission a runner gracefully, an administrator generates a short-lived removal token via API/UI and executes ./config.sh remove --token <removal-token>.

3. Supported Operating Systems & Package Structure

The GitHub Actions self-hosted runner application (actions-runner) is an open-source .NET Core application supported across major operating systems and CPU architectures:

  • Linux: x64, ARM64 (e.g., AWS Graviton, Ampere), ARM32 (Raspberry Pi / embedded).
  • Windows: x64, ARM64 (Windows Server 2019/2022, Windows 10/11).
  • macOS: x64 (Intel), ARM64 (Apple Silicon M1/M2/M3/M4).

Directory Layout of actions-runner/

actions-runner/
├── bin/                   # Core .NET runner binaries (Runner.Listener, Runner.Worker)
├── externals/             # Embedded runtimes (Node.js versions used to run JavaScript actions)
├── _work/                 # Workspace directory where repositories are cloned and jobs execute
├── .runner                # Local runner configuration, agent ID, pool ID, and server URL
├── .credentials           # Encrypted RSA keys and credentials for GitHub Actions control plane
├── .env                   # Custom environment variables injected into all workflow jobs
├── .path                  # Custom PATH entries prepended to $PATH for all jobs
├── config.sh / config.cmd # Configuration script for initial registration
├── run.sh / run.cmd       # Interactive foreground execution script
└── svc.sh / svc.cmd       # System service management wrapper (systemd/launchd/WinService)

4. Runner Scopes: Repository, Organization & Enterprise

Self-Hosted runners can be registered at three distinct hierarchical levels, determining their visibility and job-sharing boundaries:

+-----------------------------------------------------------------------------+
|                        RUNNER SCOPE HIERARCHY                               |
|                                                                             |
|   [ENTERPRISE LEVEL]  ─── Registered at Enterprise Account                  |
|                           Shared across multiple Organizations              |
|                                                                             |
|   [ORGANIZATION LEVEL]─── Registered at Organization Account                |
|                           Shared across multiple Repositories (via Groups)  |
|                                                                             |
|   [REPOSITORY LEVEL]  ─── Registered at Single Repository                   |
|                           Strictly isolated; cannot be shared               |
+-----------------------------------------------------------------------------+
Scope LevelRegistration TargetBest Use CaseGovernance & Access
Enterprisehttps://github.com/enterprises/my-corpShared centralized compute infrastructure across business units.Managed by enterprise administrators; assigned to organizations via Enterprise Runner Groups.
Organizationhttps://github.com/my-orgStandard enterprise microservices and shared team CI pools.Managed by org owners; assigned to repositories via Organization Runner Groups.
Repositoryhttps://github.com/my-org/my-repoSpecialized legacy build machines, dedicated hardware testbeds.Bound exclusively to the single repository; cannot be accessed by other repos.
Loading diagram...
Self-Hosted Runner Registration, Service Daemonization, and Polling Architecture

5. Automated Installation & Service Daemonization (svc.sh)

Running a self-hosted runner interactively in a terminal via ./run.sh is acceptable for initial testing, but production environments require the runner to operate as a continuous background daemon that survives host reboots and restarts automatically upon failure.

The GitHub Actions runner package includes the svc.sh script (on Linux/macOS) and svc.cmd (on Windows) to manage native operating system services.

Automated Production Setup Script (Ubuntu Linux / systemd)

#!/usr/bin/env bash
set -euo pipefail

# 1. Create dedicated non-root runner user
sudo useradd -m -s /bin/bash actions-runner
sudo usermod -aG docker actions-runner || true

# 2. Prepare installation directory
sudo mkdir -p /opt/actions-runner && cd /opt/actions-runner
sudo chown -R actions-runner:actions-runner /opt/actions-runner

# 3. Download official runner package and verify SHA-256 checksum
RUNNER_VERSION="2.317.0"
RUNNER_TAR="actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"
EXPECTED_HASH="9e883d3f0ee374fbd34e02f82c8e496dc53f5d752b81763761ecda10d62df13e"

sudo -u actions-runner curl -o "${RUNNER_TAR}" -L \
  "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/${RUNNER_TAR}"

echo "${EXPECTED_HASH}  ${RUNNER_TAR}" | shasum -a 256 -c
sudo -u actions-runner tar xzf "${RUNNER_TAR}"

# 4. Install OS-level dependencies
sudo ./bin/installdependencies.sh

# 5. Execute unattended registration (Using 1-hour registration token)
REG_TOKEN="AB12CD34EF56GH78IJ90KLMN"
ORG_URL="https://github.com/my-enterprise-org"

sudo -u actions-runner ./config.sh \
  --url "${ORG_URL}" \
  --token "${REG_TOKEN}" \
  --name "linux-prod-runner-01" \
  --runnergroup "production-pool" \
  --labels "self-hosted,linux,x64,gpu,nvme-storage" \
  --work "_work" \
  --unattended \
  --replace

# 6. Install and start native systemd background service
sudo ./svc.sh install actions-runner
sudo ./svc.sh start

# 7. Check service status
sudo ./svc.sh status

svc.sh Service Command Reference

  • ./svc.sh install [user]: Generates a systemd service file (e.g., /etc/systemd/system/actions.runner.<owner>.<name>.service) configured to run under the specified user account.
  • ./svc.sh start: Starts the registered systemd service immediately.
  • ./svc.sh status: Checks current execution state and PID of the running service daemon.
  • ./svc.sh stop: Gracefully stops the runner service (finishes current job before terminating).
  • ./svc.sh uninstall: Removes the service configuration from systemd.
Test Your Knowledge

A network security architect is reviewing the firewall configuration for a cluster of self-hosted runners hosted in an on-premises datacenter. What inbound and outbound firewall port rules are required for the self-hosted runners to receive jobs and upload execution logs to GitHub?

A
B
C
D
Test Your Knowledge

An administrator generates a self-hosted runner registration token from the GitHub Organization settings page at 08:00 AM. What is the validity period of this token, and how does the runner authenticate with GitHub on subsequent days after registration?

A
B
C
D
Test Your Knowledge

A platform engineer is configuring a persistent Linux virtual machine to host a self-hosted runner. The engineer needs the runner daemon to start automatically when the server boots, run under a dedicated service user, and integrate with system logging. Which command sequence should the engineer run after ./config.sh?

A
B
C
D