Eigenen systemd-Service erstellen und beim Start automatisch ausführen
Unit-Datei Zeile für Zeile: Type, User, WorkingDirectory, Restart und network-online.target. Mit den Fehlermeldungen im Wortlaut und dem Nachweis, dass der Dienst den Reboot wirklich übersteht.
Ein Programm, das nach einem Reboot nicht von allein wiederkommt, ist auf einem Server kein Dienst, sondern ein Risiko. nohup, screen und tmux halten einen Prozess am Leben, solange nichts passiert. Dieser Artikel schreibt eine eigene Unit-Datei Zeile für Zeile, zeigt die Fehlermeldungen im Wortlaut und beschreibt, wie man wieder herauskommt, wenn der Dienst im Startkarussell hängt.
Alle Befehle laufen als root. Geprüft auf Debian 13 (systemd 257), Debian 12 (252), Ubuntu 24.04 LTS (255) und Ubuntu 22.04 LTS (249). Wo sich die vier unterscheiden, steht es dabei.
Was ein systemd-Service leistet, was nohup und screen nicht leisten
Der Unterschied ist nicht Bequemlichkeit, sondern Zuständigkeit. systemd startet den Prozess beim Boot in definierter Reihenfolge, legt ihn in eine eigene cgroup, sammelt stdout und stderr im Journal, startet nach einem Absturz neu und beendet ihn beim Shutdown sauber mit SIGTERM. Die cgroup vermisst man am schmerzhaftesten: ein per nohup gestartetes Skript hinterlässt verwaiste Kindprozesse, eine Unit räumt ihre Gruppe komplett ab.
Vorbereitung: Programm, Benutzer und Verzeichnis
Bevor die Unit entsteht, muss laufen, was sie starten soll. Als Beispiel dient ein Skript, das auf stdout schreibt. Genau das ist der Punkt: ein Dienst unter systemd loggt nicht selbst in eine Datei, sondern schreibt auf stdout, und systemd legt es ins Journal.
mkdir -p /opt/kh-demo
cat > /opt/kh-demo/run.sh <<'EOF'
#!/bin/bash
set -euo pipefail
while true; do
echo "kh-demo lebt, $(date --iso-8601=seconds), PWD=$PWD, GREETING=${GREETING:-nicht gesetzt}"
sleep 10
done
EOF
chmod +x /opt/kh-demo/run.sh
Dann ein eigener Systembenutzer: --system vergibt eine UID unter 1000, /usr/sbin/nologin verhindert interaktive Logins.
useradd --system --no-create-home --home-dir /opt/kh-demo --shell /usr/sbin/nologin khdemo
id khdemo
chown -R root:khdemo /opt/kh-demo
chmod 750 /opt/kh-demo
Der entscheidende Test vor der Unit-Datei: läuft das Programm unter diesem Benutzer? Wer das überspringt, debuggt später systemd, obwohl das Problem im Programm liegt.
timeout 3 runuser -u khdemo -- /opt/kh-demo/run.sh; echo "Exitcode $?"
Exitcode 124 ist hier das gewünschte Ergebnis: timeout hat ein laufendes Programm abgebrochen. Jeder andere Wert bedeutet, dass das Skript von selbst gestorben ist, und dann liegt der Fehler nicht bei systemd.
Die Unit-Datei Zeile für Zeile
Eigene Units gehören nach /etc/systemd/system/, nicht nach /lib/systemd/system/ oder /usr/lib/systemd/system/: die beiden gehören dem Paketmanager und werden beim nächsten apt upgrade überschrieben.
cat > /etc/systemd/system/kh-demo.service <<'EOF'
[Unit]
Description=KernelHost Demo Worker
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=khdemo
Group=khdemo
WorkingDirectory=/opt/kh-demo
EnvironmentFile=-/etc/kh-demo.env
ExecStart=/opt/kh-demo/run.sh
Restart=on-failure
RestartSec=5s
TimeoutStopSec=20s
SyslogIdentifier=kh-demo
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
[Install]
WantedBy=multi-user.target
EOF
Abschnitt [Unit]
Description= ist der Text in systemctl status. Ohne technische Wirkung, aber er entscheidet darüber, ob man um drei Uhr nachts versteht, was da failed ist. Ein Satz, kein Wort.
After= legt nur die Reihenfolge fest, nicht die Abhängigkeit. After=network-online.target bedeutet: falls dieses Target in diesem Bootvorgang ohnehin gestartet wird, warte darauf. Es bedeutet nicht, dass es gestartet wird.
Wants= fordert es tatsächlich an, und zwar als schwache Abhängigkeit. Scheitert das Target, startet der Dienst trotzdem. Das Gegenstück Requires= würde den Dienst mit in den Fehler reißen. Für die meisten Anwendungen ist Wants= richtig, denn ein Dienst, der wegen eines Netzwerk-Timeouts gar nicht erst startet, ist schlimmer als einer, der kurz ins Leere läuft und dank Restart= wiederkommt.
Abschnitt [Service]
User= und Group= setzen die Identität des Prozesses. Ohne diese Zeilen läuft der Dienst als root. Auf Debian und Ubuntu legt useradd standardmäßig eine gleichnamige Gruppe an, deshalb passt Group=khdemo.
WorkingDirectory= ist das Arbeitsverzeichnis des Prozesses. Ohne diese Angabe startet der Dienst in /. Jedes Programm, das Konfigurationsdateien, Templates oder Plugins relativ zum aktuellen Verzeichnis sucht, geht dann sofort unter. Existiert das angegebene Verzeichnis nicht, endet der Start mit 200/CHDIR.
EnvironmentFile= lädt Variablen aus einer Datei. Das führende Minuszeichen vor dem Pfad macht die Datei optional. Ohne das Minus scheitert der Start, wenn die Datei fehlt. Der Inhalt sind schlichte KEY=VALUE-Zeilen, keine Shell. export davor ist falsch, und PORT=$BASE_PORT wird nicht aufgelöst.
ExecStart= braucht einen absoluten Pfad zur ausführbaren Datei. Das ist die Regel, an der die meisten scheitern, deshalb weiter unten ein eigener Abschnitt.
TimeoutStopSec= begrenzt, wie lange systemd nach dem SIGTERM wartet, bevor SIGKILL kommt. Standard sind 90 Sekunden.
SyslogIdentifier= setzt den Namen, unter dem die Zeilen im Journal auftauchen. Ohne diese Zeile heißt der Absender run.sh, was beim Filtern unbrauchbar ist.
NoNewPrivileges=true verbietet dem Prozess und allen Kindern, über setuid-Binaries Rechte zu gewinnen. PrivateTmp=true gibt dem Dienst ein eigenes /tmp. ProtectSystem=full mountet /usr, /boot und /etc nur lesbar. Die Stufe strict geht weiter und verlangt dann StateDirectory= oder ReadWritePaths= für alles Schreibbare.
Abschnitt [Install]
WantedBy=multi-user.target beantwortet die Frage, wann der Dienst automatisch starten soll. multi-user.target ist der normale Mehrbenutzerbetrieb ohne grafische Oberfläche, also das, was ein Server erreicht. Erst systemctl enable wertet diesen Abschnitt aus und legt den Symlink an. Fehlt [Install], bricht enable ab mit The unit files have no installation config (WantedBy=, RequiredBy=, Also=, Alias= settings in the [Install] section, and DefaultInstance= for template units). Der Dienst lässt sich dann von Hand starten, kommt aber nach einem Reboot nie wieder.
Type=simple, Type=exec und Type=forking
Der Type= beantwortet genau eine Frage: woran erkennt systemd, dass der Dienst gestartet ist?
Bei Type=simple gilt der Dienst als gestartet, sobald der Prozess erzeugt wurde, also nach dem fork() und noch bevor das Programm ausgeführt wird. Das ist der Standard und für fast alle modernen Programme richtig, die im Vordergrund bleiben.
Bei Type=exec wartet systemd zusätzlich, bis execve() erfolgreich war. Der praktische Unterschied ist erheblich: mit Type=simple meldet systemctl start Erfolg, selbst wenn das Binary gar nicht existiert, und der Fehler taucht erst danach im Journal auf. Mit Type=exec schlägt der Start in genau diesem Fall sofort fehl. Verfügbar seit systemd 240 und damit auf allen vier Distributionen.
Bei Type=forking geht systemd davon aus, dass sich das Programm selbst in den Hintergrund verabschiedet: der gestartete Prozess endet, ein Kind läuft weiter, und erst dieses Ende gilt als Startsignal. Das ist klassisches Unix-Daemon-Verhalten. Wer diesen Typ nutzt, braucht in aller Regel zusätzlich PIDFile= mit absolutem Pfad, sonst rät systemd, welcher der übriggebliebenen Prozesse der Hauptprozess ist.
Die Empfehlung ist eindeutig: Type=forking nur, wenn das Programm sich partout nicht davon abbringen lässt. Fast jede Software hat einen Schalter dafür, oft --foreground, -D FOREGROUND, --no-daemon oder daemon off; in der Konfiguration. Im Vordergrund plus Type=simple ist die Unit kürzer, das Logging landet im Journal und Restart= funktioniert zuverlässig.
Die typische Fehlkombination: ein Programm, das sich selbst in den Hintergrund legt, unter Type=simple. systemd sieht den Startprozess enden, hält den Dienst für beendet und killt die Kinder mit. Symptom ist Active: inactive (dead) direkt nach einem systemctl start, das keinen Fehler gemeldet hat. Der umgekehrte Fall, ein Vordergrundprogramm unter Type=forking, lässt systemd auf ein Ende warten, das nie kommt: Job for foo.service failed because a timeout was exceeded, nach 90 Sekunden.
After=network-online.target richtig verdrahten
Hier hört fast jede Anleitung zu früh auf. network.target bedeutet lediglich, dass die Netzwerkverwaltung gestartet wurde, nicht dass eine IP-Adresse konfiguriert ist. Wer eine erreichbare Adresse braucht, etwa weil das Programm an eine feste IP bindet, will network-online.target.
Dieses Target wird aber nicht von selbst erreicht. Es setzt voraus, dass ein passender Wartedienst aktiviert ist, und der unterscheidet sich je nach Netzwerkverwaltung:
systemctl list-unit-files 'systemd-networkd-wait-online.service' 'NetworkManager-wait-online.service' 'ifupdown-wait-online.service' --no-pager
Auf Ubuntu Server 22.04 und 24.04 läuft das Netzwerk über netplan mit systemd-networkd, dort ist systemd-networkd-wait-online.service aktiv und network-online.target hat eine echte Bedeutung. Auf einer klassischen Debian-Installation mit ifupdown existiert ifupdown-wait-online.service zwar, ist aber nicht aktiviert. Das Target gilt dann sofort als erreicht, und die Wartezeit, auf die man sich verlässt, findet nicht statt.
Auf Debian 12 und 13 mit ifupdown aktiviert man den Wartedienst bei Bedarf einmalig:
systemctl enable ifupdown-wait-online.service
Zwei Wartedienste gleichzeitig zu aktivieren ist keine gute Idee, weil dann beide auf ihre jeweilige Netzwerkverwaltung warten und einer zwangsläufig in den 90-Sekunden-Timeout läuft. Das ist die häufigste Ursache für einen Server, der plötzlich anderthalb Minuten länger bootet. Robuster als jede Reihenfolge ist ohnehin ein Programm, das eine fehlende Verbindung beim Start verkraftet, kombiniert mit Restart=on-failure.
Restart, RestartSec und die Startbremse
Restart=on-failure startet neu bei einem Exit-Code ungleich null, bei einem Signal wie SIGSEGV und bei einem Watchdog-Timeout, aber nicht nach Exit 0 und nicht nach einem manuellen systemctl stop. Restart=always startet zusätzlich nach sauberem Ende neu, und damit baut man sich am schnellsten eine Endlosschleife.
Dagegen hat systemd eine eingebaute Bremse: standardmäßig sind fünf Startversuche innerhalb von zehn Sekunden erlaubt (StartLimitBurst=5, StartLimitIntervalSec=10s). Danach gibt systemd auf und meldet:
kh-demo.service: Start request repeated too quickly.
kh-demo.service: Failed with result 'exit-code'.
Failed to start kh-demo.service - KernelHost Demo Worker.
Der Dienst bleibt dann im Zustand failed und reagiert auch auf systemctl start nicht mehr, bis der Zähler zurückgesetzt wird:
systemctl reset-failed kh-demo.service
Zwei Details kosten hier regelmäßig Zeit. Erstens gehören StartLimitBurst= und StartLimitIntervalSec= in den Abschnitt [Unit], nicht in [Service]. Im falschen Abschnitt werden sie ignoriert, mit Warnung im Journal, aber ohne Fehler. Zweitens zählen manuelle systemctl restart-Aufrufe mit: wer beim Debuggen fünfmal neu startet, löst die Bremse selbst aus.
Faustregel: StartLimitIntervalSec größer als RestartSec mal StartLimitBurst.
Auf Debian 13 und Ubuntu 24.04 gibt es zusätzlich RestartSteps= und RestartMaxDelaySec= (ab systemd 254) für exponentiell wachsende Wartezeiten. Auf Debian 12 und Ubuntu 22.04 existieren diese Direktiven nicht und werden ignoriert.
Aktivieren, starten und beweisen, dass es läuft
Vor dem ersten Start lohnt die Syntaxprüfung. Sie findet Tippfehler in Direktivennamen, falsch geschriebene Abschnitte und fehlende Programme, ohne etwas zu starten:
systemd-analyze verify /etc/systemd/system/kh-demo.service
Keine Ausgabe bedeutet: alles in Ordnung. Ein Tippfehler wie WorkingDirectiry= erzeugt je nach systemd-Version Unknown key name 'WorkingDirectiry' in section 'Service', ignoring oder Unknown key 'WorkingDirectiry' in section [Service], ignoring. Hier entstehen die stillen Fehler: systemd ignoriert unbekannte Schlüssel, der Dienst startet, verhält sich aber anders als erwartet.
systemctl daemon-reload
daemon-reload liest die Unit-Dateien neu ein, startet aber nichts neu. Wer den Reload vergisst, bekommt beim nächsten systemctl status: Warning: The unit file, source configuration file or drop-ins of kh-demo.service changed on disk. Run 'systemctl daemon-reload' to reload units. Reihenfolge merken: erst daemon-reload, dann restart.
systemctl enable kh-demo.service
systemctl start kh-demo.service
Beides zusammen geht mit systemctl enable --now kh-demo.service.
Jetzt der Teil, den die meisten Anleitungen auslassen: der Nachweis, dass es wirklich funktioniert hat und nicht nur kein Fehler kam. Vier unabhängige Belege.
Erstens, der Symlink existiert. Er ist der komplette Mechanismus hinter enable. Fehlt er, startet der Dienst nach dem Reboot nicht, egal was status gerade sagt.
ls -l /etc/systemd/system/multi-user.target.wants/kh-demo.service
systemctl is-enabled kh-demo.service
Zweitens, der Zustand. Entscheidend ist der Klammerzusatz in der Active:-Zeile. active (running) heißt, ein Prozess läuft. active (exited) heißt, das Programm ist fertig und niemand mehr da. Für einen Dauerdienst ist das ein Fehler, auch wenn grün danebensteht.
systemctl status kh-demo.service --no-pager
systemctl is-active kh-demo.service
Drittens, das Journal. Nicht nur, ob Zeilen ankommen, sondern ob die richtigen ankommen.
journalctl -u kh-demo.service -n 20 --no-pager
In Echtzeit mitlesen: journalctl -u kh-demo.service -f. Nur der letzte Bootvorgang: -b. Zeitraum: --since "-1h". Mehr dazu im Beitrag journalctl: Logs unter systemd auswerten.
Viertens, die wirksame Konfiguration. Nicht die Datei, sondern das, was systemd daraus gemacht hat. Der Unterschied zählt, sobald Drop-ins im Spiel sind.
systemctl show kh-demo.service -p ExecStart -p User -p WorkingDirectory -p Restart -p MainPID
systemctl cat kh-demo.service
Der abschließende Beweis bleibt ein echter Reboot. Danach zeigt dieser Befehl, ob etwas auf der Strecke geblieben ist:
systemctl list-units --type=service --state=failed --no-pager
Die häufigsten Fehler im Wortlaut
systemd meldet Startfehler über eigene Exit-Codes oberhalb von 200. Die Zahl im status sagt bereits, wo man suchen muss.
| Code | Bedeutung | Ursache |
|---|---|---|
| 200/CHDIR | EXIT_CHDIR | WorkingDirectory= existiert nicht oder ist für den Benutzer nicht betretbar |
| 203/EXEC | EXIT_EXEC | ExecStart= nicht gefunden, nicht ausführbar oder falscher Interpreter |
| 216/GROUP | EXIT_GROUP | Group= existiert nicht |
| 217/USER | EXIT_USER | User= existiert nicht |
| 219/CGROUP | EXIT_CGROUP | cgroup konnte nicht angelegt werden |
| 238/STATE_DIRECTORY | EXIT_STATE_DIRECTORY | StateDirectory= existiert bereits mit falschem Eigentümer |
203/EXEC: der falsche ExecStart-Pfad
Im Journal steht dazu eine Zeile der Form kh-demo.service: Failed at step EXEC spawning /opt/kh-demo/run.sh: No such file or directory, gefolgt von Main process exited, code=exited, status=203/EXEC.
No such file or directory ist irreführend, denn die Meldung hat drei Ursachen. Erstens: die Datei existiert wirklich nicht, meist wegen eines Tippfehlers oder weil das Binary unter /usr/local/bin statt /usr/bin liegt. Zweitens: das Executable-Bit fehlt, dann meldet das Journal Permission denied. Drittens, der fieseste Fall: die Datei ist ausführbar, aber ihre Shebang-Zeile zeigt ins Leere. Ein Skript mit #!/usr/bin/python scheitert auf Debian 12 und neuer genau so, weil dort nur /usr/bin/python3 existiert. Der Kernel meldet das Fehlen des Interpreters, systemd reicht es als Fehlen des Skripts weiter.
Die Prüfung dauert drei Sekunden:
ls -l /opt/kh-demo/run.sh
head -n 1 /opt/kh-demo/run.sh
Relative Pfade und PATH
ExecStart=node server.js funktioniert nicht, auch nicht mit gesetztem WorkingDirectory=. Der Fehler beim Laden lautet Neither a valid executable name nor an absolute path. Nur der erste Eintrag muss absolut sein, die Argumente danach dürfen relativ bleiben. Richtig ist ExecStart=/usr/bin/node server.js, wobei server.js relativ zum WorkingDirectory aufgelöst wird. Den korrekten Pfad liefert command -v:
command -v bash
Vorsicht bei Version-Managern: unter nvm liefert das einen Pfad wie /root/.nvm/versions/node/v22.14.0/bin/node, den es für den Dienstbenutzer nicht gibt. Laufzeiten für Dienste installiert man systemweit, etwa über NodeSource oder Adoptium Temurin.
Fehlende Umgebungsvariablen
Der Klassiker: im interaktiven Login läuft das Programm, als Dienst nicht. Der Grund ist, dass systemd keine Login-Shell startet. Weder /etc/profile noch ~/.bashrc noch ~/.profile werden gelesen, alles dort per export Gesetzte fehlt im Dienst. Der PATH eines Systemdienstes ist ein fester Minimalpfad, üblicherweise /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin.
Variablen kommen deshalb in die Unit oder in eine eigene Datei:
cat > /etc/kh-demo.env <<'EOF'
GREETING=servus
LANG=de_DE.UTF-8
EOF
chmod 640 /etc/kh-demo.env
chown root:khdemo /etc/kh-demo.env
systemctl restart kh-demo.service
journalctl -u kh-demo.service -n 5 --no-pager
Steht in der Ausgabe jetzt GREETING=servus statt GREETING=nicht gesetzt, ist die Datei angekommen. Kontrollieren lässt sich das auch direkt:
systemctl show kh-demo.service -p Environment -p EnvironmentFiles
Drei Fallen in solchen Dateien: Kommandosubstitution mit $(...) findet nicht statt, ein wörtlich gemeintes $ muss als $$ geschrieben werden, und Anführungszeichen landen im Wert, wo sie nicht hingehören.
Unit not found
Failed to start kh-demo.service: Unit kh-demo.service not found. bedeutet fast immer eines von drei Dingen: die Datei liegt im falschen Verzeichnis, sie hat die falsche Endung (.services statt .service) oder daemon-reload fehlt. ls -l /etc/systemd/system/ klärt die ersten beiden Fälle.
Unterschiede zwischen Debian 13, Debian 12, Ubuntu 24.04 und 22.04
Die Grundlagen, also [Unit], [Service], [Install], Type=simple, Restart=, User= und WorkingDirectory=, sind auf allen vier Systemen identisch. Die Unterschiede liegen in den Randbereichen.
Verfügbare Direktiven. Ubuntu 22.04 bringt systemd 249, Debian 12 systemd 252, Ubuntu 24.04 systemd 255 und Debian 13 systemd 257. Alles ab 253 fehlt auf den beiden älteren Systemen: Type=notify-reload (253) sowie RestartSteps=, RestartMaxDelaySec= und RestartMode=direct (254). Sie werden nicht als Fehler quittiert, sondern ignoriert. So entstehen Units, die auf einem System das Gewünschte tun und auf dem anderen scheinbar grundlos etwas anderes.
systemctl --version
Netzwerk. Ubuntu Server nutzt netplan mit systemd-networkd, Debian in der Standardinstallation ifupdown. Damit ist network-online.target auf Ubuntu ohne Zutun aussagekräftig und auf Debian nur nach dem Aktivieren von ifupdown-wait-online.service.
Interpreter- und Laufzeitpfade. Hier entstehen die meisten 203/EXEC-Fehler beim Übertragen einer Unit zwischen Systemen. Node.js liegt bei Debian 13 in 20.19 vor, bei Debian 12 in 18.20, bei Ubuntu 24.04 in 18.19 und bei Ubuntu 22.04 in 12.22. PHP ist 8.4 auf Debian 13, 8.2 auf Debian 12, 8.3 auf Ubuntu 24.04 und 8.1 auf Ubuntu 22.04. Bei Java ist der Sprung am größten: Debian 13 liefert nur openjdk-21-jre-headless, Debian 12 nur openjdk-17-jre-headless, Ubuntu 24.04 und 22.04 dagegen 8, 11, 17 und 21. Wer ExecStart=/usr/lib/jvm/java-17-openjdk-amd64/bin/java von Debian 12 auf Debian 13 kopiert, bekommt zuverlässig 203/EXEC.
Datenbanken. Debian liefert kein mysql-server, dort läuft immer MariaDB. Eine Unit mit After=mysql.service wartet auf Debian folglich auf einen Dienst, den es nicht gibt, und startet ohne jede Verzögerung, still und ohne Warnung. Richtig ist dort After=mariadb.service.
Ändern, zurücknehmen, aufräumen
Eigene Units ändert man direkt in der Datei plus daemon-reload. Für Units aus einem Paket nimmt man dagegen Drop-ins, damit das nächste Update die Anpassung nicht wegräumt:
systemctl edit kh-demo.service
Das legt /etc/systemd/system/kh-demo.service.d/override.conf an, in der nur die geänderten Direktiven stehen. Sonderfall: Listen-Direktiven wie ExecStart= werden nicht ersetzt, sondern ergänzt. Wer sie überschreiben will, muss die Liste zuerst leeren:
ExecStart=
ExecStart=/opt/kh-demo/run.sh --verbose
systemctl revert kh-demo.service entfernt alle Drop-ins wieder. Vollständiges Zurückbauen des Beispiels, in genau dieser Reihenfolge:
systemctl disable --now kh-demo.service
rm -f /etc/systemd/system/kh-demo.service
rm -rf /etc/systemd/system/kh-demo.service.d
systemctl daemon-reload
systemctl reset-failed
Achtung beim letzten Schritt: systemctl reset-failed ohne Argument setzt den Fehlerzustand aller Einheiten zurück, nicht nur den des Beispieldienstes. Auf einem System, auf dem noch andere Dienste im Zustand failed stehen, verschwinden damit auch deren Meldungen aus systemctl list-units --state=failed. Wer nur hier aufräumen will, nimmt die gezielte Form systemctl reset-failed kh-demo.service aus dem Abschnitt weiter oben.
Wer die Datei löscht, ohne vorher disable aufzurufen, hinterlässt einen toten Symlink in /etc/systemd/system/multi-user.target.wants/, der bei jedem daemon-reload als Warnung auftaucht. Aufgeräumt wird er mit find /etc/systemd/system -xtype l -delete. Dieser Aufruf entfernt allerdings jeden toten Symlink unterhalb von /etc/systemd/system, also auch Leichen fremder Dienste, die man vielleicht noch braucht. Sicherer ist erst ein Blick ohne -delete, oder gleich die gezielte Variante find /etc/systemd/system -xtype l -name 'kh-demo*' -delete. Optional noch Benutzer und Dateien:
userdel khdemo
rm -rf /opt/kh-demo /etc/kh-demo.env
Absoluter Pfad in ExecStart, passender Type, eigener Benutzer, gesetztes WorkingDirectory, Variablen über EnvironmentFile=: wer das beisammen hat, hat einen Dienst, der einen Reboot übersteht und im Fehlerfall verrät, was los war.
Häufige Fragen
Muss ich nach jeder Änderung an der Unit-Datei systemctl daemon-reload ausführen?
Was bedeutet status=203/EXEC?
Warum startet mein Dienst nach dem Reboot nicht, obwohl systemctl start funktioniert?
Type=simple oder Type=forking?
Warum findet mein Dienst seine Umgebungsvariablen nicht?
Der Dienst reagiert nicht mehr auf systemctl start, was tun?
2026 KernelHost GmbH. Alle Rechte vorbehalten. Diese Anleitung ist urheberrechtlich geschützt. Eine Veröffentlichung auf anderen Webseiten, auch auszugsweise oder in bearbeiteter Form, ist ohne unsere schriftliche Zustimmung nicht gestattet. Zitate mit Quellenangabe und Link sind ausdrücklich willkommen.

