Self-hosted GitHub Actions runner installeren op Ubuntu
Installeer een self-hosted GitHub Actions runner op Ubuntu 24.04. Leer hoe u de runner als systemd-service configureert en voorkom beveiligingsrisico's bij fork pull requests.
Wat een self-hosted GitHub Actions runner doet
Een self-hosted GitHub Actions runner is een programma dat u op uw eigen VPS installeert. Het vraagt GitHub om taken en voert deze uit op uw hardware. U registreert de runner voor één repository, installeert deze als een systemd-service en de runner start automatisch na elke reboot. GitHub plant de taak in. Uw server voert het werk uit.
CI (continuous integration) op een eigen machine is om twee redenen waardevol. Build-minuten worden niet langer geteld en een taak kan bronnen bereiken die alleen op uw machine beschikbaar zijn, zoals een warme build-cache of een privénetwerk. De prijs hiervan is beveiliging. De runner voert alles uit wat in het workflow-bestand staat, onder de gebruiker die u heeft toegewezen. Een workflow-bestand is dus inherent remote code execution. Bij een private repository is dit acceptabel, omdat alleen vertrouwde personen wijzigingen kunnen doorvoeren. Bij een public repository vormt dit een reëel risico; de sectie over fork pull requests legt het mechanisme hierachter uit.
Alles hieronder is gebaseerd op Ubuntu 24.04 met runner-versie 2.336.0, de huidige release per juli 2026.
Wat u nodig heeft voordat u begint
Begin met een VPS met een standaard beheerdersaccount en sudo, de status die u bereikt in de eerste tien minuten op een nieuwe VPS. U hoeft geen inkomende poort te openen. De runner opent een uitgaande HTTPS-verbinding (hypertext transfer protocol secure) naar GitHub en houdt deze open terwijl deze wacht op werk, waardoor GitHub nooit verbinding maakt met uw server. Uw firewall kan gesloten blijven voor de buitenwereld en taken komen alsnog aan.
U heeft ook beheerdersrechten op de repository nodig, omdat het registratietoken wordt getoond in de instellingen van de repository.
Maak een toegewezen gebruiker aan voor de runner
Voer de runner nooit uit als root of als uw eigen beheerdergebruiker. Elke taak erft de rechten van de runner-gebruiker, dus een workflow die sudo aanroept slaagt als de runner-gebruiker sudo mag gebruiken. Maak één gebruiker zonder privileges aan die niets bezit behalve de eigen thuismap. Gebruikersaccounts met minimale rechten op een VPS behandelt het algemene patroon. Hieronder volgt de specifieke uitvoering.
sudo useradd -m -s /bin/bash gharunner
sudo passwd -l gharunner
sudo chmod 750 /home/gharunner
sudo install -d -m 700 -o gharunner -g gharunner /home/gharunner/actions-runnerpasswd -l blokkeert het wachtwoord, zodat niemand kan inloggen als gharunner met een wachtwoord. De modus 700 op de runner-map is van belang omdat de runner daar zijn inloggegevens in leesbare tekst opslaat en een checkout privégegevens van de broncode kan bevatten.
Controleer beide eigenschappen voordat u verdergaat:
sudo passwd -S gharunner
sudo -l -U gharunnerpasswd -S print een regel die begint met gharunner L, waarbij L betekent dat het wachtwoord is geblokkeerd. sudo -l -U gharunner hoort te antwoorden met is not allowed to run sudo. Als het een lijst met toegestane commando's print, zit het account in een sudo-groep en is de isolatie die u zojuist heeft opgebouwd verdwenen.
De runner downloaden en het tar-bestand controleren
Werk vanaf dit punt als de gebruiker runner.
sudo -iu gharunner
cd ~/actions-runner
RUNNER_VERSION=2.336.0
curl -fL -o actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"Voer eerst uname -m uit als u niet zeker bent van de architectuur. x86_64 gebruikt het bestand linux-x64 hierboven. aarch64 gebruikt actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz.
Controleer nu wat u heeft gedownload. De SHA256 (secure hash algorithm, 256 bit) hieronder is voor het 2.336.0 x64 tar-bestand. GitHub toont de waarde voor de huidige release op de releasepagina en op het scherm New self-hosted runner. Deze waarde verandert bij elke versie; kopieer deze dus vanaf daar wanneer u een andere versie installeert.
echo "04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d actions-runner-linux-x64-2.336.0.tar.gz" | sha256sum -cEen correcte download geeft één regel weer:
actions-runner-linux-x64-2.336.0.tar.gz: OKEen afgebroken of gewijzigd bestand geeft de foutmelding en een waarschuwing weer:
actions-runner-linux-x64-2.336.0.tar.gz: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchSla de controle niet over en laat niet tar het probleem vinden. Een onvolledig geschreven archief faalt met gzip: stdin: unexpected end of file en tar: Unexpected EOF in archive; dit geeft aan dat het bestand beschadigd is, maar niet of het voortijdig is afgebroken of is vervangen.
tar xzf ./actions-runner-linux-x64-2.336.0.tar.gz
lsWat het tarball-bestand bevat en wat niet
Na het uitpakken bevat de map config.sh, run.sh, env.sh, safe_sleep.sh, bin/ en externals/. bin/ bevat de runner-binaries en bin/installdependencies.sh. externals/ bevat de gebundelde Node-runtime waarop JavaScript-acties worden uitgevoerd.
Er is nog geen svc.sh aanwezig. De documentatie van GitHub beschrijft dit als het script "dat wordt aangemaakt na het succesvol toevoegen van de runner", omdat het wordt geschreven op basis van een sjabloon waarin uw repository- en runnernaam in de servicenaam zijn verwerkt. Daarom faalt sudo ./svc.sh install vóór ./config.sh met sudo: ./svc.sh: command not found. Registreer eerst en installeer daarna de service.
Installeer de afhankelijkheden voor de runner
De runner is een .NET-applicatie en vereist daarom enkele gedeelde bibliotheken. Verlaat de shell van de runner-gebruiker en installeer deze met sudo, aangezien het script schrijft naar de pakketdatabase van het systeem.
exit
cd /home/gharunner/actions-runner
sudo ./bin/installdependencies.shOp Ubuntu 24.04 haalt dit libkrb5-3, zlib1g, liblttng-ust1t64, libssl3t64 en libicu74 op. Het script probeert verschillende versienamen voor elke bibliotheek en behoudt de versie die uw release aanbiedt; daarom werkt hetzelfde script op oudere Ubuntu-versies en op Debian.
Sla deze stap over en ./config.sh stopt voordat er enige actie wordt ondernomen:
Dependencies is missing for Dotnet Core 6.0
Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.Een ontbrekende libicu levert hetzelfde advies op onder een andere eerste regel, Libicu's dependencies is missing for Dotnet Core 6.0. Beide zijn afkomstig van dezelfde bron: config.sh voert ldd uit op de gebundelde bibliotheken voordat het proces start. Een niet-opgeloste koppeling stopt het script dus in plaats van dat er later een onduidelijke crash optreedt.
De runner registreren bij uw repository
Haal een token op uit de repository. Open Settings, vervolgens Actions, dan Runners en kies New self-hosted runner. De pagina toont een registratietoken dat begint met A. Dit token verloopt één uur na aanmaak; genereer het dus pas wanneer u klaar bent om het te plakken.
Registreer de runner als de runner-gebruiker. config.sh weigert te draaien onder sudo.
sudo -iu gharunner
cd ~/actions-runner
./config.sh --url https://github.com/YOUR-USER/YOUR-REPO \
--token PASTE_REGISTRATION_TOKEN_HERE \
--name vps-runner-1 \
--labels vps \
--work _work \
--unattended \
--replaceWat deze vlaggen doen. --name bepaalt hoe de runner verschijnt in de repository; kies dus een naam die u over zes maanden nog steeds herkent. --labels voegt uw eigen labels toe; de runner beschikt al automatisch over self-hosted, Linux en X64. --work benoemt de map waar checkouts worden geplaatst, binnen de runner-directory. --unattended beantwoordt de interactieve vragen met de standaardwaarden; dit is gewenst wanneer het commando in een script staat. --replace neemt een bestaande registratie met dezelfde naam over in plaats van te falen; dit is nuttig wanneer u de server opnieuw opbouwt.
Een succesvolle uitvoering eindigt met deze regels:
√ Runner successfully added
√ Runner connection is good
√ Settings Saved.De registratie bevindt zich nu in de runner-directory als .runner, .credentials en .credentials_rsaparams. De laatste twee bestanden identificeren deze runner bij GitHub; iedereen die deze kan lezen, kan zich dus als de runner voordoen. Dit is de reden waarom de directory de modus 700 heeft en de gebruiker geen sudo-rechten bezit.
De runner installeren als systemd-service
./run.sh in een terminal is prima voor een eenmalige test, maar het proces stopt zodra uw SSH-sessie wordt beëindigd. Installeer de service zodat de runner automatisch start bij het opstarten van het systeem. systemd-services en timers op een VPS legt de unit-bestanden zelf uit. Hier schrijft svc.sh er een voor u.
exit
cd /home/gharunner/actions-runner
sudo ./svc.sh install gharunner
sudo ./svc.sh start
sudo ./svc.sh statussvc.sh vereist root-rechten omdat het een unit naar /etc/systemd/system schrijft en deze inschakelt. Het argument na install is de gebruiker waaronder de service wordt uitgevoerd. Geef gharunner expliciet op. Zonder argument valt het script terug op $SUDO_USER, wat uw beheerdersaccount is, waardoor elke taak wordt uitgevoerd als een gebruiker die sudo kan gebruiken.
De unit is vernoemd naar de repository en de runner, in de vorm actions.runner.YOUR-USER-YOUR-REPO.vps-runner-1.service. U hoeft dit nooit volledig uit te typen:
systemctl list-units 'actions.runner.*'
sudo journalctl -u 'actions.runner.*' -n 20 --no-pagerEen gezonde runner logt √ Connected to GitHub en vervolgens een regel die eindigt op Listening for Jobs, en de Runners-pagina van de repository toont deze als Idle. Een runner die als Offline wordt weergegeven, draait niet of kan GitHub niet bereiken op poort 443.
Een taak naar de runner sturen
runs-on selecteert een runner op basis van een label. Vraag om self-hosted plus uw eigen label, zodat een taak niet terechtkomt op een runner die u niet bedoelde.
name: build
on:
push:
branches: [main]
jobs:
build:
runs-on: [self-hosted, linux, vps]
steps:
- uses: actions/checkout@v5
- run: uname -aAls de taak blijft wachten bij Waiting for a runner to pick up this job, komen de labels niet overeen. Elk label in runs-on moet bestaan op de runner; één extra woord zorgt er al voor dat de taak in de wachtrij blijft staan zonder dat er ergens een foutmelding verschijnt. Vergelijk de lijst met de labels die naast de runner in de repository-instellingen worden getoond.
Waarom self-hosted runners en publieke repositories niet samengaan
Dit is het onderdeel dat mensen overslaan. De richtlijnen van GitHub zijn duidelijk: self-hosted runners "zouden bijna nooit gebruikt moeten worden voor publieke repositories", en ze "bieden geen garanties wat betreft het draaien in kortstondige, schone virtuele machines en kunnen permanent gecompromitteerd worden door niet-vertrouwde code in een workflow".
Het mechanisme is eenvoudig. Een pull request vanuit een fork bevat een eigen kopie van het workflowbestand. Als uw publieke repository pull request-workflows uitvoert op uw runner, kan iedereen die de repository fork-t een workflow voorstellen die hun commando's op uw VPS uitvoert. Zij hebben geen schrijftoegang nodig, omdat hetgeen zij voorstellen precies datgene is wat wordt uitgevoerd.
Instellingen voor goedkeuring verzachten dit zonder het probleem op te lossen. Het standaardbeleid voor een publieke repository vereist dat een beheerder de fork-workflow van een nieuwe bijdrager goedkeurt. Nadat u die persoon één keer heeft goedgekeurd, worden hun latere pull requests uitgevoerd zonder nieuwe melding. De controle is dus afhankelijk van een mens die elke keer een diff leest, en een payload die drie niveaus diep in een build-script verborgen zit, is gemakkelijk over het hoofd te zien.
Een fork pull request ontvangt uw secrets niet en de GITHUB_TOKEN is alleen-lezen. Dat beperkt de schade binnen GitHub. Het doet echter niets voor uw server. De aanvaller beschikt over een shell als gharunner, waardoor deze elk bestand kan lezen waar die gebruiker toegang toe heeft, alles kan bereiken wat de VPS op zijn privénetwerk kan bereiken, en iets kan achterlaten in ~/.bashrc of een systemd-unit van de gebruiker die tijdens de volgende taak wordt uitgevoerd.
Registratie met --ephemeral zorgt ervoor dat de runner één taak accepteert en zich daarna afmeldt, zodat één taak de werkruimte van de volgende taak niet kan lezen. Dit helpt alleen als iets de machine of de container voor elke taak opnieuw opbouwt, omdat een achterdeur die in de home-directory van de runner-gebruiker is geschreven, een nieuwe registratie overleeft.
De onderstaande regels zijn kort. Gebruik self-hosted runners voor private repositories. Als u er toch een moet koppelen aan een publieke repository, voer dan geen fork pull requests uit op die machine, bewaar niets anders op die server en behandel de machine als wegwerpbaar.
Docker-taken en de groep die in feite root is
Container-taken, service-containers en elke workflow-stap die docker build aanroept, vereisen een Docker-daemon op de runner-host. Installeer Docker op de gebruikelijke wijze, zoals beschreven in Docker en Docker Compose op een VPS, en voeg vervolgens de runner-gebruiker toe aan de docker-groep.
Begrijp de afweging voordat u dit uitvoert. Het lidmaatschap van de docker-groep staat gelijk aan root-toegang, omdat een container / kan mounten en daarin als root kan draaien. Een workflow die met de Docker-socket kan communiceren, kan dus elk bestand op de VPS lezen en schrijven, inclusief /etc/shadow. Bij een private repository met vertrouwde bijdragers kan dit een acceptabele prijs zijn. In alle andere gevallen heft het de beveiliging van een gebruiker zonder privileges op. Rootless Docker houdt container-builds binnen de rechten van de runner-gebruiker zelf, ten koste van een tragere storage-driver en het onvermogen om geprivilegieerde containers te draaien.
Updates en het correct verwijderen van de runner
Een self-hosted runner werkt zichzelf standaard bij. De runner detecteert een nieuwe release, vervangt de eigen bestanden en herstart de service, waardoor u normaal gesproken geen actie hoeft te ondernemen. ./config.sh --disableupdate schakelt de automatische update uit wanneer u een vaste versie nodig heeft. Daarna is het bijwerken uw verantwoordelijkheid: de documentatie van GitHub is expliciet dat een runner die is geconfigureerd met --disableupdate handmatig moet worden bijgewerkt.
Een handmatige update behoudt de registratie, omdat .runner en .credentials niet in het tarball-bestand zitten. Stop de service, download en controleer de checksum van het nieuwe tarball-bestand zoals beschreven in gharunner, pak het uit over dezelfde map met tar xzf en start daarna de service opnieuw:
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh startOm de runner te verwijderen, deïnstalleert u eerst de service en voert u daarna de deregistratie uit. Het verwijderingstoken is afkomstig van dezelfde Runners-pagina, onder de knop Remove van de betreffende runner.
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh uninstall
sudo -iu gharunner
cd ~/actions-runner
./config.sh remove --token PASTE_REMOVAL_TOKEN_HEREHet verwijderen van de map zonder deregistratie zorgt ervoor dat de runner als Offline in de repository blijft staan. GitHub verneemt namelijk pas dat de runner is verwijderd wanneer de runner dit zelf meldt of wanneer een beheerder de vermelding handmatig verwijdert.
Foutmodi en bijbehorende meldingen
Must not run with sudo. config.sh toont deze melding en sluit af wanneer het als root wordt uitgevoerd. Deze controle is bewust ingebouwd, omdat bestanden die eigendom zijn van root in _work alle latere taken die als servicegebruiker worden uitgevoerd, verstoren. Voer ./config.sh uit als gharunner. De variabele RUNNER_ALLOW_RUNASROOT omzeilt de controle, maar het gebruik ervan verplaatst het probleem enkel naar een later moment.
sudo: ./svc.sh: command not found. U bevindt zich in de juiste map. svc.sh bestaat nog niet, omdat config.sh de registratie nog niet heeft voltooid. Registreer de runner en installeer daarna de service.
Http response code: NotFound from 'POST https://api.github.com/actions/runner-registration'. Het token is geen geldig registratietoken. Het is mogelijk verlopen, aangezien deze slechts één uur geldig zijn, of er is een personal access token geplakt in plaats van het registratietoken van de Runners-pagina. Genereer een nieuw token en plak dit opnieuw.
Dependencies is missing for Dotnet Core 6.0. Voer sudo ./bin/installdependencies.sh uit vanuit de runner-map als root en registreer daarna opnieuw.
Runner Offline na een herstart. Voer systemctl is-enabled 'actions.runner.*' uit. Als er niets wordt vermeld, is ./svc.sh install nooit uitgevoerd; de runner bestond in dat geval alleen binnen uw terminalsessie. Als de unit is ingeschakeld en de runner nog steeds Offline is, lees dan journalctl -u 'actions.runner.*' en controleer het uitgaande HTTPS-verkeer.
De schijf raakt vol. Checkouts, build-caches en Docker-images hopen zich op onder _work en in de home-map van de runner-gebruiker, en er is geen proces dat deze automatisch opruimt. Monitor du -sh /home/gharunner/actions-runner/_work en voeg een geplande opschoonactie toe voordat de schijfcapaciteit het proces voor u beëindigt.
FAQ
Waarom geeft sudo ./svc.sh install de melding command not found?
Omdat svc.sh niet in de tarball van de runner zit. Het wordt gegenereerd in de runner-directory zodra ./config.sh de registratie voltooit, waarbij uw repository- en runnernaam worden gebruikt om de servicenaam op te bouwen. Voer ./config.sh eerst uit als de runner-gebruiker. Daarna vindt sudo ./svc.sh install gharunner het script en schrijft het een unit genaamd actions.runner.OWNER-REPO.RUNNER-NAME.service naar /etc/systemd/system.
Moet ik een firewallpoort openen voor een self-hosted runner?
Nee. De runner opent een uitgaande HTTPS-verbinding naar GitHub en houdt deze open terwijl hij wacht op taken; GitHub initieert dus nooit een verbinding met uw VPS. Sta uitgaand verkeer op poort 443 toe en laat uw inkomende regels gesloten. Als de runner Offline aangeeft terwijl de service draait, controleer dan uitgaande filtering en DNS in plaats van inkomende regels.
Kan ik een self-hosted runner gebruiken voor een publieke repository?
Dat kan, maar GitHub raadt het af. Een pull request van een fork bevat een eigen workflow-bestand, dus iedereen die uw repository kan forken, kan commando's voorstellen die op uw machine worden uitgevoerd. De goedkeuringsprompt geldt alleen voor de eerste run van een contributor. Als u een runner aan een publieke repository koppelt, schakel dan workflows voor fork-pull-requests uit, bewaar niets anders op die server en installeer de machine periodiek opnieuw.
Waarom mislukt de registratie met Http response code: NotFound?
De registratie-aanroep geeft NotFound terug wanneer de inloggegevens onjuist zijn, niet alleen wanneer de URL onjuist is, wat de melding misleidend maakt. Registratietokens verlopen één uur nadat ze zijn getoond en een personal access token wordt voor deze aanroep niet geaccepteerd. Open Settings, Actions, Runners, New self-hosted runner opnieuw, kopieer het nieuwe token en bevestig dat de waarde --url naar een repository wijst waar u beheerdersrechten heeft.