Zurück zum Blog

Systemd-Dienst startet nicht? So behebst du es

14. September 2026

Ein systemd-Dienst, der nicht startet, ist fast nie geheimnisvoll. Führe systemctl status <unit> aus und lies die letzten 50 Journal-Zeilen dieser Unit mit journalctl -u <unit> -n 50 --no-pager — zwischen den beiden benennt einer von fünf Ursachen den Fall meist direkt: ein falscher Pfad, eine fehlende Binärdatei, falsche Berechtigungen, ein SELinux/AppArmor-Veto oder ein Syntaxfehler in der Unit-Datei. Der Grund steht im Log; die Fixes unten sind nur Mustererkennung dagegen.

Wie sehe ich, warum ein systemd-Dienst scheitert?

Erst der Status, dann das Journal:

systemctl status myapp.service
journalctl -u myapp.service -n 50 --no-pager

status gibt dir den Zustand (inactive (dead), failed (exit-code), activating (auto-restart)) und die letzten Log-Zeilen. Das Journal liefert die ganze Geschichte: stdout, stderr und systemds eigene Beschwerden über die Unit.

Schlug die Unit früher fehl und du willst den Datensatz dieses Laufs, ergänze -b für den aktuellen Boot oder --since today:

journalctl -u myapp.service -b --no-pager

Für das volle Werkzeug — Boots, Prioritäten, Ausgabe live verfolgen — siehe das journalctl cheat sheet. Dasselbe Muskelgedächtnis.

Noch ein Paar, das man kennen sollte:

systemctl list-units --failed
systemctl reset-failed myapp.service

Das erste listet jede rote Unit auf der Kiste. Das zweite räumt den failed-Zustand nach dem Fix weg — Kosmetik, aber es hört auf, dass Statusseiten Wolf schreien.

Was sind die häufigsten Ursachen?

Hat das Log das Symptom benannt, ist die Ursache fast immer eine dieser fünf:

Log sagtWahrscheinliche UrsacheFix
status=203/EXECFalscher ExecStart=-Pfad oder fehlender InterpreterAbsoluter Pfad, chmod +x, Shebang prüfen
status=203/EXEC bei einem SkriptSkript hat CRLF-Zeilenenden oder kaputten Shebangdos2unix script.sh, erste Zeile richten
status=1/FAILURE, keine App-LogsWorking Directory oder Umgebungsvariable fehltWorkingDirectory= setzen, Environment= ergänzen
Permission deniedNutzer darf Dateien nicht lesen oder den Port bindenOwnership richten; Ports unter 1024 brauchen AmbientCapabilities=CAP_NET_BIND_SERVICE oder root
Unit is maskedJemand hat systemctl mask ausgeführtsystemctl unmask myapp.service
Unit-Datei-Änderung „bewirkt nichts”Daemon nicht neu geladensystemctl daemon-reload

Die 203/EXEC-Familie verdient eine Sondereinlage, weil sie Nachmittage frisst. Systemd startet ExecStart= nicht über deine Shell. Das heißt:

# Falsch — keine Shell, ~ wird nie aufgelöst, keine PATH-Suche
ExecStart=~/app/run.sh

# Richtig
ExecStart=/opt/app/run.sh

Und das Skript selbst muss ausführbar sein und mit einem echten Shebang beginnen (#!/bin/bash oder #!/usr/bin/env bash). Ein Skript, das im Terminal läuft, aber unter systemd mit 203 scheitert, hat fast immer eins davon: nicht ausführbar, CRLF-Zeilenenden, ein Shebang ins Leere oder ein relativer Pfad.

Warum startet er manuell, aber nicht beim Boot?

Der klassische Reihenfolgen-Bug. Zeigt das Log Fehler direkt nach dem Boot, startet die Unit aber fein per Hand mit systemctl start, verliert dein Service ein Rennen — er greift nach Netzwerk, einer gemounteten Platte oder einer Datenbank, bevor das Ding existiert.

Der Fix: Abhängigkeiten deklarieren statt hoffen:

[Unit]
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
ExecStartPre=/usr/bin/test -f /opt/app/config.toml

After= ordnet den Start; Wants= bringt systemd dazu, die Abhängigkeit tatsächlich hochzufahren. network-online.target funktioniert nur, wenn der Network-Wait-Dienst auf deiner Distro aktiviert ist — prüfe systemctl is-enabled NetworkManager-wait-online.service (oder das systemd-networkd-Pendant). Der ExecStartPre-Wächter ist der billige, ehrliche Weg, laut mit einer lesbaren Nachricht zu scheitern statt mit einem Stack Trace.

Zweite Variante: Der Service startet beim Boot, stirbt aber sofort. Suche nach Dingen, die deine interaktive Umgebung hatte und der Boot nicht — PATH-Unterschiede, ein virtualenv, ein HOME. Setz explizit, was du brauchst:

[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app

Warum schreibt mein Service nichts ins Journal?

Zeigt journalctl -u nichts, drei Checks der Reihe nach:

  1. StandardOutput= und StandardError= in der Unit — sie müssen journal (der Default) oder journal+console sein. Irgendwer hat sie womöglich auf null gestellt.
  2. Die App schreibt in eine Datei statt auf stdout. Systemd fängt nur stdout/stderr ab; Log-Datei-Schreiber umgehen das Journal komplett. Lenk die App auf stdout oder lies die Datei.
  3. Speicherlimits haben alte Zeilen fallen lassen: journalctl --disk-usage, und SystemMaxUse= in /etc/systemd/journald.conf, wenn das Journal erstickt.

Für das Debugging des Starts selbst schlägt nichts, eine Shell in den Boot-Zeitkontext zu hängen:

[Service]
ExecStart=/bin/bash -c 'exec /opt/app/bin/server 2>&1'

Oder, für echte Mysterien: den exakten ExecStart=-Befehl als Unit-Nutzer in einer Shell laufen lassen — die meisten Umgebungsunterschiede zeigen sich in den ersten zehn Sekunden.

Wie kriegst du einen automatischen Neustart nach einem Crash hin?

Die Default-Policy ist Restart=no: Ein gecrashter Service bleibt tot, und du erfährst es von einem Nutzer. Pro Service fixen:

[Service]
Restart=on-failure
RestartSec=5
EinstellungStartet neu, wenn
no (Default)Nie
on-failureExit-Code ungleich null, Signal, Timeout
alwaysJeder Exit, auch ein sauberer
on-watchdogNur Watchdog-Timeout

Restart=on-failure mit einer Startraten-Bremse paaren, damit eine Crash-Schleife nicht auf die Kiste hämmert: StartLimitIntervalSec= und StartLimitBurst= in der [Unit]-Sektion. Fünf Fehlschläge in sechzig Sekunden sollten einen Menschen rufen, keine CPU glühen lassen.

Wenn du das als geplanten Job statt als Daemon aufsetzt, wäge zuerst cron vs. systemd-Timer ab — Timer geben dir Journal-Logging und Abhängigkeits-Ordnung umsonst, genau das, worauf dieser Artikel ständig zugreift.

Die 60-Sekunden-Checkliste

  1. systemctl status <unit> — Zustand und letzte Zeilen lesen.
  2. journalctl -u <unit> -n 50 --no-pager — den echten Fehler finden.
  3. 203/EXEC? Pfad, Shebang, Berechtigungen fixen. Permission denied? Nutzer und File-Ownership richten.
  4. Scheitert nur beim Boot? After=/Wants=network-online.target und einen ExecStartPre-Wächter ergänzen.
  5. Unit-Datei geändert? systemctl daemon-reload && systemctl restart <unit>.
  6. Restart=on-failure ergänzen, damit der nächste Crash sich meldet, statt sich zu verstecken.

Die meisten „systemd ist kompliziert”-Momente reduzieren sich auf der Fehler steckte die ganze Zeit im Journal. Lies es, bevor du die Unit-Datei editierst, nicht danach.

— mrsaynothing

Get the next one by email

One email per post. No spam, no algorithms.

self-hosted · no third parties · one-click unsubscribe

what is this?

GGUF-Quantisierung: Welches Level solltest du nutzen?

Sie mögen die Artikel? Genau so baue ich beruflich. anheuern