How to Set Up GitLab Runner on Windows Server 2025
GitLab Runner is the open-source agent that picks up CI/CD jobs defined in your .gitlab-ci.yml files and executes them on the host where the runner is installed. Running a GitLab Runner on Windows Server 2025 is the right choice when your pipelines build Windows executables, run MSBuild or NuGet restore operations, execute PowerShell-based test suites, or need access to Windows-only dependencies. This guide covers every stage of the setup: downloading the binary, choosing the right executor, registering with your GitLab instance, running as a Windows service, and tuning the configuration file for reliable operation.
Prerequisites
- Windows Server 2025 with administrator privileges
- Network access to your GitLab instance (cloud gitlab.com or self-managed)
- A GitLab project or group with Maintainer or Owner role (to obtain a registration token)
- PowerShell 5.1 or later
- Optional: Docker Desktop for Windows if using the
docker-windowsexecutor
Step 1: Download gitlab-runner.exe
GitLab publishes pre-built binaries for Windows on its package repository. Download the latest stable 64-bit binary with PowerShell:
# Create a dedicated directory for GitLab Runner
New-Item -ItemType Directory -Path "C:GitLab-Runner" -Force
# Download the latest gitlab-runner binary
$runnerUrl = "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe"
Invoke-WebRequest -Uri $runnerUrl -OutFile "C:GitLab-Runnergitlab-runner.exe"
# Verify the download
Get-FileHash "C:GitLab-Runnergitlab-runner.exe" -Algorithm SHA256
Add C:GitLab-Runner to the system PATH so the binary is available in every PowerShell and Command Prompt session:
$currentPath = [System.Environment]::GetEnvironmentVariable("PATH", "Machine")
[System.Environment]::SetEnvironmentVariable(
"PATH",
"$currentPath;C:GitLab-Runner",
[System.EnvironmentVariableTarget]::Machine
)
# Reload PATH in current session
$env:PATH += ";C:GitLab-Runner"
# Confirm binary is accessible
gitlab-runner --version
Step 2: Obtain a Registration Token
Registration tokens determine which GitLab scope the runner is available to. Tokens are found in:
- Project-level: Settings → CI/CD → Runners → New project runner (GitLab 16+) or expand Specific runners section
- Group-level: Group → Settings → CI/CD → Runners
- Instance-level: Admin Area → CI/CD → Runners (self-managed only)
For GitLab 16 and later the legacy registration token is replaced by runner authentication tokens generated after creating a runner object in the UI. Copy the token — it is shown only once.
Step 3: Register the Runner
Run the interactive registration command. For unattended registration pass all flags directly:
# Interactive registration
gitlab-runner register
# Unattended registration (shell executor — recommended for Windows builds)
gitlab-runner register `
--non-interactive `
--url "https://gitlab.example.com/" `
--token "glrt-xxxxxxxxxxxxxxxxxxxx" `
--executor "shell" `
--shell "powershell" `
--description "win2025-shell-runner" `
--tag-list "windows,powershell,dotnet" `
--run-untagged="false" `
--locked="false"
For the docker-windows executor (requires Docker Desktop with Windows containers enabled):
gitlab-runner register `
--non-interactive `
--url "https://gitlab.example.com/" `
--token "glrt-xxxxxxxxxxxxxxxxxxxx" `
--executor "docker-windows" `
--docker-image "mcr.microsoft.com/windows/servercore:ltsc2025" `
--description "win2025-docker-runner" `
--tag-list "windows,docker"
Step 4: Review config.toml
After registration, the configuration is written to C:GitLab-Runnerconfig.toml. Open it to verify and tune settings:
concurrent = 4
check_interval = 0
shutdown_timeout = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "win2025-shell-runner"
url = "https://gitlab.example.com/"
id = 42
token = "glrt-xxxxxxxxxxxxxxxxxxxx"
token_obtained_at = 2025-09-01T00:00:00Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "shell"
shell = "powershell"
[runners.custom_build_dir]
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.cache.s3]
[runners.cache.gcs]
[runners.cache.azure]
Key settings to adjust for Windows production use:
concurrent— maximum number of simultaneous jobs across all runners on this hostshell = "powershell"— use PowerShell rather thancmdfor all shell stepsbuilds_dir— set a custom build workspace path (e.g.,"D:\GitLab\builds")cache_dir— set a custom cache path to persist dependencies between jobs
[[runners]]
name = "win2025-shell-runner"
url = "https://gitlab.example.com/"
token = "glrt-xxxxxxxxxxxxxxxxxxxx"
executor = "shell"
shell = "powershell"
builds_dir = "D:\GitLab\builds"
cache_dir = "D:\GitLab\cache"
Step 5: Install the Runner as a Windows Service
Running gitlab-runner as a Windows service ensures it starts automatically at boot and can be managed with standard Windows service tools:
# Install the Windows service (runs as SYSTEM by default)
gitlab-runner install
# Start the service
gitlab-runner start
# Verify it is running
Get-Service -Name "gitlab-runner"
# View live logs
gitlab-runner status
Start-Process "eventvwr.msc" # check Application log for runner events
To run the service as a specific user account rather than SYSTEM (recommended for domain environments):
gitlab-runner install `
--user "DOMAINsvc-glrunner" `
--password "ServiceAccount!Pass25"
Step 6: Configure a PowerShell Executor Pipeline
With the shell executor configured to use PowerShell, your .gitlab-ci.yml jobs run natively in PowerShell. Here is an example pipeline for a .NET application:
variables:
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
stages:
- build
- test
- package
build:
stage: build
tags:
- windows
- powershell
script:
- Write-Host "Building on $env:COMPUTERNAME"
- dotnet restore MyApp.sln
- dotnet build MyApp.sln --configuration Release --no-restore
test:
stage: test
tags:
- windows
script:
- dotnet test MyApp.TestsMyApp.Tests.csproj --configuration Release --logger "trx;LogFileName=results.trx"
artifacts:
reports:
junit: "**/*.trx"
expire_in: 7 days
package:
stage: package
tags:
- windows
script:
- dotnet publish MyAppMyApp.csproj -c Release -o publish
- Compress-Archive -Path publish* -DestinationPath "MyApp-$env:CI_COMMIT_SHORT_SHA.zip"
artifacts:
paths:
- "*.zip"
Step 7: Troubleshoot Authentication Issues
Common registration and authentication problems on Windows Server 2025 include:
- TLS certificate errors: If your GitLab instance uses a self-signed certificate, add it to the Windows Trusted Root store or pass
--tls-ca-fileduring registration. - Token already used: Runner authentication tokens can only be used once. Generate a new runner in the GitLab UI if registration fails.
- Service account lacks rights: Ensure the service account has write access to
builds_dirandcache_dir. - PowerShell execution policy: The runner service may run under a policy that blocks scripts. Fix with:
# Set policy for the service account context (Machine scope)
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
- Antivirus interference: Exclude
C:GitLab-Runner,builds_dir, andcache_dirfrom real-time scanning to avoid file-lock conflicts during builds.
Step 8: Managing and Unregistering Runners
# List registered runners
gitlab-runner list
# Stop and unregister a specific runner by token
gitlab-runner unregister --token "glrt-xxxxxxxxxxxxxxxxxxxx"
# Stop the Windows service
gitlab-runner stop
# Uninstall the Windows service
gitlab-runner uninstall
Conclusion
A GitLab Runner on Windows Server 2025 gives your GitLab CI/CD pipelines direct access to the Windows SDK, PowerShell modules, COM objects, and Windows-only test frameworks that containerized Linux runners cannot provide. By choosing the shell executor with PowerShell, maintaining a well-structured config.toml, and running the runner as a dedicated service account, you get a reliable and auditable build agent. For further scaling, register multiple runners on additional Windows Server 2025 hosts and assign them different tags to route pipeline jobs by capability — for example, separating GPU-intensive tasks from standard .NET builds.