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.shinitialization to exchange the token for long-lived cryptographic credentials stored in.runnerand.credentials.The
actions-runnerpackage 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.shwrapper script to integrate with systemd on Linux, launchd on macOS, or Windows Services.
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.Listenerprocess 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
.proxyor environment variables (HTTP_PROXY,HTTPS_PROXY, andNO_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
- Short-Lived (1-Hour Expiration): Registration tokens generated via the GitHub Web UI or the REST API (
POST /repos/{owner}/{repo}/actions/runners/registration-tokenor/orgs/{org}/...) are valid for exactly 60 minutes. - 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. - Local Credential Storage: The permanent credentials and configuration are written to hidden files in the runner directory (
.runnercontaining runner metadata and.credentialscontaining the cryptographic OAuth tokens/keys). Future daemon restarts use these local credentials directly without needing a new registration token. - 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 Level | Registration Target | Best Use Case | Governance & Access |
|---|---|---|---|
| Enterprise | https://github.com/enterprises/my-corp | Shared centralized compute infrastructure across business units. | Managed by enterprise administrators; assigned to organizations via Enterprise Runner Groups. |
| Organization | https://github.com/my-org | Standard enterprise microservices and shared team CI pools. | Managed by org owners; assigned to repositories via Organization Runner Groups. |
| Repository | https://github.com/my-org/my-repo | Specialized legacy build machines, dedicated hardware testbeds. | Bound exclusively to the single repository; cannot be accessed by other repos. |
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.
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?
Allow inbound TCP traffic on port 443 from GitHub IP addresses, and allow outbound TCP traffic on port 22.
Allow bidirectional TCP traffic on ports 80, 443, and 8080 between the runner host and GitHub.
Allow inbound WebSocket connections on port 8443, with no outbound rules required.
Allow outbound HTTPS traffic on port 443 to GitHub endpoints; zero inbound ports or listening services need to be opened on the corporate firewall.
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?
The token expires in 1 hour; during ./config.sh execution, the runner exchanges the token with GitHub for permanent cryptographic RSA credentials saved in .runner and .credentials.
The token is valid permanently for the lifetime of the runner host and must be passed as an environment variable in systemd on every daemon reboot.
The token expires in 24 hours; the runner daemon automatically requests a new registration token daily using OAuth refresh tokens.
The token expires in 30 days; an administrator must re-run ./config.sh every month to refresh the runner's connection.
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?
Run nohup ./run.sh & and add the command to the user's ~/.bashrc file.
Execute sudo systemctl create actions-runner && sudo systemctl enable --now actions-runner.
Execute sudo ./svc.sh install actions-runner followed by sudo ./svc.sh start in the runner directory.
Configure a cron job using @reboot /opt/actions-runner/run.sh under the root user crontab.
Sections you finish are checked off in the contents.