GitHub Actions self-hosted runners allow you to execute CI/CD workflows on your own hardware, giving you full control over the operating system, installed tooling, network access, and resource allocation. On RHEL 8 this is especially valuable for pipelines that must access internal systems, use licensed software, or run workloads that exceed GitHub’s hosted-runner resource limits. This tutorial covers downloading and configuring the runner agent, running it as a systemd service, verifying it in the GitHub UI, writing a workflow that targets it, and the security considerations you must apply before exposing a self-hosted runner to untrusted code.

Prerequisites

  • RHEL 8 server with a non-root sudo user
  • A GitHub repository where you have Admin access
  • Outbound HTTPS access from the RHEL 8 host to github.com and *.actions.githubusercontent.com
  • curl, tar, and libicu installed on the host
  • A dedicated OS user for the runner (created in Step 1) — never run as root

Step 1 — Create a Dedicated Runner User

Running the GitHub Actions agent as a dedicated non-privileged user limits the blast radius if a malicious workflow is submitted to your repository.

# Install runtime dependency
sudo dnf install -y libicu curl tar

# Create a dedicated system user (no login shell, home directory created)
sudo useradd -m -s /bin/bash github-runner

# Optionally allow the runner user to run dnf without a password
# (only if your pipelines need to install packages)
echo "github-runner ALL=(ALL) NOPASSWD: /usr/bin/dnf" | 
  sudo tee /etc/sudoers.d/github-runner
sudo chmod 440 /etc/sudoers.d/github-runner

# Switch to the runner user for the next steps
sudo -u github-runner -i

Step 2 — Download the Runner Package

Navigate to your GitHub repository → SettingsActionsRunnersNew self-hosted runner. Select Linux and x64 to reveal the exact download URL and registration token for your repository.

# Run as github-runner user
cd ~

# Create a dedicated directory for the runner
mkdir actions-runner && cd actions-runner

# Download the latest runner package (replace the version as shown in GitHub UI)
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

# (Optional) Verify the SHA-256 checksum shown on the GitHub download page
echo "EXPECTED_SHA256  actions-runner-linux-x64-2.317.0.tar.gz" | sha256sum --check

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

Step 3 — Configure the Runner

The config.sh script registers the runner with GitHub using a short-lived registration token. The token is displayed on the New self-hosted runner page and expires after one hour.

# Still running as github-runner in ~/actions-runner/

# Configure the runner (replace URL and TOKEN with values from GitHub UI)
./config.sh 
  --url https://github.com/YOUR_ORG/YOUR_REPO 
  --token AXXXXXXXXXXXXXXXXXXXXXXXX 
  --name rhel8-runner-01 
  --labels rhel8,self-hosted,linux,x64 
  --work _work 
  --unattended

# Test the runner in the foreground before installing as a service
./run.sh

While run.sh is active, the runner will appear as Idle on the GitHub Runners page. Press Ctrl-C to stop it before installing as a service.

Step 4 — Install the Runner as a systemd Service

The bundled svc.sh script creates and enables a systemd unit so the runner starts automatically on boot and restarts if it crashes.

# Return to a sudo-capable user
exit

# Install the service (must be run as root or via sudo)
cd /home/github-runner/actions-runner
sudo ./svc.sh install github-runner

# Start the service
sudo ./svc.sh start

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

# Equivalent systemctl commands
sudo systemctl status actions.runner.YOUR_ORG-YOUR_REPO.rhel8-runner-01.service
sudo journalctl -u actions.runner.YOUR_ORG-YOUR_REPO.rhel8-runner-01.service -f

Step 5 — Use the Runner in a Workflow

Target your self-hosted runner by specifying its labels in the runs-on key of a workflow job. Using custom labels (set during config.sh) is safer than self-hosted alone because it prevents accidental routing to the wrong machine.

# .github/workflows/ci.yml

name: CI on RHEL 8

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

jobs:
  build-and-test:
    runs-on: [self-hosted, rhel8, linux]

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

      - name: Show runner environment
        run: |
          uname -a
          python3 --version
          echo "Running on: $HOSTNAME"

      - name: Install dependencies
        run: sudo dnf install -y make gcc

      - name: Run tests
        run: make test

Step 6 — Apply Security Hardening

Self-hosted runners execute arbitrary code from workflow files. The following measures reduce the risk of a compromised or malicious pull request gaining access to your host or credentials.

# 1. Restrict which repositories can use the runner
#    (In GitHub: Settings → Actions → Runner Groups → set access policy)

# 2. Disable automatic token inheritance — use OIDC or explicit secrets instead
#    Add to the job:
#    permissions:
#      contents: read

# 3. Run workflows inside an ephemeral container to isolate each job
#    Install Docker or Podman, then configure the runner with container hooks

# 4. Rotate the runner token — remove and re-register periodically
cd /home/github-runner/actions-runner
sudo ./svc.sh stop
sudo -u github-runner ./config.sh remove --token OLD_TOKEN
# Then re-register with a new token from the GitHub UI

# 5. Keep the runner binary up to date
sudo -u github-runner bash -c "cd ~/actions-runner && ./run.sh --once"
# GitHub auto-updates the runner binary on the next job run

# 6. Restrict outbound network access with firewalld
sudo firewall-cmd --permanent --direct --add-rule ipv4 filter OUTPUT 0 
  -p tcp --dport 443 -m comment --comment "allow GitHub Actions" -j ACCEPT
sudo firewall-cmd --reload

Conclusion

You have created a dedicated runner OS user, downloaded and registered the GitHub Actions runner agent on RHEL 8, installed it as a systemd service for automatic startup, targeted it in a workflow with specific labels, and applied key security hardening steps. Your RHEL 8 host is now a first-class CI/CD execution environment fully integrated into your GitHub Actions pipelines.

Next steps: Build a Full GitHub Actions CI/CD Pipeline for a RHEL 8 Application, Harden GitHub Actions Workflows with OIDC and Least-Privilege Permissions, and Use Terraform to Provision Infrastructure on RHEL 8.