Systemd Service Units: Linux-Dienste richtig verwalten

Learn how to create custom systemd service units, manage Linux services, and set up automatic monitoring with practical examples for web developers.

Systemd Service Units: Linux-Dienste richtig verwalten

Wenn ich eine Node-App, ein Python-Script oder irgendeinen sonstigen Long-Running-Prozess auf einem Server starten will, ist systemd meine erste Wahl. Nicht PM2, nicht Docker, nicht supervisord. Der Grund ist banal: systemd ist auf jedem aktuellen Linux-System schon da, das Logging integriert sich mit journalctl, und Auto-Restart, Resource-Limits, Dependencies sind alles One-Liner in der Unit-Datei.

PM2 ist okay, wenn man im Node-Universum lebt und einen Prozess-Manager mit Web-Interface will. Aber für alles andere — und sobald mehrere Sprachen im Spiel sind — ist systemd die robuste Wahl.

Eine simple Unit für eine Node-App

/etc/systemd/system/portfolio.service:

[Unit]
Description=Portfolio Next.js
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/root/portfolio
EnvironmentFile=/root/portfolio/.env.local
ExecStart=/usr/bin/node node_modules/.bin/next start -p 3003
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Aktivieren:

systemctl daemon-reload
systemctl enable --now portfolio.service

Und das war's. Der Service startet beim Boot, restartet bei Crashes, loggt nach journalctl -u portfolio.service.

Was die einzelnen Felder tun

Type=simple ist der Default und passt für die meisten modernen Apps. Der Prozess läuft im Vordergrund — systemd sieht den Hauptprozess als den ExecStart-Prozess. Andere Typen (forking, notify, oneshot) sind für spezielle Fälle.

Restart=on-failure — restartet nur bei Exit-Code != 0. Restart=always würde auch bei sauberen Exits restarten, was selten gewollt ist.

RestartSec=5 — wartet 5 Sekunden vor dem Restart. Verhindert Restart-Loops, die das System fluten.

EnvironmentFile= — lädt Variablen aus einer Datei. Ich nutze das für Secrets, statt sie direkt in die Unit zu schreiben.

After=network.target — startet nach dem Netzwerk. Bei Apps, die DB-Verbindungen brauchen: After=postgresql.service.

Logging mit journalctl

journalctl -u portfolio.service          # alles
journalctl -u portfolio.service -f       # follow (wie tail -f)
journalctl -u portfolio.service --since "1 hour ago"
journalctl -u portfolio.service -p err   # nur Errors
journalctl -u portfolio.service -n 100   # letzte 100 Zeilen

journalctl ist mächtiger als reines tail -f über eine Logdatei. Strukturiert, filterbar, persistent über Reboots.

Ressourcen-Limits

Ein Node-Prozess, der unbeschränkt RAM nimmt, kann den Server killen. systemd kann das beschränken:

[Service]
MemoryMax=512M
CPUQuota=50%
TasksMax=100

MemoryMax killt den Prozess, wenn er die Grenze überschreitet (OOMKill). Bei MemoryHigh wird er nur throttled. Für Hobby-Apps reicht MemoryMax.

Security-Hardening

systemd hat eine ganze Reihe von Optionen, die den Service-Prozess in eine Art Sandbox stecken:

[Service]
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/log/myapp /var/lib/myapp

ProtectSystem=strict macht das gesamte Filesystem read-only, bis auf explizite Ausnahmen via ReadWritePaths. Eine kompromittierte App kann nichts überschreiben, was sie nicht explizit darf.

Diese Optionen sind underused. Sie kosten nichts (außer 5 Zeilen Konfiguration) und bringen echten Schutz.

Timer statt Cron

Für periodische Aufgaben nutze ich systemd-Timer statt Cron — bessere Logging-Integration und einfacheres Debugging.

/etc/systemd/system/backup.service:

[Unit]
Description=Daily Backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

/etc/systemd/system/backup.timer:

[Unit]
Description=Daily Backup Timer

[Timer]
OnCalendar=daily
RandomizedDelaySec=30m
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true heißt: wenn der Timer mal nicht laufen konnte (Server war aus), wird er beim nächsten Start nachgeholt. Das macht Cron nicht.

systemctl enable --now backup.timer aktiviert ihn. systemctl list-timers zeigt, wann er nächstes Mal feuert.

Häufige Fehler

Vergessen daemon-reload nach Änderungen. systemd cached Unit-Dateien — neue Änderungen werden nur nach systemctl daemon-reload aktiv.

Pfade als relative Pfade. ExecStart braucht absoluten Pfad. node reicht nicht, /usr/bin/node ja.

Falscher User. Wenn der User keine Rechte hat, auf das WorkingDirectory zuzugreifen oder den Port zu binden, scheitert der Start kommentarlos. journalctl -u service.name zeigt's, aber man muss hingucken.

Was systemd nicht ist

Kein Prozess-Manager fürs Frontend mit Real-Time-Charts. Kein Cluster-Tool. Kein Auto-Scaler. Es startet Prozesse, hält sie am Leben, loggt was sie ausgeben. Das reicht für 95 % aller Server-Workloads.

Wer mehr braucht, nimmt Kubernetes. Aber bis dahin: systemd, immer.