Last updated: 2026/10/30
If you build Windows software with GitHub Actions, such as .NET apps, desktop tools or Windows installers, you need a Windows machine to run your builds. GitHub’s own Windows runners are charged by the minute and start from scratch every time. A self-hosted runner on a Windows VPS gives you an always-on build machine with your tools already installed, for a flat monthly cost.
Key takeaways
- A self-hosted runner is a build machine you control. GitHub sends jobs to it, and it keeps your tools and caches between builds.
- Setup takes about 15 minutes: download the runner, connect it to your repository, and install it as a Windows service.
- Runs as a service, so it starts automatically after reboots with no one signed in.
- Use it with private repositories only. GitHub warns that public repositories can let others run code on your machine.
Why use a self-hosted runner on a Windows VPS?
GitHub offers two kinds of runners: GitHub-hosted (GitHub provides the machine) and self-hosted (you provide it). For Windows builds, a self-hosted runner on a VPS has some clear advantages:
- Flat monthly cost. GitHub-hosted Windows runners cost $0.010 per minute from January 2026, beyond your plan’s included minutes. If you build often, a VPS at a fixed monthly price can work out cheaper.
- Tools stay installed. GitHub-hosted runners are wiped after every job. On your own VPS, SDKs, build tools and dependency caches stay in place, so builds often start faster.
- Full control. Install exactly the software versions you need, including licensed or older tools that GitHub’s images don’t include.
- A fixed IP address. Useful when builds need to reach servers, databases or APIs that only allow approved IP addresses.
A note on pricing: in December 2025, GitHub announced a per-minute fee for self-hosted runners in private repositories, then postponed it the next day after community feedback. Check GitHub’s current billing documentation for the latest before you decide.
When GitHub-hosted runners are the better choice
- You only build occasionally, and your plan’s included minutes cover it.
- Your repository is public. GitHub recommends self-hosted runners for private repositories only (see Security).
- You need a fresh, clean machine for every job.
What you need
| Item | Requirement |
|---|---|
| Windows VPS | Windows Server 2019, 2022 or 2025 |
| Memory | 4 GB for light builds. 8 GB or more for .NET, C++ or larger projects. |
| Storage | Enough for your tools, source code and build output. Build folders grow over time. |
| GitHub access | Owner of the repository (personal account), or admin access to it (organization) |
| Repository | A private repository |
| Network | Outbound HTTPS to GitHub. No inbound ports need to be opened. |
Step 1: Install your build tools
Connect to your VPS with Remote Desktop and install what your builds need, for example Git, the .NET SDK, Visual Studio Build Tools or Node.js. At minimum, install Git. Open PowerShell as Administrator:
$ProgressPreference = 'SilentlyContinue'
$rel = Invoke-RestMethod https://api.github.com/repos/git-for-windows/git/releases/latest
$url = ($rel.assets | Where-Object name -match '^Git-.*-64-bit\.exe$').browser_download_url
Invoke-WebRequest $url -OutFile "$env:TEMP\git.exe" -UseBasicParsing
Start-Process "$env:TEMP\git.exe" -ArgumentList '/VERYSILENT /NORESTART' -Wait
On Windows Server 2025, you can use winget install --id Git.Git -e --source winget instead.
Step 2: Create the runner folder
Keep the runner in its own folder near the root of the drive:
New-Item -ItemType Directory -Force C:\actions-runner
Set-Location C:\actions-runner
Step 3: Get the setup commands from GitHub
- Open your repository on GitHub and click Settings.
- In the left sidebar, click Actions → Runners.
- Click New self-hosted runner.
- Choose Windows and x64.
GitHub shows the exact commands for the current runner version, including a registration token. The token expires after one hour, so do the next steps straight away.
Step 4: Download the runner
Copy the Download commands from the GitHub page and run them in the same PowerShell window. They look like this, with the current version number in place of X.Y.Z:
Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/vX.Y.Z/actions-runner-win-x64-X.Y.Z.zip -OutFile actions-runner-win-x64-X.Y.Z.zip
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD\actions-runner-win-x64-X.Y.Z.zip", "$PWD")
Use the commands GitHub shows you rather than these, so you get the right version (GitHub’s page also includes a checksum check).
Step 5: Connect the runner and install it as a service
Run the Configure command from the GitHub page. It looks like this:
.\config.cmd --url https://github.com/YOUR-ACCOUNT/YOUR-REPO --token YOUR-TOKEN
It then asks a few questions. Press Enter to accept a default:
| Question | Suggested answer |
|---|---|
| Runner group | Press Enter (default) |
| Runner name | Something clear, e.g. windows-vps-01 |
| Additional labels | windows-vps (used to send jobs to this runner) |
| Work folder | Press Enter (_work) |
| Run as service? | Y, so it starts automatically after reboots |
| User account for the service | Press Enter for the default, or enter a dedicated account (see Security) |
Choosing Y for the service matters. According to GitHub’s documentation, if you configure the runner without the service option, you have to remove it and configure it again to add it later.
Step 6: Check the runner is online
On the VPS:
Get-Service "actions.runner.*"
The status should be Running. On GitHub, refresh Settings → Actions → Runners. Your runner should appear with the status Idle, meaning it’s ready for jobs.
Step 7: Send a workflow to your runner
Create .github/workflows/windows-build.yml in your repository:
name: Build on Windows VPS
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
build:
runs-on: [self-hosted, windows, windows-vps]
steps:
- uses: actions/checkout@v4
- name: Check the build machine
shell: powershell
run: |
git --version
$PSVersionTable.PSVersion
Get-ComputerInfo -Property OsName, CsTotalPhysicalMemory
The runs-on line sends the job to runners with all three labels, which means your VPS. Push the file, then open the Actions tab to watch it run. Replace the last step with your real build commands, such as dotnet build or msbuild.
Tip: use shell: powershell (Windows PowerShell, built in). shell: pwsh only works if you’ve installed PowerShell 7 on the VPS.
Keeping the runner running
- Reboots: the service starts automatically. Unlike desktop apps, a runner service doesn’t need anyone signed in.
- Runner updates: the runner updates itself automatically when GitHub releases new versions.
- Windows Update: keep the VPS patched, and schedule restarts for a quiet time so they don’t interrupt builds.
- Disk space: build folders under
C:\actions-runner\_workgrow over time. Clean them up regularly, or add a cleanup step to your workflows. - Start, stop and check the service:
Start-Service "actions.runner.*"
Stop-Service "actions.runner.*"
Get-Service "actions.runner.*"
Security
- Private repositories only. GitHub recommends using self-hosted runners only with private repositories. In a public repository, anyone could open a pull request that runs their own code on your runner.
- Use a dedicated machine. Don’t run the runner on a server that also hosts your live website or database. A separate VPS keeps build jobs away from production.
- Use a limited service account. The default service account has limited rights. If your builds need more access, create a dedicated Windows user with only the permissions required, rather than using an administrator account.
- Keep secrets in GitHub. Store passwords and API keys as GitHub Actions secrets, not in files on the VPS.
- Lock down Remote Desktop. Use a strong password and, if possible, allow Remote Desktop only from your own IP address. The runner itself only needs outgoing connections to GitHub.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| Job stuck on “Waiting for a runner to pick up this job” | The labels in runs-on don’t match the runner’s labels, or the runner is offline |
Compare the labels in Settings → Actions → Runners with your workflow. Check the service is running. |
| Runner shows as Offline | The service has stopped, or the VPS restarted and the service failed to start | Run Start-Service "actions.runner.*" and check its status. |
| “Token expired” during setup | The registration token is only valid for one hour | Open New self-hosted runner again to get a fresh token. |
| A tool isn’t found in the build, but works when you’re signed in | The service started before the tool was installed, or the tool was installed only for your user | Restart the service after installing tools, and install them for all users. |
| “Access denied” errors in a build | The service account can’t reach a folder or resource | Give the service account access to that folder, or reconfigure the runner with a dedicated account. |
| “pwsh is not recognized” | PowerShell 7 isn’t installed | Use shell: powershell, or install PowerShell 7. |
Removing the runner
On GitHub, open the runner under Settings → Actions → Runners and click Remove to get a removal command with its own token. Then on the VPS:
Set-Location C:\actions-runner
Stop-Service "actions.runner.*"
.\config.cmd remove --token YOUR-REMOVAL-TOKEN
Choosing a VPS for your runner
For build machines, memory, CPU and disk speed matter more than location, since the runner only talks to GitHub. Choose a plan that fits your builds: 4 GB for light work, 8 GB or more for larger .NET or C++ projects. If your team or the servers your builds connect to are in Japan or East Asia, a VPS in the same region keeps those connections fast.
FAQ
Is a self-hosted runner free?
GitHub announced a per-minute fee for self-hosted runners in December 2025, then postponed it. Check GitHub’s billing documentation for the current position. You pay for the VPS either way.
Can I use a self-hosted runner with a public repository?
You can, but GitHub recommends against it, because anyone could run code on your machine through a pull request. Use GitHub-hosted runners for public repositories.
Can I run more than one runner on one VPS?
Yes. Configure each runner in its own folder (for example C:\actions-runner-2). Make sure the VPS has enough memory and CPU for builds running at the same time.
Do I need Remote Desktop after setup?
Only for maintenance, such as installing new tools. Once the runner is installed as a service, it works without anyone signed in.
Windows Server or Windows 11?
Windows Server is the standard choice for a VPS and works well for most builds. If you specifically need to test on Windows 11, that’s a separate setup.
How is this different from running Claude Code on a VPS?
Both use an always-on Windows VPS, but a runner works as a background service and needs no one signed in, while interactive tools need a signed-in session. See our guide to running Claude Code 24/7 on a Windows VPS.
Need a Windows VPS for your builds?
Winserver offers Windows Server VPS plans from our data center in Osaka, Japan, with full administrator access and plans from 2 GB up. Compare plans to find the right size for your build machine.


