Häufige Systemd-Konfigurationsfehler und wie man sie behebt

Beheben Sie häufige Fehler in Systemd-Unit-Dateien: falsche Pfade, falsche Servicetypen, fehlende Umgebungsvariablen, Berechtigungen und Abhängigkeitsreihenfolge.

Häufige Systemd-Konfigurationsfehler und wie man sie behebt

Systemd-Konfigurationsfehler wirken meist dramatischer, als sie sind. Ein Dienst startet nicht, ein Deployment wird zurückgesetzt, oder der Bootvorgang hängt an einer Unit, an deren Erstellung Sie sich kaum erinnern. Die eigentliche Ursache ist dann ein fehlender Schrägstrich in ExecStart=, ein Prozess, der als falscher Benutzer läuft, oder eine Änderung an einer Unit-Datei, die nie den Systemd-Manager erreicht hat, weil niemand daemon-reload ausgeführt hat.

Der schnellste Weg durch diese Probleme ist, die Unit-Datei als Vertrag zu betrachten. Sie sagt Systemd, welcher Prozess ausgeführt werden soll, unter welchem Benutzer, was zuerst vorhanden sein muss, wie die Bereitschaft gemeldet wird und was nach einem Absturz passieren soll. Wenn eines dieser Details falsch ist, macht Systemd in der Regel genau das, was ihm gesagt wurde. Die Arbeit besteht darin, die Diskrepanz zwischen den Anforderungen der Anwendung und dem tatsächlichen Inhalt der Unit-Datei zu finden.

1. Syntax- und Pfadfehler in Unit-Dateien

Eine der häufigsten Ursachen für Dienstausfälle ist ein einfacher Tippfehler oder ein falsch definierter Pfad in der Unit-Datei.

Falsche oder nicht absolute Pfade in Exec-Befehlen

Systemd führt Ihren Dienst nicht in derselben Shell-Sitzung aus, die Sie zum Testen verwendet haben. Es startet den Prozess in einer kontrollierten Umgebung, sodass Annahmen über Aliase, Shell-Funktionen, Virtualenv-Aktivierung und einen benutzerdefinierten PATH oft fehlschlagen. Verwenden Sie absolute Pfade für die ausführbare Datei in ExecStart= und geben Sie jedes Verzeichnis oder jede Datei an, die der Dienst benötigt, explizit an.

Der Fehler:

Verwendung eines Befehlsnamens ohne Angabe seines Speicherorts.

[Service]
ExecStart=my-app-server --config /etc/config.yaml

Wenn sich my-app-server in /usr/local/bin befindet, wird Systemd es wahrscheinlich nicht finden.

Die Behebung:

Verwenden Sie immer den vollständigen, absoluten Pfad zur ausführbaren Datei.

[Service]
ExecStart=/usr/local/bin/my-app-server --config /etc/config.yaml

Überprüfen Sie vor der Konfiguration von ExecStart= den Pfad mit command -v my-app-server oder which my-app-server. Wenn die Anwendung an einem sprachspezifischen Ort lebt, z. B. in einer Python-Virtual-Umgebung unter /opt/myapp/venv/bin/gunicorn, zeigen Sie direkt auf diese Binärdatei, anstatt sich auf Aktivierungsskripte zu verlassen.

Tipp- und Groß-/Kleinschreibungsfehler

Systemd-Konfigurationsdirektiven unterscheiden zwischen Groß- und Kleinschreibung und müssen in den richtigen Abschnitten ([Unit], [Service], [Install]) platziert werden. Rechtschreibfehler oder falsche Groß-/Kleinschreibung führen dazu, dass der Dienst nicht geladen wird oder unerwartetes Verhalten zeigt.

Beispiel Fehler:

[Service]
ExecStart=/usr/bin/python3 app.py
RestartAlways=true  ; Sollte Restart=always sein

Die Behebung:

Verwenden Sie systemd-analyze verify <unit_file>, bevor Sie den Daemon neu laden. Es wird nicht jeden Laufzeitfehler abfangen, aber es erkennt viele falsch geschriebene Direktiven, ungültige Abschnittsplatzierungen und Parsing-Fehler, bevor Sie Zeit mit der Suche in Anwendungsprotokollen verschwenden.

$ systemd-analyze verify /etc/systemd/system/my-service.service

2. Fehlverwaltung von Dienstabhängigkeiten und -reihenfolge

Abhängigkeiten definieren, welche Ressourcen ein Dienst benötigt, während die Reihenfolge definiert, wann diese Ressourcen verfügbar sein müssen.

Verwechslung von Requires vs. Wants

Diese Direktiven werden verwendet, um Abhängigkeiten zu definieren, behandeln Fehler jedoch unterschiedlich:

  • Wants=: Eine schwache Abhängigkeit. Wenn die gewünschte Unit fehlschlägt oder nicht startet, wird die aktuelle Unit dennoch versuchen zu starten. Verwenden Sie dies für nicht kritische Abhängigkeiten.
  • Requires=: Eine starke Abhängigkeit. Wenn die erforderliche Unit nicht gestartet werden kann, schlägt auch die aktuelle Unit fehl. Wenn die erforderliche Unit explizit gestoppt wird, wird auch die abhängige Unit gestoppt.

Verlassen auf Requires ohne korrekte Reihenfolge

Das Definieren einer Abhängigkeit, z. B. Requires=network.target, zieht die Abhängigkeit in die Transaktion. Es erzeugt für sich genommen keine Startreihenfolge, und network.target bedeutet nicht "das Netzwerk ist für ausgehende Verbindungen nutzbar". Wenn Ihr Dienst ein konfiguriertes Netzwerk benötigt, verwenden Sie network-online.target und stellen Sie sicher, dass der Wait-Online-Dienst der Distribution aktiviert ist, wenn dieses Verhalten erforderlich ist.

Der Fehler:

Ein Webserver startet, aber die Datenbankverbindung schlägt fehl, weil der Netzwerkstack noch initialisiert wird.

Die Behebung: Verwendung von After= und Before=

Um die Reihenfolge zu erzwingen, müssen Sie After= (oder Before=) verwenden. Eine häufige Anforderung ist sicherzustellen, dass das Netzwerk vollständig hochgefahren und konfiguriert ist, bevor fortgefahren wird.

[Unit]
Description=My Web Application Service
Wants=network-online.target
After=network-online.target

[Service]
...

Für die meisten Anwendungsdienste kombinieren Sie die Absicht der Abhängigkeit mit der Absicht der Reihenfolge. Wants=postgresql.service bedeutet "bitte starte auch PostgreSQL". After=postgresql.service bedeutet "starte mich, nachdem der Startjob von PostgreSQL abgeschlossen ist". Sie lösen unterschiedliche Probleme.

Falsche Verwaltung des Diensttyps

Systemd-Dienste haben mehrere Ausführungstypen, die über die Direktive Type= verwaltet werden. Eine Fehlkonfiguration ist eine häufige Ursache dafür, dass Dienste kurz starten und dann sofort fehlschlagen.

Der Fehler: Falsche Verwendung von Type=forking

Wenn Ihre Anwendung dazu entwickelt wurde, im Vordergrund zu laufen und einen einzigen Hauptprozess zu verwalten, teilt die Einstellung Type=forking Systemd mit, dass es ein Daemon-Verhalten alter Art erwartet. Dies kann zu verwirrenden Ergebnissen führen: Systemd wartet möglicherweise auf das Beenden eines Elternprozesses, kann den eigentlichen Hauptprozess nicht identifizieren oder markiert den Dienst als aktiv, obwohl die Anwendung noch nicht bereit ist.

Die Behebungen:

  1. Für moderne Anwendungen: Verwenden Sie Type=simple. Dies ist die Standardeinstellung und erwartet, dass der ExecStart-Prozess der Hauptprozess ist.
  2. Für Legacy-Anwendungen, die sich daemonisieren (forken): Setzen Sie Type=forking und definieren Sie entscheidend die Direktive PIDFile=, damit Systemd den untergeordneten Prozess verfolgen kann, der den Fork überlebt hat.
[Service]
Type=forking
PIDFile=/var/run/legacy-app.pid
ExecStart=/usr/sbin/legacy-app

Es gibt eine weitere häufige Bereitschaftsfalle: die Verwendung von Type=simple für eine Anwendung, die lange braucht, um nutzbar zu werden. Mit Type=simple betrachtet Systemd den Dienst als gestartet, sobald der Prozess erzeugt wurde. Wenn ein anderer Dienst sofort danach startet und eine Verbindung zu ihm herstellt, können Sie intermittierende Fehler sehen. Für Anwendungen, die Systemd benachrichtigen können, wenn sie bereit sind, ist Type=notify sauberer. Für Anwendungen, die dies nicht können, vermeiden Sie es, so zu tun, als sei die Unit vollständig bereit, nur weil der Prozess existiert; verwenden Sie einen echten Health-Check in der abhängigen Anwendung, Socket-Aktivierung oder eine ExecStartPre=-Prüfung, wenn die Voraussetzung einfach genug zu testen ist.

Seien Sie auch bei oneshot vorsichtig. Ein Type=oneshot-Dienst ist für einen Befehl, der eine Aufgabe ausführt und beendet wird, z. B. das Erstellen eines Verzeichnisses, das Laden einer Firewall-Regel oder das Ausführen einer Migration. Wenn Sie es für einen langlebigen Daemon verwenden, wird Systemd ihn nicht so überwachen, wie Sie es erwarten. Wenn der Befehl erfolgreich beendet wird und Sie möchten, dass die Unit für Abhängigkeitszwecke "aktiv" bleibt, fügen Sie RemainAfterExit=yes hinzu; andernfalls sehen abhängige Units möglicherweise nicht den von Ihnen beabsichtigten Zustand.

3. Probleme mit Umgebungs- und Benutzerkontext

Dienstausfälle resultieren oft daraus, dass der Dienst in einem anderen Kontext läuft als von der Anwendung erwartet, was normalerweise mit Berechtigungen oder Umgebungsvariablen zusammenhängt.

Berechtigungsverweigerung oder fehlende Dateien

Wenn Sie eine Anwendung manuell testen, läuft sie normalerweise unter Ihrem Benutzerkonto mit entsprechenden Berechtigungen. Wenn sie von Systemd ausgeführt wird, wird standardmäßig der Root-Benutzer oder der in der Unit-Datei angegebene Benutzer verwendet.

Der Fehler:

Typische Symptome sind eindeutig: Permission denied, No such file or directory, Failed to open log file oder ein anwendungsspezifischer Fehler, der besagt, dass kein Socket erstellt, keine PID-Datei geschrieben oder keine Konfigurationsdatei gelesen werden kann. Die Unit funktioniert möglicherweise, wenn Sie den Befehl manuell als root ausführen, schlägt dann aber unter User=app fehl.

Die Behebung:

  1. Definieren Sie einen Nicht-Root-Benutzer: Geben Sie immer einen dedizierten Benutzer und eine Gruppe mit niedrigen Privilegien für Ihren Dienst an.

    [Service]
    User=www-data
    Group=www-data
    ...
    
  2. Überprüfen Sie die Eigentümerschaft: Stellen Sie sicher, dass das Arbeitsverzeichnis des Dienstes, die Protokolldateien und die Konfigurationsdateien dem angegebenen User= und Group= gehören.

    sudo chown -R www-data:www-data /var/www/my-app
    
  3. Überprüfen Sie jeden Pfad, den der Dienst berührt: Hören Sie nicht beim Anwendungsverzeichnis auf. Überprüfen Sie WorkingDirectory=, Protokollverzeichnisse, Upload-Verzeichnisse, Cache-Verzeichnisse, TLS-Schlüsseldateien, Unix-Sockets und jeden Pfad, auf den eine Umgebungsdatei verweist.

    sudo -u www-data test -r /etc/my-app/config.yml
    sudo -u www-data test -w /var/lib/my-app
    sudo -u www-data /usr/local/bin/my-app --check-config
    

Wenn der Dienst an Port 80 oder 443 binden muss, führen Sie ihn nicht automatisch als root aus. Auf vielen Systemen können Sie einen Reverse-Proxy davor schalten, Socket-Aktivierung verwenden oder der Binärdatei die spezifische Fähigkeit erteilen, die sie benötigt. Die richtige Wahl hängt vom Dienst ab, aber ein breiter Root-Prozess sollte nicht die Standardantwort sein.

Ein weiteres Berechtigungsdetail fällt oft auf: Übergeordnete Verzeichnisse benötigen die Ausführungsberechtigung. Eine Datei kann lesbar erscheinen, aber der Dienst kann sie trotzdem nicht erreichen, weil /opt, /opt/myapp oder ein anderes übergeordnetes Verzeichnis den Zugriff für den Dienstbenutzer blockiert. namei -l /opt/myapp/config.yml ist nützlich, weil es die Berechtigungen für jede Pfadkomponente anzeigt, nicht nur für die endgültige Datei.

Fehlende Umgebungsvariablen

Systemd-Dienste laufen in einer minimalen Umgebung. Alle wichtigen Umgebungsvariablen (wie API-Schlüssel, Datenbankverbindungszeichenfolgen oder benutzerdefinierte Bibliothekspfade) müssen explizit übergeben werden.

Die Behebung: Verwendung von Environment= oder EnvironmentFile=

Für einfache Variablen verwenden Sie Environment=:

[Service]
Environment="APP_PORT=8080"
Environment="API_KEY=ABCDEFG"

Für komplexe oder zahlreiche Variablen verwenden Sie EnvironmentFile= mit einer standardmäßigen .env-Datei:

[Service]
EnvironmentFile=/etc/default/my-app.conf

Bewahren Sie Geheimnisse nicht in weltlesbaren Unit-Dateien auf. Eine Unit-Datei unter /etc/systemd/system ist normalerweise für lokale Benutzer lesbar. Wenn Sie API-Schlüssel direkt in Environment= setzen, gehen Sie davon aus, dass sie für jeden sichtbar sind, der Unit-Dateien lesen oder Prozessmetadaten einsehen kann. Bevorzugen Sie eine root-eigene Umgebungsdatei mit restriktiven Berechtigungen, einen Secret-Manager oder Systemd-Anmeldeinformationen auf Distributionen, die diese unterstützen.

Denken Sie auch daran, dass EnvironmentFile= kein Shell-Skript ist. Zeilen wie export APP_PORT=8080 oder Befehlsersetzungen wie TOKEN=$(cat /run/token) werden nicht so interpretiert wie in Bash. Verwenden Sie einfache Zuweisungen:

APP_PORT=8080
APP_ENV=production

Wenn die Anwendung eine Login-Shell-Einrichtung benötigt, ist das normalerweise ein schlechtes Zeichen. Setzen Sie die eigentliche Umgebung in die Unit-Datei, die Umgebungsdatei oder die eigene Konfiguration der Anwendung, anstatt sich auf .bashrc zu verlassen.

Für Sprachlaufzeiten weisen Sie Systemd auf die Laufzeit hin, die Sie tatsächlich getestet haben. Ein Python-Dienst sollte normalerweise die Binärdatei der virtuellen Umgebung direkt aufrufen, z. B. /opt/myapp/venv/bin/python oder /opt/myapp/venv/bin/gunicorn. Ein Node-Dienst, der über einen Versionsmanager installiert wurde, funktioniert möglicherweise in Ihrem Terminal, schlägt aber unter Systemd fehl, weil nvm oder asdf nur Ihre interaktive Shell modifiziert haben. In Produktions-Units sind explizite Pfade besser als Shell-Start-Magie.

4. Der entscheidende Debugging-Workflow

Der häufigste Konfigurationsfehler ist das Vergessen des entscheidenden Schritts zwischen dem Bearbeiten der Unit-Datei und dem Versuch, den Dienst neu zu starten.

Vergessen, den Daemon neu zu laden

Systemd überwacht Unit-Dateien nicht automatisch auf Änderungen. Nach jeder Änderung an einer Datei in /etc/systemd/system/ muss der Systemd-Manager angewiesen werden, seinen Konfigurationscache neu zu laden.

Der Fehler:

Sie bearbeiten die Datei, führen systemctl restart my-service aus, aber die alte Konfiguration wird weiterhin verwendet.

Die Behebung: Führen Sie daemon-reload aus

Führen Sie diesen Befehl immer unmittelbar nach dem Speichern einer Änderung an einer Unit-Datei aus:

sudo systemctl daemon-reload
sudo systemctl restart my-service

Wenn Sie einen Drop-In-Override mit systemctl edit my-service bearbeitet haben, gilt dieselbe Regel. Der generierte Override wird unter /etc/systemd/system/my-service.service.d/ gespeichert, und Systemd muss trotzdem seinen Unit-Cache neu laden, bevor die neuen Einstellungen wirksam werden.

Wenn ein Neustart sich seltsam verhält, überprüfen Sie die genaue zusammengeführte Unit, die Systemd sieht:

systemctl cat my-service.service
systemctl show my-service.service -p FragmentPath -p DropInPaths -p User -p ExecStart

Dies fängt einen häufigen Fehler ein: das Bearbeiten einer Vendor-Unit in /usr/lib/systemd/system, während ein Override in /etc/systemd/system die Einstellung immer noch ändert, oder das Bearbeiten einer Kopie einer Unit, die nicht die von Systemd geladene ist.

Effektive Nutzung von Protokollierungswerkzeugen

Wenn ein Dienst fehlschlägt, verlassen Sie sich auf die offiziellen Werkzeuge für eine genaue Diagnose.

  1. Überprüfen Sie den Dienststatus: Dies gibt Ihnen den aktuellen Zustand, Exit-Codes und die letzten Protokollzeilen.

    systemctl status my-service.service
    
  2. Überprüfen Sie das Journal: Das Journal enthält die umfassende Ausgabe (stdout/stderr) des Dienstes. Suchen Sie nach Hinweisen wie "Permission denied" oder "No such file or directory".

    # Zeigt die letzten Protokolle speziell für Ihre Unit an
    journalctl -u my-service.service --since '1 hour ago' 
    
    # Zeigt Protokolle an und folgt der Ausgabe in Echtzeit
    journalctl -f -u my-service.service
    

Ein praktischer Troubleshooting-Durchlauf

Wenn ich eine defekte Unit überprüfe, mache ich normalerweise einen Durchlauf in dieser Reihenfolge:

systemctl status my-service.service
journalctl -u my-service.service --since "15 minutes ago"
systemctl cat my-service.service
systemd-analyze verify /etc/systemd/system/my-service.service

Dann stelle ich einfache Fragen. Zeigt ExecStart= auf eine echte ausführbare Datei? Kann der konfigurierte User= sie ausführen? Existiert WorkingDirectory=? Sind die Umgebungsvariablen vorhanden, ohne sich auf eine Shell zu verlassen? Ist der Type= ehrlich darüber, wie sich der Prozess verhält? Sind Wants= und After= beide vorhanden, wenn ich eine andere Unit benötige, die gestartet und vor dieser geordnet werden soll?

Nach jeder Bearbeitung lade ich neu und teste eine Sache:

sudo systemctl daemon-reload
sudo systemctl restart my-service.service
systemctl status my-service.service --no-pager

Wenn der Dienst immer noch fehlschlägt, widerstehen Sie dem Drang, die Unit-Datei blind weiter zu ändern. Das Journal sagt Ihnen normalerweise, ob das nächste Problem die Systemd-Konfiguration, die Anwendungskonfiguration, Berechtigungen oder eine Abhängigkeit ist, die von selbst fehlschlägt.