How to Configure a GitHub Actions Self-Hosted Runner on Windows Server 2012 R2

GitHub Actions self-hosted runners allow you to execute CI/CD workflows on your own infrastructure rather than GitHub-hosted virtual machines. This is essential when your build process requires access to internal network resources, licensed software, hardware dongles, or a specific Windows Server configuration that cannot be replicated on cloud runners. Windows Server 2012 R2 can function as a self-hosted runner with some preparation, since GitHub’s runner agent requires .NET and PowerShell capabilities that WS2012 R2 provides natively. This guide covers the complete registration and operational configuration of a GitHub Actions runner on WS2012 R2.

Prerequisites

  • A GitHub account with access to a repository or organization where the runner will be registered
  • Windows Server 2012 R2 with PowerShell 4.0 (standard)
  • .NET Framework 4.7.2 or later installed (upgrade from 4.5.2 default if needed)
  • Git for Windows installed on the runner machine
  • A local administrator or domain account for running the runner service
  • Outbound HTTPS access to github.com, api.github.com, and *.actions.githubusercontent.com

Step 1: Upgrade .NET Framework

The GitHub Actions runner agent requires .NET Framework 4.7.2 as a minimum on older operating systems. WS2012 R2 ships with .NET 4.5.2. Download and install the upgrade:

Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/?linkid=863265" -OutFile "C:TempNDP472-KB4054530-x86-x64-AllOS-ENU.exe"
Start-Process -FilePath "C:TempNDP472-KB4054530-x86-x64-AllOS-ENU.exe" -ArgumentList "/q /norestart" -Wait

Verify the installed version after installation:

(Get-ItemProperty "HKLM:SOFTWAREMicrosoftNET Framework SetupNDPv4Full").Release
# 461808 or higher = .NET 4.7.2

Step 2: Obtain the Runner Registration Token

Navigate to your GitHub repository, click Settings, then Actions, then Runners, then New self-hosted runner. Select Windows as the operating system. GitHub will display a registration token that is valid for one hour. Copy the token — it looks like AABBC123... and will be used in the configuration step.

For organization-level runners (shared across multiple repositories), navigate to your organization’s Settings page, then Actions, then Runner groups and runners.

Step 3: Create a Dedicated Runner Directory

Create a directory for the runner agent. A dedicated directory on a non-system drive is preferred:

New-Item -ItemType Directory -Path "C:actions-runner" -Force
Set-Location C:actions-runner

Step 4: Download and Extract the Runner Agent

Download the latest GitHub Actions runner package for Windows x64. Check the GitHub Actions runner releases page for the current version number and update the URL accordingly:

Invoke-WebRequest -Uri "https://github.com/actions/runner/releases/download/v2.317.0/actions-runner-win-x64-2.317.0.zip" -OutFile "C:actions-runneractions-runner-win-x64.zip"

Extract the archive:

Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:actions-runneractions-runner-win-x64.zip","C:actions-runner")

On WS2012 R2, the Expand-Archive cmdlet is not available in PowerShell 4.0 natively, so using the .NET ZipFile class directly avoids dependency on PowerShell 5+ features.

Step 5: Configure the Runner

Run the configuration script with your repository URL and registration token. Replace the URL and token with your actual values:

.config.cmd --url https://github.com/YOUR_ORG/YOUR_REPO --token YOUR_REGISTRATION_TOKEN --name "ws2012r2-runner-01" --labels "windows,ws2012r2,self-hosted" --work "_work" --unattended

The --labels flag is important — it allows workflows to specifically target this runner using the runs-on directive in YAML. Setting ws2012r2 as a label lets you direct specific jobs (such as those requiring legacy .NET Framework builds) to this machine while routing other jobs to modern runners.

The --unattended flag suppresses interactive prompts, which is required for automated provisioning scripts.

Step 6: Install and Start the Runner as a Windows Service

The runner can be run interactively for testing, but for production use it must run as a Windows service so that it starts automatically after reboots. Install the service using the included script:

.svc.cmd install
.svc.cmd start

Verify the service is running:

Get-Service "actions.runner.*" | Select-Object Name, Status, StartType

The service name will be prefixed with actions.runner. followed by your organization/repository and runner name. Confirm Status is Running and StartType is Automatic.

Step 7: Configure Service Account Permissions

By default the runner service runs as NETWORK SERVICE. For builds that require domain access, network share access, or local administrator rights, configure the service to run under a specific account:

$runnerSvcName = (Get-Service "actions.runner.*").Name
$svc = Get-WmiObject Win32_Service -Filter "Name='$runnerSvcName'"
$svc.Change($null,$null,$null,$null,$null,$null,"DOMAINbuild_svc","ServiceAccountPassword",$null,$null,$null)
Restart-Service $runnerSvcName

Step 8: Configure a GitHub Actions Workflow to Use This Runner

In your repository, create or modify a workflow YAML file at .github/workflows/build.yml. Target the self-hosted runner using its labels:

name: Build on WS2012R2

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

jobs:
  build:
    runs-on: [self-hosted, windows, ws2012r2]
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Run PowerShell verification
        shell: powershell
        run: |
          Write-Host "Runner: $env:COMPUTERNAME"
          Write-Host "OS: $([System.Environment]::OSVersion.VersionString)"
          Write-Host "PS Version: $($PSVersionTable.PSVersion)"
      
      - name: Build with MSBuild
        shell: powershell
        run: |
          $msbuild = "C:WindowsMicrosoft.NETFramework64v4.0.30319MSBuild.exe"
          & $msbuild .MySolution.sln /p:Configuration=Release /p:Platform="Any CPU"

Step 9: Configure Runner Cleanup and Maintenance

Self-hosted runners accumulate workspace data over time. Configure automated cleanup of stale workspace directories with a scheduled task:

$cleanupScript = @'
$workDir = "C:actions-runner_work"
Get-ChildItem $workDir -Directory | Where-Object {
    $_.LastWriteTime -lt (Get-Date).AddDays(-7)
} | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue
'@

$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NonInteractive -Command `"$cleanupScript`""
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At "03:00AM"
Register-ScheduledTask -TaskName "GitHubRunnerCleanup" -Action $action -Trigger $trigger -RunLevel Highest

Step 10: Monitor Runner Status and Logs

Runner diagnostic logs are stored in C:actions-runner_diag. Review them if a runner shows as offline or fails to pick up jobs:

Get-ChildItem "C:actions-runner_diag" | Sort-Object LastWriteTime -Descending | Select-Object -First 5
Get-Content "C:actions-runner_diagRunner_$(Get-Date -Format 'yyyyMMdd')*.log" -Tail 50

From the GitHub repository Settings > Actions > Runners page, the runner should appear with a green status indicator when it is online and ready to accept jobs.

Security Considerations

Self-hosted runners that process code from public repositories pose a significant security risk since malicious pull requests can execute arbitrary code on your server. For WS2012 R2 runners, restrict runner usage to private repositories only, or require pull request approval before running workflows from external contributors. Use the runner groups feature in GitHub Organizations to limit which repositories can access which runners. Additionally, consider running the runner service under a low-privilege account and using Windows AppLocker policies to restrict what the runner service account can execute.

Summary

Your Windows Server 2012 R2 machine is now registered as a GitHub Actions self-hosted runner, running as a Windows service with automatic startup. Workflows targeting the ws2012r2 label will be routed to this machine, enabling builds that require legacy .NET Framework, internal network access, or specific Windows Server toolchain configurations unavailable on cloud-hosted runners. The runner operates securely with a dedicated service account, automated workspace cleanup, and diagnostic logging for operational monitoring.