
GitHub's hosted runners are convenient but come with limits. You get 2,000 minutes per month on free plans, and the machines are shared. If you're running lots of builds or need specific software, those limits get annoying fast.
Self-hosted runners solve this. Your builds run on your own server with no minute caps, faster execution (no cold starts), and whatever environment you want.
The main wins: no minute limits, faster builds (your runner stays warm instead of cold-starting a VM each time), and you can install whatever you need once rather than every build.
A $10/month VPS can handle more CI minutes than GitHub's paid tiers. And if you care about keeping code on your own infrastructure, self-hosted is the only option.
The tradeoff is maintenance. You keep the runner updated and secure.
You'll need:
Go to your repository on GitHub. Click Settings → Actions → Runners → New self-hosted runner.
Select Linux and x64 (or ARM64 if you're on an ARM server). GitHub shows you the installation commands. Keep this page open.
SSH into your VPS and create a dedicated user for the runner:
sudo useradd -m -s /bin/bash github-runner
sudo usermod -aG sudo github-runner
sudo su - github-runner
Create the runner directory:
mkdir actions-runner && cd actions-runner
Download the runner package. Copy the exact commands from GitHub since the version changes:
curl -o actions-runner-linux-x64-2.321.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-linux-x64-2.321.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.321.0.tar.gz
Run the configuration script. You'll need the token from GitHub:
./config.sh --url https://github.com/YOUR_USERNAME/YOUR_REPO --token YOUR_TOKEN
The script asks a few questions:
vps-runner-1self-hosted,linux,x64 (or custom ones like docker,nodejs)_workYou want the runner to start automatically and survive reboots:
sudo ./svc.sh install
sudo ./svc.sh start
Check the status:
sudo ./svc.sh status
You should see the runner listed as "Idle" in GitHub's runner settings.
Update your workflow file to target your self-hosted runner:
name: Build
on:
push:
branches: [main]
jobs:
build:
runs-on: self-hosted # This targets your runner
steps:
- uses: actions/checkout@v4
- name: Build
run: |
npm install
npm run build
You can also target specific labels:
runs-on: [self-hosted, linux, docker]
Your runner starts minimal. Install what your builds need:
# Docker
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker github-runner
# Node.js
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# PHP & Composer
sudo apt install -y php php-cli php-mbstring php-xml unzip
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
Self-hosted runners execute code from your repository. A few things to keep in mind:
Don't use self-hosted runners on public repos. Anyone who opens a PR could run arbitrary code on your server. GitHub warns about this too.
Use a dedicated server. Don't run the runner on a machine with sensitive data or production workloads.
Keep it updated. The runner auto-updates by default, but verify occasionally:
./config.sh --version
Limit network access. Consider firewall rules that restrict what the runner can reach.
One server can run multiple runners. Just create separate directories:
mkdir ~/runner-2 && cd ~/runner-2
# Repeat the download and config steps with a different name
For heavy workloads, you might want runners on separate VPS instances.
Runner shows as offline:
Check if the service is running: sudo ./svc.sh status
Check logs: journalctl -u actions.runner.*
Builds fail with permission errors:
Make sure the github-runner user has access to Docker, build directories, etc.
Runner is slow: Check if the server has enough RAM. Builds that swap to disk crawl.
Running lots of CI builds? Our VPS plans include unmetered bandwidth and fast NVMe storage. See pricing.