How to Register a GitHub Actions Self-Hosted Runner on RHEL 7

GitHub Actions is a CI/CD platform built directly into GitHub, and while GitHub provides hosted runners for common workflows, there are many scenarios where you need to run workflows on your own infrastructure: accessing internal services not exposed to the internet, using specific hardware like GPU nodes or high-memory machines, running on RHEL 7 where enterprise software is already licensed and configured, or simply reducing GitHub Actions billing costs at scale. A self-hosted runner is a lightweight agent process that polls GitHub for queued workflow jobs and executes them locally on your RHEL 7 system. This guide covers the complete setup: downloading the runner, configuring it via the GitHub UI, installing it as a systemd service so it starts automatically, configuring runner labels, using it in workflow YAML files, working with runner groups, and understanding the security implications.

Prerequisites

  • A RHEL 7 system with outbound HTTPS access to github.com and *.actions.githubusercontent.com on port 443
  • Root or sudo access
  • A GitHub account with admin access to the repository or organization where you want to register the runner
  • A dedicated non-root user account to run the runner process (recommended for security)
  • curl and tar installed on the system
  • Sufficient disk space for the runner software and job workspace (recommend at least 20 GB free)

Step 1: Create a Dedicated Runner User

Running the GitHub Actions runner as root is a significant security risk — any workflow job would have unrestricted access to the entire system. Create a dedicated user account:

sudo useradd -m -s /bin/bash github-runner
sudo passwd -l github-runner  # Lock password login, SSH key only

# If the runner needs to run Docker or other tools, add to relevant groups
sudo usermod -aG docker github-runner

# Create the runner working directory
sudo mkdir -p /home/github-runner/actions-runner
sudo chown github-runner:github-runner /home/github-runner/actions-runner

Step 2: Get the Registration Token from GitHub

You need an ephemeral registration token from GitHub before you can configure the runner. Tokens expire after one hour, so generate one immediately before running the configuration script.

For a repository-level runner:

  1. Open your GitHub repository in a browser
  2. Go to SettingsActionsRunners
  3. Click New self-hosted runner
  4. Select Linux as the operating system and x64 as the architecture
  5. GitHub will display the download URL and a registration token — copy both values

For an organization-level runner (shared across all repos in the org):

  1. Go to your GitHub organization page
  2. Click SettingsActionsRunners
  3. Click New self-hosted runner
  4. Copy the download URL and token shown

You can also generate a token via the GitHub API if you prefer scripted setup:

# Generate a registration token via API (requires a Personal Access Token with repo scope)
curl -s -X POST 
  -H "Authorization: token YOUR_PAT_HERE" 
  -H "Accept: application/vnd.github+json" 
  https://api.github.com/repos/OWNER/REPO/actions/runners/registration-token 
  | python -c "import sys,json; print(json.load(sys.stdin)['token'])"

Step 3: Download and Extract the Runner Package

Switch to the runner user and download the runner agent. Use the exact version URL shown in the GitHub UI, or check https://github.com/actions/runner/releases for the latest release:

sudo -u github-runner -i
cd /home/github-runner/actions-runner

# Download the runner tarball (adjust version number as needed)
curl -o actions-runner-linux-x64-2.317.0.tar.gz -L 
  https://github.com/actions/runner/releases/download/v2.317.0/actions-runner-linux-x64-2.317.0.tar.gz

# Verify the download integrity using the SHA-256 hash published on the GitHub releases page
echo "EXPECTED_SHA256_HASH  actions-runner-linux-x64-2.317.0.tar.gz" | sha256sum -c

# Extract the package
tar xzf ./actions-runner-linux-x64-2.317.0.tar.gz

# List the extracted contents
ls -la

The runner package includes config.sh (registration), run.sh (foreground execution), and svc.sh (systemd service management). It ships with its own bundled .NET runtime so no separate .NET installation is needed.

Step 4: Configure the Runner with config.sh

Run the configuration script with your repository URL and the registration token obtained in Step 2:

# Still running as github-runner user
./config.sh 
  --url https://github.com/OWNER/REPO 
  --token AXXXXXXXXXXXXXXXXXXXXXXXXXX 
  --name "rhel7-prod-01" 
  --labels "self-hosted,linux,x64,rhel7,production" 
  --work "_work" 
  --unattended

Key flags explained:

  • --url: Your repository or organization URL
  • --token: The ephemeral registration token from GitHub
  • --name: A friendly name for the runner as it appears in the GitHub UI
  • --labels: Comma-separated custom labels used to target this runner from workflow YAML
  • --work: Directory where job workspaces are created (relative to runner directory)
  • --unattended: Skip interactive prompts — required for scripted setup

For an interactive setup (without --unattended), the script will prompt you to confirm the runner group, name, and labels. The runner is registered but not yet running after this step.

Step 5: Install and Start the Runner as a systemd Service

Running ./run.sh directly starts the runner in the foreground and it stops when the terminal closes. For production use, install it as a systemd service that starts automatically on boot:

# Exit back to root (the svc.sh install command must run as root)
exit

# Install the systemd service
sudo /home/github-runner/actions-runner/svc.sh install github-runner

# Start the service
sudo systemctl start actions.runner.OWNER-REPO.rhel7-prod-01

# Enable it to start on boot
sudo systemctl enable actions.runner.OWNER-REPO.rhel7-prod-01

# Check the service status
sudo systemctl status actions.runner.OWNER-REPO.rhel7-prod-01

The service name format is actions.runner.{OWNER}-{REPO}.{RUNNER_NAME}. Check the exact service name that svc.sh install creates:

sudo systemctl list-units | grep actions.runner

View runner logs with journald:

sudo journalctl -u actions.runner.OWNER-REPO.rhel7-prod-01 -f

The runner will appear as Idle in your GitHub repository’s Settings → Actions → Runners page once the service is running and connected successfully.

Step 6: Using the Runner in Workflow YAML

Target your self-hosted runner in a GitHub Actions workflow by setting runs-on to match your runner’s labels:

# .github/workflows/build.yml
name: Build and Deploy

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build:
    # Use the self-hosted runner with all matching labels
    runs-on: [self-hosted, linux, rhel7, production]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install dependencies
        run: |
          sudo yum install -y make gcc

      - name: Build application
        run: make build

      - name: Run tests
        run: make test

  deploy:
    runs-on: [self-hosted, rhel7]
    needs: build
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to production
        run: |
          sudo systemctl restart myapp
          sudo systemctl status myapp

When multiple labels are specified in the runs-on list, GitHub selects a runner that has all the specified labels. This lets you precisely target specific machines in large runner pools.

Step 7: Runner Groups

Runner groups allow organizations to control which repositories can use which runners. This is available on GitHub Enterprise or with GitHub Teams/Enterprise Cloud plans.

To create and manage runner groups:

  1. In your organization, go to SettingsActionsRunner groups
  2. Click New runner group and give it a name (e.g., production-runners)
  3. Set repository access: allow all repositories or select specific ones
  4. Move runners into the group from the Runners page

Register a runner directly into a named group using the --runnergroup flag in config.sh:

./config.sh 
  --url https://github.com/OWNER 
  --token AXXXXXXXXXXXXXXXXXXXXXXXXXX 
  --name "rhel7-prod-02" 
  --labels "self-hosted,linux,x64,rhel7" 
  --runnergroup "production-runners" 
  --unattended

Step 8: Security Considerations

Self-hosted runners require careful security planning, especially when accepting pull requests from forked repositories:

  • Never run self-hosted runners on public repositories without strict controls — any fork can submit a pull request that executes arbitrary code on your runner host
  • Use pull_request_target trigger cautiously; it runs in the context of the base branch but can still access secrets if misconfigured
  • Run the runner as a low-privilege user (the github-runner user created in Step 1) and use sudo rules to grant only the specific permissions needed
  • Configure SELinux on RHEL 7 in enforcing mode and write appropriate SELinux policies for runner operations
  • Isolate runners in their own network segment or VLAN, allowing only outbound HTTPS to GitHub and access to internal resources the CI jobs actually need
  • Regularly update the runner software — check for new releases and update with:
# Stop the service
sudo systemctl stop actions.runner.OWNER-REPO.rhel7-prod-01

# Download new version, extract, run config.sh again (token is not needed for updates)
# Or use the built-in update — the runner updates itself automatically when a job starts

# Restart the service
sudo systemctl start actions.runner.OWNER-REPO.rhel7-prod-01
  • Use ephemeral runners (configured with --ephemeral) for sensitive workloads — the runner registers, executes one job, then deregisters, ensuring no state persists between jobs
  • Store secrets in GitHub Secrets, not in files on the runner host — secrets are masked in logs and injected as environment variables
  • Rotate registration tokens and PATs regularly; a compromised token allows a malicious actor to register rogue runners in your organization
# Unregister a runner cleanly before decommissioning the host
sudo systemctl stop actions.runner.OWNER-REPO.rhel7-prod-01
sudo /home/github-runner/actions-runner/svc.sh uninstall github-runner
sudo -u github-runner /home/github-runner/actions-runner/config.sh remove 
  --token DEREGISTRATION_TOKEN_HERE

You now have a fully operational GitHub Actions self-hosted runner on RHEL 7, running as a systemd service with appropriate security configuration. The runner connects to GitHub, accepts workflow jobs matching its labels, and executes them on your infrastructure with access to your internal network and pre-installed tooling. This unlocks the full power of GitHub Actions for enterprise RHEL 7 environments — giving you the workflow automation of a modern CI/CD platform while retaining complete control over the execution environment, network access policies, and the software stack your pipelines depend on.