SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-25

systemd Type=: simple, forking und notify richtig wählen

Die Unit ist aktiv, obwohl der Daemon beendet ist? Erfahren Sie, wann simple, exec, forking, oneshot und notify die falsche Main-PID überwachen.

Warum meldet systemd eine Unit als aktiv, obwohl der Prozess beendet wurde

Eine systemd-Service-Unit bleibt active, solange der eine Prozess, den systemd als Hauptprozess betrachtet, noch läuft. Type= im Abschnitt [Service] legt fest, um welchen Prozess es sich handelt. Wenn Sie den falschen Wert wählen, überwacht systemd möglicherweise einen Shell-Wrapper oder einen kurzlebigen übergeordneten Prozess, während der eigentliche Daemon innerhalb derselben Unit beendet wird. Die Unit beschreibt den Zustand des Prozesses, den systemd überwachen soll.

Eine Änderung der Restart-Richtlinie hilft in diesem Fall nicht. Restart= wird aktiv, wenn der Hauptprozess beendet wird. Daher wird Restart=always nicht ausgelöst, solange die Main-PID (Prozess-ID) zu einem noch laufenden Prozess gehört. Korrigieren Sie zuerst Type=. Was systemd nach dem tatsächlichen Beenden des Hauptprozesses tut, ist eine separate Entscheidung. Sie wird im Leitfaden zu Restart= und RestartSec= behandelt.

Was entscheidet Type= tatsächlich

Jeder Type=-Wert beantwortet gleichzeitig zwei Fragen: Ab wann darf systemd diese Unit als gestartet betrachten, und welcher Prozess ist der Hauptprozess?

Die erste Antwort steuert die Reihenfolge. Eine Unit, die Ihre Unit in After= aufführt, wartet, bis systemd Ihre Unit als gestartet meldet. Ein Type=, das zu früh „started“ meldet, lässt abhängige Units starten, bevor Ihr Dienst Anfragen beantworten kann.

Die zweite Antwort steuert die Überwachung. systemd ordnet jeden von einer Unit gestarteten Prozess einer cgroup (control group) zu. Dabei handelt es sich um eine Kernel-Funktion, die Prozesse gruppiert, damit sie gemeinsam begrenzt und beendet werden können. Über die cgroup führt systemctl stop die Bereinigung durch: KillMode= ist standardmäßig auf control-group gesetzt, sodass beim Stoppen einer Unit jeder Prozess innerhalb der cgroup ein Signal erhält. Die Haupt-PID ist enger definiert. Sie bezeichnet den einzelnen Prozess, dessen Beendigung die Unit beendet und dessen Exit-Status zum Ergebnis der Unit wird. Die Verwirrung beginnt, wenn die cgroup so behandelt wird, als wäre sie die Haupt-PID.

Type=simple meldet den Start, bevor die Binärdatei ausgeführt wird

Type=simple ist der Standard, wenn ExecStart= gesetzt ist und weder Type= noch BusName= vorhanden ist. systemd erstellt den Prozess, betrachtet die Unit sofort als gestartet und behandelt diesen Prozess als Haupt-PID. Nachgelagerte Units beginnen sofort, noch bevor die Dienst-Binärdatei ausgeführt wurde.

Dieses letzte Detail erklärt eine häufige Überraschung. Ein Tippfehler im Pfad ExecStart= führt trotzdem zu einem erfolgreichen Startjob. Der Fehler tritt erst einen Moment später auf, wenn die Ausführung fehlschlägt. systemd protokolliert diesen Fall mit dem Exit-Code 203. In der eigenen Tabelle nennt systemd ihn EXEC und definiert ihn als Fehlschlagen bei der Ausführung der Dienst-Binärdatei. Wenn systemctl start also ohne Fehler zurückkehrt, ist damit nicht nachgewiesen, dass Ihre Binärdatei vorhanden ist.

Verwenden Sie simple für ein Programm, das im Vordergrund bleibt und sich nie selbst in den Hintergrund verschiebt. Das gilt für die meisten modernen Daemons und für praktisch alles, was Sie selbst schreiben.

Type=exec wartet, bis das Programm tatsächlich gestartet wurde

Type=exec ist simple mit einem zusätzlichen Schritt. systemd betrachtet die Unit erst dann als gestartet, wenn sowohl der Fork als auch die Ausführung der Binärdatei erfolgreich waren. Eine fehlende Binärdatei oder ein User=, das nicht aufgelöst werden kann, lässt den Startauftrag nun selbst fehlschlagen. Zuvor wurde ein Erfolg gemeldet, obwohl der Dienst kurz darauf still fehlschlug.

Type=exec wurde in systemd 240 eingeführt und ist daher in jeder aktuellen Serverdistribution verfügbar. Ubuntu 24.04 enthält systemd 255, Debian 13 enthält systemd 257, Stand August 2026. Prüfen Sie Ihre Version mit systemctl --version.

Beim Start ist dafür ein zusätzlicher Synchronisierungsschritt erforderlich. Dafür liefert systemctl start einen korrekten Exit-Status. Für ein Programm im Vordergrund sollten Sie exec gegenüber simple bevorzugen.

Type=forking und wie die Haupt-PID verloren geht

Type=forking teilt systemd mit, dass der Prozess in ExecStart= einen Kindprozess erzeugt und anschließend absichtlich beendet wird. systemd wartet, bis dieser erste Prozess beendet ist, und betrachtet die Unit erst danach als gestartet. Der zurückbleibende Kindprozess ist der Daemon. Dieses Verhalten stammt aus der SysV-Zeit. Damals überwachte nichts einen Daemon, sobald das Init-Skript zurückgekehrt war. Eine PID-Datei war der einzige Nachweis dafür, welcher Prozess ausgeführt wurde. Diese Einschränkung gehört zu den zentralen Gründen warum systemd Init-Skripte ersetzt hat.

Die Schwierigkeit besteht in der Identität des Prozesses. Der von systemd gestartete Prozess ist beendet. systemd muss daher ermitteln, welcher der verbleibenden Prozesse der Hauptprozess ist. Setzen Sie PIDFile= auf die Datei, in die der Daemon seine PID schreibt, normalerweise auf einen Pfad unter /run. systemd liest die PID aus dieser Datei. systemd prüft außerdem, ob die PID in dieser Datei zu einem Prozess gehört, der bereits diesem Dienst zugeordnet ist. Eine veraltete Datei, die einen unabhängigen Prozess angibt, wird daher abgelehnt und nicht als vertrauenswürdig behandelt.

Ohne PIDFile= gilt GuessMainPID=. Der Standardwert ist yes. Die Zuordnung ist nur zuverlässig, wenn der Dienst in einem einzelnen Prozess verbleibt. Das Handbuch beschreibt die Einschränkung eindeutig: Besteht der Daemon aus mehreren Prozessen, kann die Zuordnung falsch sein. Die Erkennung von Fehlern funktioniert dann nicht mehr. Eine Unit kann außerdem eine Haupt-PID von 0 erhalten. Das bedeutet, dass systemd überhaupt keinen Prozess überwachen kann.

Die meisten Daemons, die sich forken, verfügen auch über eine Option, mit der sie im Vordergrund bleiben. Verwenden Sie diese Option mit Type=exec und entfernen Sie die Zeile PIDFile=. Weniger beteiligte Komponenten bedeuten weniger Möglichkeiten, die PID zu verlieren.

Type=oneshot für Arbeiten, die abgeschlossen werden

Type=oneshot erwartet, dass der Prozess ausgeführt wird und beendet wird. systemd betrachtet die Unit erst nach dem Beenden als gestartet. Damit ist oneshot die passende Form für alles, worauf eine andere Unit warten muss. Dieser Typ ist außerdem der implizite Standard, wenn eine Unit weder Type= noch ExecStart= angibt.

Für oneshot gelten zwei besondere Verhaltensweisen. Nur dieser Typ akzeptiert mehr als eine ExecStart=-Zeile, und diese Zeilen werden der Reihe nach ausgeführt. Außerdem ist das Start-Timeout standardmäßig deaktiviert. Ein oneshot, der hängen bleibt, wartet daher unbegrenzt, sofern Sie TimeoutStartSec= nicht selbst festlegen.

Nach dem Beenden des Prozesses wechselt die Unit zurück in den Zustand inaktiv. RemainAfterExit=yes hält sie ohne laufenden Prozess im Zustand active. Das ist die beabsichtigte Variante des Symptoms am Anfang dieser Seite. Sie ist korrekt, wenn die Aufgabe der Unit darin bestand, einen Zustand zu hinterlassen, statt einen laufenden Dienst aufrechtzuerhalten: etwa beim Laden eines Firewall-Regelsatzes oder beim Starten eines Container-Stacks. Dieses Muster liegt auch einem Docker-Compose-Stack zugrunde, der nach einem Reboot wieder gestartet wird. Dabei führt die Unit den Compose-Befehl aus, beendet sich und bleibt aktiv, weil die gestarteten Container länger als die Unit laufen. Eine oneshot-Unit wird außerdem durch einen Zeitplan ausgelöst. Das ist die andere Möglichkeit von einen Job mit einem systemd-Timer statt mit cron auszuführen.

Type=notify meldet die Bereitschaft des Dienstes

Type=notify verlagert die Entscheidung auf den Dienst. systemd hält den Startauftrag offen, bis der Prozess READY=1 über einen Unix-Socket sendet. Den Pfad zu diesem Socket erhält der Prozess über die Umgebungsvariable NOTIFY_SOCKET. Die C-Schnittstelle ist sd_notify(3), und viele Server unterstützen sie bereits.

Dies ist die genaue Antwort auf die Frage „ist der Dienst gestartet?“. simple und exec melden den Start, bevor der Dienst seine Konfiguration gelesen oder seinen Listening-Socket geöffnet hat. Eine abhängige Unit kann dadurch zu früh starten und bei ihrer ersten Verbindung fehlschlagen. notify meldet den Start genau dann, wenn der Dienst selbst seine Bereitschaft meldet.

systemd akzeptiert diese Nachricht nur vom Hauptprozess. Das bedeutet NotifyAccess=main, und Type=notify setzt dies voraus. Kommt die Nachricht von einem Kindprozess oder einem Hilfsprozess, setzen Sie NotifyAccess=all. Ein Shell-Skript kann systemd-notify --ready aufrufen. Dieser Aufruf läuft jedoch als separater, kurzlebiger Prozess. Deshalb benötigt er NotifyAccess=all. Außerdem kann systemd eine Nachricht möglicherweise nicht mehr zuordnen, wenn der Absender bereits beendet wurde. Ein Dienst, der das Protokoll selbst implementiert, ist zuverlässiger.

Zwei verwandte Einstellungen sollten Sie kennen. Type=notify-reload ist seit systemd 253 verfügbar und erweitert denselben Handshake auf Reloads. Dadurch kehrt systemctl reload zurück, wenn der Dienst meldet, dass der Reload abgeschlossen ist, und nicht bereits beim Senden des Signals. WatchdogSec= fordert einen Dienst mit Benachrichtigungsunterstützung auf, in einem bestimmten Intervall eine Keep-Alive-Nachricht zu senden. systemd wertet das Überschreiten einer Frist als Fehler.

Type=dbus und Type=idle

Type=dbus wartet, bis der Dienst einen Namen auf D-Bus übernimmt. D-Bus ist der Nachrichtenbus, über den System- und Desktop-Dienste miteinander kommunizieren. Dafür ist BusName= erforderlich. Type=dbus wird zum Standard, sobald BusName= gesetzt ist. Verwenden Sie Type=dbus nur für einen Dienst, der tatsächlich einen Busnamen registriert.

Type=idle verhält sich wie simple, verzögert die Ausführung des Programms jedoch, bis ausstehende Aufträge verteilt wurden. Dafür gilt eine Obergrenze von fünf Sekunden. Dieser Typ verhindert, dass sich Konsolenausgaben beim Booten mit Statusmeldungen überschneiden. Er dient nicht zur Festlegung der Startreihenfolge und gehört nicht zu einem normalen Dienst.

Warum ein Wrapper-Skript systemd an der falschen PID festhält

Dieses Muster erzeugt das ursprüngliche Symptom.

[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 erfasst die Shell als Haupt-PID. Die Shell bleibt aktiv, während exporter im Vordergrund läuft. Wenn server beendet wird, bemerkt die Shell das nicht. Die Haupt-PID ist daher weiterhin aktiv, die Unit befindet sich weiterhin im Zustand active, und Restart= hat nichts, worauf es reagieren kann. Beide Prozesse befinden sich die ganze Zeit in der cgroup der Unit. Deshalb bereinigt systemctl stop weiterhin korrekt. Die Überwachung ist fehlgeschlagen, nicht die Bereinigung.

Die Lösung hängt davon ab, wie viele langfristig laufende Prozesse die Unit tatsächlich enthält.

Wenn es nur einen gibt, ersetzen Sie die Shell durch diesen Prozess.

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

exec ersetzt die Shell durch das angegebene Programm und behält dieselbe PID bei. Die von systemd erfasste PID gehört dadurch nun zum Daemon. Noch besser ist es, den Wrapper zu entfernen. Environment= und EnvironmentFile= übergeben die Variablen, und ExecStartPre= führt den Einrichtungsschritt aus. Dadurch kann systemd den Daemon direkt starten und seine PID von Anfang an kennen.

Wenn es zwei sind, repräsentiert keine einzelne PID die Unit. Teilen Sie sie in zwei Units auf und ordnen Sie sie mit After= und Wants=. Eine Unit pro Prozess ist die Struktur, die systemd zuverlässig überwachen kann. Nur so erhält jeder Prozess sein eigenes Neustartverhalten.

Welche Änderungen ExitType=cgroup bewirkt

ExitType= wurde in systemd 250 hinzugefügt. Der Standardwert ist main: Die Unit gilt als beendet, sobald der Hauptprozess beendet wird. Mit ExitType=cgroup gilt die Unit so lange als aktiv, wie ein beliebiger Prozess in ihrer cgroup läuft.

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

Damit wird ein bestimmtes Problem gelöst. Ein Launcher, der die eigentliche Arbeit startet und sich anschließend beendet, würde unter ExitType=main dazu führen, dass systemd die Unit als beendet betrachtet und die verbleibenden Prozesse beendet. Mit ExitType=cgroup folgt die Unit stattdessen der gesamten Prozessgruppe.

Beachten Sie, was dadurch nicht gelöst wird. ExitType=cgroup hält eine Unit aktiv, solange mindestens ein Prozess läuft. Eine Unit mit zwei Daemons bleibt daher aktiv, nachdem einer der beiden beendet wurde. Der Fall mit dem Launcher ist damit gelöst. Die Unit wird dadurch jedoch nicht zu einem Supervisor für mehrere unabhängige Prozesse. ExitType= kann außerdem nicht mit Type=oneshot kombiniert werden.

Auch die Ressourcenabrechnung erfolgt über die cgroup. Daher gelten Einschränkungen wie MemoryMax= und CPUQuota= für jeden von der Unit gestarteten Prozess, unabhängig davon, was Type= über die Haupt-PID festlegt. Dieser Aspekt wird unter Speicher- und CPU-Nutzung eines Dienstes mit systemd begrenzen behandelt.

So ermitteln Sie den Prozess, den systemd tatsächlich überwacht

Führen Sie diese Schritte der Reihe nach für die Unit aus, die Sie debuggen. Lesen Sie zuerst, was systemd geladen hat. Lesen Sie danach, welche Prozesse systemd verfolgt. Vergleichen Sie das anschließend mit der Prozesstabelle.

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

systemctl cat gibt die Unit-Datei zusammen mit allen darauf angewendeten Drop-ins aus. Sie lesen damit die Konfiguration, die systemd geladen hat, und nicht die Datei, an deren Bearbeitung Sie sich erinnern. systemctl show gibt die effektiven Werte einschließlich der Standardwerte aus, die Sie nicht explizit festgelegt haben. Notieren Sie den Wert von MainPID, bevor Sie fortfahren.

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

systemd-cgls listet alle Prozesse in der cgroup der Unit auf. Die Zeile ps beschreibt den einzelnen Prozess, den systemd überwacht. Lesen Sie beide Ausgaben zusammen. Ein MainPID von 0 bedeutet, dass systemd keinen Prozess überwachen kann. Ein MainPID, der auf eine Shell verweist, während sich in der cgroup auch Ihr Daemon befindet, entspricht dem oben beschriebenen Wrapper-Fall. Enthält eine cgroup mehr Prozesse als erwartet, ist ein Launcher oder ein forking Daemon beteiligt.

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

systemctl status gibt die Statuszeile und den cgroup-Baum zusammen aus. Damit lassen sich häufig beide Fragen auf einmal beantworten. journalctl -u, mit -b auf den aktuellen Boot begrenzt, zeigt die von systemd aufgezeichneten Start- und Stop-Ereignisse der Unit einschließlich der beobachteten Exit-Codes. Wenn der Daemon in eine eigene Logdatei statt in das Journal schreibt, lesen Sie auch diese Datei. systemd kann nur Daten aufzeichnen, die es erreicht haben.

Wenn Sie Type= ändern, führen Sie einen Reload und anschließend einen Neustart durch.

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

systemd-analyze verify analysiert die Datei und meldet Einstellungen, die systemd nicht akzeptieren kann. daemon-reload veranlasst systemd, die Unit-Dateien erneut von der Festplatte einzulesen. Ein geändertes Type= gilt nicht für eine bereits laufende Unit. Der Neustart ist daher erforderlich.

Testen Sie anschließend die Änderung. Ermitteln Sie die PID des tatsächlich relevanten Prozesses mit systemd-cgls und beenden Sie diesen Prozess. Führen Sie unmittelbar danach systemctl is-active app.service aus. Wenn Type= korrekt ist, verlässt die Unit den aktiven Zustand. Bleibt sie aktiv, überwacht systemd weiterhin einen anderen Prozess.

Welchen systemd-Diensttyp sollten Sie verwenden?

  • Ein Programm, das im Vordergrund bleibt: Type=exec.
  • Ein Programm mit Bereitschaftsbenachrichtigung: Type=notify, zusätzlich notify-reload, wenn es auch das Neuladen bestätigt.
  • Ein Daemon, der unbedingt in den Hintergrund wechseln muss: Type=forking mit PIDFile= oder seine Option für den Vordergrundbetrieb mit Type=exec.
  • Ein Skript, das eine Aufgabe ausführt und beendet wird: Type=oneshot, zusätzlich RemainAfterExit=yes, wenn dabei ein Zustand erhalten bleiben soll.
  • Ein Launcher, der beendet wird, während seine untergeordneten Prozesse weiterlaufen: Type=simple mit ExitType=cgroup.

Wenn Sie unsicher sind, welchen Typ ein Daemon eines Drittanbieters benötigt, lesen Sie zuerst die mitgelieferte Unit-Datei. systemctl cat für eine von der Distribution bereitgestellte Unit zeigt den von Type= gewählten Typ. Diese Auswahl wurde von mehr Personen getestet als Ihre eigene.

FAQ

Warum bleibt meine systemd-Unit aktiv, wenn der Prozess beendet wurde?

Weil der Prozess, den systemd als Hauptprozess behandelt, noch läuft. systemd überwacht pro Dienst eine einzelne PID, die anhand von Type= ausgewählt wird, und nicht jeden Prozess in der cgroup der Unit. Ein mit Type=simple gestartetes Wrapper-Skript ist die häufigste Ursache: Die Shell ist die Haupt-PID. Deshalb bleibt die Unit aktiv, wenn ein von der Shell im Hintergrund gestarteter Daemon beendet wird. Führen Sie systemctl show -p MainPID app.service aus. Listen Sie anschließend die cgroup der Unit mit systemd-cgls --unit=app.service auf und vergleichen Sie beide Angaben.

Was ist der Unterschied zwischen Type=simple und Type=exec?

Type=simple betrachtet die Unit als gestartet, sobald systemd den Prozess erstellt hat, noch bevor die Binärdatei ausgeführt wurde. Daher führt ein falscher Pfad in ExecStart= zunächst zu einem erfolgreichen Startauftrag, gefolgt von einem Fehler. Type=exec wartet, bis die Ausführung erfolgreich war. Der Fehler wird dadurch direkt vom Startauftrag gemeldet. Beide behandeln denselben Prozess als Haupt-PID. Type=exec erfordert systemd 240 oder neuer.

Benötige ich mit Type=forking weiterhin PIDFile=?

Ja, sofern der Daemon eine solche Datei schreibt. Ohne diese Datei verwendet systemd ersatzweise GuessMainPID=. Das ist eine Schätzung und nur bei einem Dienst zuverlässig, der sich auf einen einzelnen Prozess einpendelt. Ist die Schätzung falsch oder nicht möglich, funktionieren die Fehlererkennung und der automatische Neustart für diese Unit nicht mehr. Setzen Sie PIDFile= auf den exakten Pfad, in den der Daemon schreibt, normalerweise unter /run.

Wann sollte ich RemainAfterExit=yes verwenden?

Wenn die Aufgabe der Unit darin bestand, den Systemzustand zu ändern, statt einen Prozess am Laufen zu halten. Eine Type=oneshot-Unit, die Firewall-Regeln lädt oder einen Container-Stack startet, wird beendet, sobald ihre Arbeit abgeschlossen ist. Ohne RemainAfterExit=yes wird die Unit inaktiv. Dadurch hat systemctl stop nichts zu stoppen und keine Möglichkeit, eine ExecStop=-Bereinigung auszuführen. Mit dieser Einstellung bleibt die Unit ohne Prozesse aktiv. Das ist in diesem Fall beabsichtigt.

Benötigt eine Änderung von Type= einen daemon-reload?

Ja. Zusätzlich muss die Unit neu gestartet werden. systemctl daemon-reload veranlasst systemd, die Unit-Dateien auf der Festplatte erneut einzulesen. Eine laufende Instanz verwendet jedoch weiterhin die Type=, mit der sie gestartet wurde. Führen Sie vor dem Testen sudo systemctl daemon-reload und anschließend sudo systemctl restart app.service aus. Andernfalls beobachten Sie weiterhin das alte Überwachungsverhalten.