SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-28

systemd Type instellen: simple, forking of notify?

Blijft uw service actief terwijl het proces is gestopt? Leer hoe u de juiste Type= kiest voor simple, forking of notify om de werkelijke hoofd-PID correct te bewaken.

Waarom rapporteert systemd een unit als actief terwijl het proces is beëindigd

Een systemd-service-unit blijft active zolang het proces dat systemd als hoofdproces beschouwt in leven is, en Type= in de sectie [Service] bepaalt welk proces dit is. Als u een onjuiste waarde kiest, houdt systemd een shell-wrapper of een kortstondig ouderproces in de gaten, terwijl de daemon die u wilt monitoren binnen dezelfde unit sterft. De unit rapporteert correct over het proces dat het moet bewaken.

Het wijzigen van het herstartbeleid helpt hier niet. Restart= treedt in werking wanneer het hoofdproces wordt beëindigd, dus Restart=always wordt nooit geactiveerd zolang de hoofd-PID (process identifier) toebehoort aan een proces dat nog steeds draait. Corrigeer eerst Type=. Wat systemd doet nadat het hoofdproces daadwerkelijk is beëindigd, is een afzonderlijke beslissing, die wordt behandeld in de handleiding voor Restart= en RestartSec=.

Wat bepaalt Type= precies

Elke Type=-waarde beantwoordt tegelijkertijd twee vragen. Wanneer mag systemd deze unit als gestart beschouwen, en welk proces is het hoofdproces.

Het eerste antwoord regelt de volgorde. Een unit die de uwe noemt in After= wacht totdat systemd de uwe als gestart markeert. Een Type= die te vroeg "gestart" rapporteert, zorgt ervoor dat afhankelijke units worden uitgevoerd voordat uw service deze kan afhandelen.

Het tweede antwoord regelt het toezicht. systemd plaatst elk proces dat een unit start in een cgroup (control group), een kernel-functie die processen groepeert zodat ze gezamenlijk kunnen worden beperkt en beëindigd. De cgroup is de manier waarop systemctl stop opschoont: KillMode= staat standaard op control-group, waardoor het stoppen van een unit een signaal stuurt naar elk proces daarbinnen. De hoofd-PID is specifieker. Dit is het enige proces waarvan het beëindigen de unit stopt, en waarvan de exit-status het resultaat van de unit wordt. Het lezen van de cgroup alsof dit de hoofd-PID is, is waar de verwarring begint.

Type=simple rapporteert dat de service is gestart voordat het binaire bestand wordt uitgevoerd

Type=simple is de standaardinstelling wanneer ExecStart= is ingesteld en noch Type=, noch BusName= aanwezig is. systemd maakt het proces aan, beschouwt de unit direct als gestart en behandelt dat proces als het hoofd-PID. Vervolg-units starten onmiddellijk, nog voordat het binaire bestand van de service is uitgevoerd.

Dat laatste detail verklaart een veelvoorkomende verrassing. Een typefout in het ExecStart=-pad resulteert nog steeds in een starttaak die slaagt, waarna de fout kort daarna optreedt wanneer de uitvoering mislukt. systemd registreert dat geval met exitcode 203, die in de eigen tabel EXEC wordt genoemd en gedefinieerd als een fout bij het uitvoeren van het binaire bestand van de service. Het feit dat systemctl start zonder foutmelding terugkeert, bewijst dus niet dat uw binaire bestand bestaat.

Gebruik simple voor een programma dat op de voorgrond blijft en zichzelf nooit naar de achtergrond verplaatst. Dit is van toepassing op de meeste moderne daemons en vrijwel alles wat u zelf schrijft.

Type=exec wacht tot het programma daadwerkelijk is gestart

Type=exec is simple met één extra stap. systemd beschouwt de unit pas als gestart nadat zowel de fork als de uitvoering van het binaire bestand zijn geslaagd. Een ontbrekend binair bestand of een User= die niet kan worden opgelost, zorgt er nu voor dat de starttaak zelf faalt, in plaats van dat deze succes rapporteert en even later geruisloos stopt.

Type=exec werd geïntroduceerd in systemd 240, dus elke huidige serverdistributie beschikt hierover. Ubuntu 24.04 levert systemd 255 en Debian 13 levert systemd 257, peildatum augustus 2026. Controleer uw versie met systemctl --version.

De prijs hiervoor is één extra synchronisatiestap bij het opstarten. Het voordeel is een eerlijke exit-status van systemctl start. Voor een programma op de voorgrond geniet exec de voorkeur boven simple.

Type=forking, en hoe de hoofd-PID verloren gaat

Type=forking vertelt systemd dat het proces in ExecStart= een child-proces zal forken en vervolgens opzettelijk zal afsluiten. systemd wacht tot dat eerste proces is afgesloten en markeert de unit pas daarna als gestart. Het achtergebleven child-proces is de daemon. Die gewoonte stamt uit het SysV-tijdperk, toen niets een daemon controleerde zodra het init-script was voltooid en een PID-bestand de enige registratie was van wat er draaide; een beperking die de kern vormt van waarom systemd init-scripts verving.

Het probleem is de identiteit. Het proces dat systemd startte is verdwenen, dus moet systemd uitzoeken welke overlevende het hoofdproces is. Stel PIDFile= in op het bestand dat de daemon schrijft, meestal een pad onder /run, en systemd leest de PID daaruit. systemd controleert ook of de PID in dat bestand verwijst naar een proces dat al bij deze service hoort, zodat een verouderd bestand dat naar een ongerelateerd proces wijst, wordt geweigerd in plaats van vertrouwd.

Zonder PIDFile= is GuessMainPID= van toepassing, en de standaardwaarde daarvan is yes. Het gokken is alleen betrouwbaar wanneer de service uit één enkel proces bestaat. De handleiding stelt de beperking duidelijk: als de daemon uit meer dan één proces bestaat, kan de gok onjuist zijn en stopt de foutdetectie met werken. Een unit kan ook eindigen met een hoofd-PID van 0, wat betekent dat systemd helemaal niets heeft om te controleren.

De meeste daemons die forken hebben ook een schakelaar om ze op de voorgrond te houden. Gebruik die schakelaar met Type=exec en verwijder de regel PIDFile=. Minder bewegende delen betekent minder manieren om de PID te verliezen.

Type=oneshot voor taken die eindigen

Type=oneshot verwacht dat het proces wordt uitgevoerd en wordt afgesloten. systemd markeert de unit pas als gestart nadat deze is afgesloten, wat oneshot de juiste vorm maakt voor alles waar een andere unit op moet wachten. Dit is ook de impliciete standaard wanneer een unit noch Type=, noch ExecStart= specificeert.

Twee gedragingen zijn specifiek voor oneshot. Het is het enige type dat meer dan één ExecStart=-regel accepteert, en deze regels worden in volgorde uitgevoerd. De start-timeout is standaard ook uitgeschakeld, dus een oneshot die blijft hangen wacht oneindig, tenzij u zelf TimeoutStartSec= instelt.

Nadat het proces is afgesloten, keert de unit terug naar de status inactief. RemainAfterExit=yes houdt deze active zonder dat er een proces draait. Dat is de bewuste versie van het symptoom bovenaan deze pagina, en het is correct wanneer de taak van de unit was om een status achter te laten in plaats van iets draaiende te houden: het laden van een firewall-ruleset of het opstarten van een container-stack. Het is het patroon achter een Docker Compose-stack die terugkeert na een reboot, waarbij de unit het compose-commando uitvoert, afsluit en actief blijft omdat de containers die het startte langer blijven bestaan dan de unit zelf. Een oneshot-unit is ook wat een planning activeert, wat de andere helft is van een taak uitvoeren op een systemd-timer in plaats van cron.

Type=notify laat de service aangeven wanneer deze gereed is

Type=notify verlegt de beslissing naar de service. systemd houdt de starttaak open totdat het proces READY=1 verstuurt via een Unix-socket, waarvan het pad wordt ontvangen in de omgevingsvariabele NOTIFY_SOCKET. De C-interface is sd_notify(3) en veel servers ondersteunen deze reeds.

Dit is het nauwkeurige antwoord op de vraag "is het gestart". simple en exec rapporteren dat de service is gestart voordat deze de configuratie heeft gelezen of de luisterende socket heeft geopend, waardoor een afhankelijke unit te vroeg kan starten en de eerste verbinding kan mislukken. notify rapporteert dat de service is gestart op het moment dat de service zelf aangeeft gereed te zijn.

systemd accepteert dat bericht alleen van het hoofdproces, wat NotifyAccess=main betekent, en Type=notify impliceert dit. Als het bericht afkomstig is van een kindproces of een helper, stel dan NotifyAccess=all in. Een shellscript kan systemd-notify --ready aanroepen, maar dat draait als een afzonderlijk, kortstondig proces. Daarom is NotifyAccess=all nodig en kan systemd mogelijk geen bericht toeschrijven aan een afzender die al is afgesloten. Een service die het protocol zelf ondersteunt, is betrouwbaarder.

Twee gerelateerde instellingen zijn het vermelden waard. Type=notify-reload, beschikbaar sinds systemd 253, breidt dezelfde handshake uit naar herlaadacties, zodat systemctl reload pas terugkeert wanneer de service meldt dat het herladen is voltooid, in plaats van direct na het verzenden van het signaal. WatchdogSec= vraagt een notificerende service om met een bepaald interval een keep-alive-bericht te sturen, waarbij systemd een gemiste deadline als een fout beschouwt.

Type=dbus en Type=idle

Type=dbus wacht totdat de service een naam claimt op D-Bus, de message bus die systeem- en desktopservices gebruiken om met elkaar te communiceren. Dit vereist BusName= en wordt de standaard zodra BusName= is ingesteld. Gebruik dit alleen voor een service die daadwerkelijk een busnaam registreert.

Type=idle gedraagt zich als simple, maar stelt het uitvoeren van het programma uit totdat taken in de wachtrij zijn afgehandeld, met een limiet van vijf seconden. Dit is bedoeld om te voorkomen dat console-uitvoer tijdens het opstarten vermengd raakt met statusberichten. Het is geen tool voor het bepalen van de volgorde en hoort niet thuis bij een standaard service.

Waarom een wrapper-script systemd op het verkeerde PID laat vastlopen

Dit is de structuur die het oorspronkelijke symptoom veroorzaakt.

[Service]
Type=simple
ExecStart=/opt/app/run.sh
#!/bin/bash
export APP_ENV=production
/opt/app/bin/server --config /etc/app.yaml &
/opt/app/bin/exporter --port 9101

systemd registreert de shell als het hoofd-PID. De shell blijft actief terwijl exporter op de voorgrond draait. Als server crasht, merkt de shell dit niet op. Het hoofd-PID blijft dus actief, de unit is nog steeds active en Restart= heeft geen actiepunt. Beide processen bevinden zich de gehele tijd in de cgroup van de unit, waardoor systemctl stop de opruimacties nog steeds correct uitvoert. Het toezicht is verbroken, niet de opschoning.

De oplossing hangt af van het aantal langlopende processen in de unit.

Als er één proces is, vervang de shell dan door dat proces.

#!/bin/bash
export APP_ENV=production
exec /opt/app/bin/server --config /etc/app.yaml

exec vervangt de shell door het genoemde programma en behoudt hetzelfde PID. Het PID dat systemd heeft geregistreerd, behoort nu dus tot de daemon. Nog beter is het om de wrapper te verwijderen. Environment= en EnvironmentFile= dragen de variabelen over en ExecStartPre= voert de configuratiestap uit, zodat systemd de daemon direct kan starten en het PID bij constructie kent.

Als er twee processen zijn, vertegenwoordigt geen enkel PID de unit. Splits ze in twee units en orden ze met After= en Wants=. Eén unit per proces is de configuratie die systemd goed kan bewaken, en het is de enige manier waarop elk proces zijn eigen herstartgedrag krijgt.

Wat wijzigt ExitType=cgroup

ExitType= werd toegevoegd in systemd 250. De standaardwaarde is main: de unit wordt als gestopt beschouwd zodra het hoofdproces eindigt. Met ExitType=cgroup wordt de unit als actief beschouwd zolang er een proces in de bijbehorende cgroup actief is.

[Service]
Type=simple
ExitType=cgroup
ExecStart=/opt/app/launcher

Dit lost een specifiek probleem op. Een launcher die het eigenlijke werk start en vervolgens afsluit, zou onder ExitType=main door systemd als gestopt worden beschouwd, waarna de overgebleven processen worden beëindigd. Met ExitType=cgroup volgt de unit de gehele groep.

Wees duidelijk over wat dit niet oplost. ExitType=cgroup houdt een unit actief zolang er ten minste één proces draait; een unit die twee daemons beheert, blijft dus actief nadat er één is gestopt. Het lost het scenario met de launcher op. Het maakt van één unit geen supervisor voor meerdere onafhankelijke processen. ExitType= kan bovendien niet worden gecombineerd met Type=oneshot.

De cgroup is ook de plek waar resource-accounting plaatsvindt, dus limieten zoals MemoryMax= en CPUQuota= zijn van toepassing op elk proces dat door de unit is gestart, ongeacht wat Type= zegt over de main PID. Dat aspect wordt behandeld in het beperken van het geheugen- en CPU-gebruik van een service met systemd.

Hoe u het proces vindt dat systemd daadwerkelijk monitort

Doorloop deze stappen op de unit die u aan het debuggen bent, in de juiste volgorde. Lees wat systemd heeft geladen, bekijk wat het volgt en vergelijk dit vervolgens met de procestabel.

systemctl cat app.service
systemctl show -p Type,ExitType,GuessMainPID,PIDFile,MainPID,RemainAfterExit app.service

systemctl cat toont het unit-bestand samen met elke drop-in die erop van toepassing is, zodat u leest wat systemd daadwerkelijk heeft geladen in plaats van het bestand waarvan u denkt dat u het heeft bewerkt. systemctl show toont de effectieve waarden, inclusief de standaardinstellingen die u niet zelf heeft gedefinieerd. Noteer de waarde van MainPID voordat u verdergaat.

systemd-cgls --unit=app.service
ps -o pid,ppid,stat,etime,args -p "$(systemctl show -p MainPID --value app.service)"

systemd-cgls somt elk proces in de cgroup van de unit op. De regel ps beschrijft het specifieke proces dat systemd beheert. Lees deze twee samen. Een MainPID van 0 betekent dat systemd geen proces heeft om te monitoren. Een MainPID die verwijst naar een shell terwijl de cgroup ook uw daemon bevat, duidt op het eerder genoemde wrapper-scenario. Een cgroup met meer processen dan verwacht betekent dat er een launcher of een forking daemon bij betrokken is.

systemctl status app.service
journalctl -u app.service -b

systemctl status toont de statusregel en de cgroup-boom samen, waardoor deze vaak beide vragen tegelijk beantwoordt. journalctl -u, beperkt tot de huidige boot met -b, toont de start- en stopgebeurtenissen die systemd voor de unit heeft geregistreerd, inclusief de waargenomen exitcodes. Als de daemon naar een eigen logbestand schrijft in plaats van naar de journal, lees dan ook dat bestand, aangezien systemd alleen kan registreren wat het bereikt.

Wanneer u Type= wijzigt, moet u herladen en herstarten.

systemd-analyze verify /etc/systemd/system/app.service
sudo systemctl daemon-reload
sudo systemctl restart app.service

systemd-analyze verify parseert het bestand en rapporteert instellingen die niet geaccepteerd kunnen worden. daemon-reload zorgt ervoor dat systemd de unit-bestanden opnieuw inleest vanaf de schijf. Een gewijzigde Type= is niet van toepassing op een unit die al draait, dus de herstart is vereist, niet optioneel.

Test vervolgens de wijziging. Neem het PID van het proces waar het u om gaat uit systemd-cgls en beëindig dit proces. Voer direct daarna systemctl is-active app.service uit. Als Type= correct is, verlaat de unit de actieve status. Als deze actief blijft, monitort systemd nog steeds iets anders.

Welk systemd service Type= moet u gebruiken

  • Een programma dat op de voorgrond blijft draaien: Type=exec.
  • Een programma dat ondersteuning biedt voor readiness-notificaties: Type=notify, en notify-reload als het ook herlaadacties bevestigt.
  • Een daemon die zichzelf naar de achtergrond verplaatst: Type=forking met PIDFile=, of de foreground-schakelaar van de applicatie zelf met Type=exec.
  • Een script dat een taak uitvoert en daarna afsluit: Type=oneshot, plus RemainAfterExit=yes wanneer het doel is om een status achter te laten.
  • Een launcher die afsluit terwijl de onderliggende processen blijven draaien: Type=simple met ExitType=cgroup.

Als u niet zeker weet welk type een daemon van derden vereist, lees dan eerst het meegeleverde unit-bestand. Het uitvoeren van systemctl cat op een unit die door de distributie is meegeleverd, toont het Type= dat door de ontwikkelaars is gekozen; deze keuze is door meer gebruikers getest dan uw eigen configuratie.

FAQ

Waarom blijft mijn systemd-unit actief als het proces is beëindigd?

Omdat het proces dat systemd als hoofdproces beschouwt, nog steeds actief is. systemd controleert één PID per service, gekozen op basis van Type=, in plaats van elk proces in de cgroup van de unit. Een wrapper-script dat wordt gestart met Type=simple is hiervan meestal de oorzaak: de shell is het hoofd-PID, waardoor de unit actief blijft wanneer een daemon die door de shell op de achtergrond is gestart, wordt afgesloten. Voer systemctl show -p MainPID app.service uit, vermeld vervolgens de cgroup van de unit met systemd-cgls --unit=app.service en vergelijk beide.

Wat is het verschil tussen Type=simple en Type=exec?

Type=simple beschouwt de unit als gestart zodra systemd het proces heeft aangemaakt, nog voordat het binaire bestand is uitgevoerd. Een onjuist pad in ExecStart= resulteert daarom in een succesvolle starttaak, gevolgd door een foutmelding. Type=exec wacht tot de uitvoering is geslaagd, zodat een fout direct door de starttaak zelf wordt gerapporteerd. Beide behandelen hetzelfde proces als het hoofd-PID. Type=exec vereist systemd 240 of nieuwer.

Heb ik nog steeds PIDFile= nodig bij Type=forking?

Ja, wanneer de daemon er een schrijft. Zonder dit bestand valt systemd terug op GuessMainPID=, wat een schatting is en alleen betrouwbaar is voor een service die in één enkel proces draait. Wanneer de schatting onjuist of onmogelijk is, werken foutdetectie en automatisch herstarten niet meer voor die unit. Wijs PIDFile= naar het exacte pad waar de daemon naar schrijft, normaal gesproken onder /run.

Wanneer moet ik RemainAfterExit=yes gebruiken?

Wanneer het doel van de unit was om de systeemstatus te wijzigen in plaats van een proces draaiende te houden. Een Type=oneshot-unit die firewallregels laadt of een containerstack start, wordt afgesloten zodra het werk is voltooid. Zonder RemainAfterExit=yes wordt de unit inactief, waardoor systemctl stop niets meer heeft om te stoppen en er geen manier is om een ExecStop=-opschoonactie uit te voeren. Hiermee blijft de unit actief zonder processen, en dat is hier de bedoeling.

Vereist het wijzigen van Type= een daemon-reload?

Ja, en ook een herstart van de unit. systemctl daemon-reload zorgt ervoor dat systemd de unit-bestanden op de schijf opnieuw inleest, maar een draaiende instantie behoudt de Type= waarmee deze is gestart. Voer sudo systemctl daemon-reload uit en vervolgens sudo systemctl restart app.service voordat u gaat testen; anders bekijkt u nog steeds het oude supervisiegedrag.