TL;DR — Kurzzusammenfassung

Systemd-Dienste unter Linux meistern: Unit-File-Aufbau, Node.js- und Python-Beispiele, Neustart-Richtlinien, Ressourcenlimits und Sicherheitshaertung.

SYSTEMD — LINUX-DIENSTVERWALTUNG nginx.service active (running) myapp.service active (running) backup.timer active (waiting) worker.service active (running) Verwalten Sie alle Prozesse Ihres Linux-Servers ueber ein einziges Init-System

Langlebige Prozesse auf einem Linux-Server am Laufen zu halten erforderte fruher komplexe SysVinit-Skripte, Supervisor-Konfigurationen oder fragile Shell-Hacks. systemd hat das alles veraendert. Als Standard-Init-System auf allen grossen Linux-Distributionen — Ubuntu, Debian, RHEL, Fedora, Arch — bietet systemd eine einheitliche, deklarative Moeglichkeit, genau zu definieren, wie jeder Prozess gestartet, gestoppt, neugestartet, protokolliert und mit dem Rest des Systems interagiert werden soll.

Dieser Leitfaden behandelt alles, was Sie benoetigen, um produktionsreife eigene systemd-Dienste zu erstellen: den Aufbau einer Unit-Datei, konkrete Beispiele fuer Node.js- und Python-Anwendungen, Diensttypen, Neustart-Richtlinien, Ressourcenlimits, Sicherheitsdirektiven, Socket-Aktivierung, Timer-Units als Cron-Alternative und das Debuggen von Fehlern mit journalctl.


Voraussetzungen

Stellen Sie vor Beginn sicher, dass Sie Folgendes haben:

  • Ein Linux-System mit systemd (Ubuntu 16.04+, Debian 8+, CentOS 7+, RHEL 7+, Fedora 15+ oder eine moderne Distribution)
  • Ein Terminal mit sudo- oder Root-Zugriff
  • Grundlegende Vertrautheit mit der Kommandozeile und einem Texteditor (nano oder vim)
  • Eine Anwendung, die als Dienst ausgefuehrt werden soll (Node.js, Python, Go-Binary usw.)

Pruefen Sie, ob systemd laeuft:

systemctl --version
# Sollte ausgeben: systemd 249 (oder aehnliche Versionsnummer)

Aufbau der Unit-Datei

Jeder von systemd verwaltete Dienst wird durch eine Unit-Datei beschrieben — eine einfache Textkonfigurationsdatei im INI-Format. Benutzerdefinierte Dienst-Unit-Dateien befinden sich in /etc/systemd/system/. Der Dateiname endet auf .service.

Eine vollstaendige Unit-Datei hat drei Abschnitte:

[Unit]
Description=Mein Anwendungsdienst
Documentation=https://beispiel.com/docs
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=meinapp
Group=meinapp
WorkingDirectory=/opt/meinapp
ExecStart=/usr/bin/node /opt/meinapp/server.js
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Der [Unit]-Abschnitt

DirektiveZweck
Description=Lesbarer Name in der systemctl status-Ausgabe
After=Startet diese Unit nach den aufgelisteten Units
Wants=Weiche Abhaengigkeit — aufgelistete Units werden gestartet, Fehler toleriert
Requires=Harte Abhaengigkeit — wenn die aufgelistete Unit fehlschlaegt, schlaegt diese auch fehl
BindsTo=Wie Requires=, stoppt diese Unit aber auch wenn die Abhaengigkeit stoppt

Der [Service]-Abschnitt

DirektiveZweck
Type=Prozess-Lebenszyklus-Modell (simple, forking, oneshot, notify)
User= / Group=Fuehrt den Prozess als diesen Benutzer/Gruppe aus
ExecStart=Der auszufuehrende Befehl (muss absoluter Pfad sein)
ExecStartPre=Befehle, die vor ExecStart ausgefuehrt werden
Restart=Wann automatisch neugestartet werden soll
Environment=Setzt Umgebungsvariablen direkt
EnvironmentFile=Laedt Umgebungsvariablen aus einer Datei
StandardOutput=Wohin stdout gesendet wird (journal, file:, null)

Der [Install]-Abschnitt

DirektiveZweck
WantedBy=Welches Target diesen Dienst aktiviert (ueblicherweise multi-user.target)
Alias=Zusaetzliche Namen fuer diese Unit

Einen Node.js-Dienst erstellen

Angenommen, Sie haben eine Node.js-API unter /opt/meinapi/server.js, die auf Port 3000 lauscht.

Schritt 1: Dedizierten Benutzer erstellen

sudo useradd --system --no-create-home --shell /usr/sbin/nologin nodeapi
sudo chown -R nodeapi:nodeapi /opt/meinapi

Schritt 2: Unit-Datei erstellen

sudo nano /etc/systemd/system/meinapi.service
[Unit]
Description=Mein Node.js API-Dienst
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=nodeapi
Group=nodeapi
WorkingDirectory=/opt/meinapi
EnvironmentFile=/etc/meinapi/environment
ExecStart=/usr/bin/node /opt/meinapi/server.js
ExecStartPre=/usr/bin/node --check /opt/meinapi/server.js
Restart=on-failure
RestartSec=5s
TimeoutStopSec=10s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=meinapi
MemoryMax=512M
CPUQuota=50%
LimitNOFILE=65536
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
ReadWritePaths=/opt/meinapi/logs /var/lib/meinapi

[Install]
WantedBy=multi-user.target

Schritt 3: Umgebungsdatei erstellen

sudo mkdir -p /etc/meinapi
sudo nano /etc/meinapi/environment
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://benutzer:passwort@localhost/meindb
JWT_SECRET=ihr-geheimnis-hier
sudo chmod 600 /etc/meinapi/environment

Schritt 4: Aktivieren und starten

sudo systemctl daemon-reload
sudo systemctl enable meinapi.service
sudo systemctl start meinapi.service
sudo systemctl status meinapi.service

Einen Python-Dienst erstellen

Fuer einen Python-Worker unter /opt/worker/worker.py:

[Unit]
Description=Python-Hintergrund-Worker
After=network.target redis.service
Requires=redis.service

[Service]
Type=simple
User=pyworker
Group=pyworker
WorkingDirectory=/opt/worker
ExecStart=/opt/worker/venv/bin/python -u worker.py
EnvironmentFile=/etc/worker/environment
Restart=on-failure
RestartSec=10s
StartLimitIntervalSec=60s
StartLimitBurst=3
StandardOutput=journal
StandardError=journal
MemoryMax=256M
CPUQuota=25%
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Das -u-Flag beim Python-Befehl deaktiviert die Ausgabepufferung, sodass Logs sofort in journald erscheinen.


Diensttypen

TypWann verwendenWie systemd die Bereitschaft verfolgt
simpleDie meisten modernen AppsBetrachtet die Unit als gestartet, sobald ExecStart einen Fork macht
execWie simple, aber wartet auf exec-ErfolgWartet bis das Binary erfolgreich ausgefuehrt wird
forkingTraditionelle Daemons, die forkenWartet bis der Elternprozess beendet ist
oneshotSkripte, die einmal laufen und beendenWartet auf ExecStart-Beendigung vor der Aktivierung
notifyApps, die Bereitschaftsbenachrichtigung sendenWartet auf die Benachrichtigung vor der Aktivierung

Neustart-Richtlinien und Ressourcenlimits

Neustart-Richtlinien

Restart=always        # Neustart bei jeder Beendigung des Dienstes
Restart=on-failure    # Neustart nur bei Fehler (nicht bei sauberem Exit)
Restart=on-abnormal   # Neustart bei Fehler + Watchdog-Timeout + anormale Signale
Restart=no            # Kein Neustart

Mit Ratenbegrenzung, um zu verhindern, dass ein abstuerzeender Dienst das System ueberlastet:

Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=5

Ressourcenlimits

MemoryMax=1G          # Hartes Speicherlimit — Prozess wird beendet wenn ueberschritten
MemoryHigh=800M       # Weiches Speicherlimit — Kernel aktiviert Rueckgewinnung
MemorySwapMax=0       # Swap fuer diesen Dienst deaktivieren
CPUQuota=200%         # Auf 2 volle CPU-Kerne begrenzen
LimitNOFILE=65536     # Maximale offene Dateideskriptoren
LimitNPROC=512        # Maximale Anzahl von Prozessen/Threads

Sicherheitshaertung

[Service]
NoNewPrivileges=true          # Verhindert neue Berechtigungen via setuid/setgid
ProtectSystem=strict          # Haengt /usr, /boot, /etc schreibgeschuetzt ein
PrivateTmp=true               # Privates /tmp fuer diesen Dienst
DynamicUser=true              # Dynamischer unprivilegierter Benutzer
ProtectHome=true              # Blockiert Zugriff auf /home-Verzeichnisse
SystemCallFilter=@system-service  # Filtert verfuegbare Systemaufrufe
ProtectKernelTunables=true    # Verhindert Schreiben in Kernelvariablen
ProtectKernelModules=true     # Verhindert Laden von Kernelmodulen

Analysieren Sie den Sicherheitsscore eines laufenden Dienstes:

systemd-analyze security meinapi.service

Timer-Units: systemd als Cron-Alternative

sudo nano /etc/systemd/system/naechtiches-backup.service
[Unit]
Description=Naechtliches Datenbank-Backup

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-db.sh
StandardOutput=journal
sudo nano /etc/systemd/system/naechtiches-backup.timer
[Unit]
Description=Naechtliches Backup um 2 Uhr starten

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now naechtiches-backup.timer
systemctl list-timers --all

systemd vs. andere Prozessmanager

FunktionsystemdSysVinit / init.dsupervisordPM2
Im OS enthaltenJa (alle grossen Distros)Ja (Legacy)Nein (pip install)Nein (npm install)
AbhaengigkeitsreihenfolgeVollstaendiger GraphManuelle ReihenfolgeBegrenztNein
Socket-AktivierungJaNeinNeinNein
Cgroup-RessourcenlimitsJa (nativ)NeinNeinNein
Kernel-Level-SandboxJa (umfangreich)NeinNeinNein
Zentrales Loggingjournald (strukturiert)syslog / DateienNur DateienPM2-Dateien
Geplante JobsJa (Timer-Units)NeinNeinJa (Cron-aehnlich)
Node.js-IntegrationGutSchlechtGutAusgezeichnet
LernkurveModeratNiedrigNiedrigSehr niedrig

Praxisbeispiel

Sie haben einen Produktionsserver mit einer Python-FastAPI-Anwendung, die Jobs aus einer Redis-Queue verarbeitet. Der Dienst soll bei Absturz automatisch neu starten und niemals mehr als 512 MB RAM verbrauchen.

[Unit]
Description=FastAPI Job Worker
After=network.target redis.service
Requires=redis.service
StartLimitIntervalSec=120s
StartLimitBurst=4

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/opt/fastapi-worker
ExecStart=/opt/fastapi-worker/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2
EnvironmentFile=/etc/fastapi-worker/environment
Restart=on-failure
RestartSec=8s
TimeoutStartSec=30s
TimeoutStopSec=15s
StandardOutput=journal
SyslogIdentifier=fastapi-worker
MemoryMax=512M
CPUQuota=100%
LimitNOFILE=32768
NoNewPrivileges=true
ProtectSystem=strict
PrivateTmp=true
ReadWritePaths=/opt/fastapi-worker/data

[Install]
WantedBy=multi-user.target

Fallstricke und Sonderfaelle

ExecStart muss absoluter Pfad sein. Sie koennen keine Shell-Built-ins, Pipes oder Umleitungen direkt in ExecStart verwenden. Kapseln Sie komplexe Befehle in einem Skript.

Expansion von Umgebungsvariablen. systemd expandiert $VAR in ExecStart, wenn die Variable ueber Environment= oder EnvironmentFile= gesetzt wird, laedt aber KEINE Shell-Initialisierungsdateien. Ihr ~/.bashrc wird niemals geladen.

Prozentzeichen in Unit-Dateien. In Unit-Dateien ist % ein Spezifikatorzeichen. Um ein echtes Prozentzeichen zu verwenden, escapen Sie es als %%.

DynamicUser und Dateiberechtigungen. Bei DynamicUser=true aendert sich die zugeteilte UID bei jedem Neustart. Verwenden Sie Direktiven wie StateDirectory= oder LogsDirectory=, damit systemd persistente Verzeichnisse korrekt verwaltet.

After= vs. Requires=. After= kontrolliert nur die Reihenfolge — es erstellt keine Abhaengigkeit. Requires= erstellt die Abhaengigkeit, kontrolliert aber nicht die Reihenfolge. Sie wollen fast immer beide zusammen.


Fehlerbehebung

Dienst startet nicht

sudo systemctl status meinapi.service
journalctl -u meinapi.service -b
journalctl -u meinapi.service --since "10 minutes ago"
journalctl -u meinapi.service -f

Dienst startet, stuerzt aber sofort ab

journalctl -u meinapi.service -n 100 --no-pager
ls -la /usr/bin/node
sudo -u nodeapi /usr/bin/node /opt/meinapi/server.js

Fehlgeschlagenen Dienst zuruecksetzen

sudo systemctl reset-failed meinapi.service
sudo systemctl start meinapi.service

Sicherheitsexposition pruefen

systemd-analyze security meinapi.service
systemd-analyze verify /etc/systemd/system/meinapi.service

Zusammenfassung

  • Unit-Dateien befinden sich in /etc/systemd/system/ und haben drei Abschnitte: [Unit], [Service] und [Install]
  • Fuehren Sie immer systemctl daemon-reload nach dem Bearbeiten einer Unit-Datei aus
  • Verwenden Sie Type=simple fuer die meisten modernen Apps; verwenden Sie Type=notify fuer Apps, die Bereitschaft signalisieren
  • Speichern Sie Geheimnisse in einer separaten EnvironmentFile= mit Berechtigung 600, nicht direkt in der Unit-Datei
  • Konfigurieren Sie Neustart-Richtlinien mit Restart=on-failure und StartLimitBurst um vorueber gehende Fehler zu ueberstehen
  • Wenden Sie Sicherheitsdirektiven auf jeden Produktionsdienst an
  • Ersetzen Sie Cron-Jobs durch Timer-Units fuer besseres Logging
  • Debuggen Sie mit journalctljournalctl -u dienstname -f ist Ihr erster Anlaufpunkt bei Fehlern

Verwandte Artikel