systemd verstehen – Services, Timer und Logs auf Linux
systemd ist das Init-System aller großen Linux-Distributionen. Es startet das System, verwaltet Dienste, schreibt Logs und plant Tasks. Wer systemd versteht, hat die volle Kontrolle über sein Linux-System.
Dieser Guide erklärt, wie systemd funktioniert, wie du eigene Services erstellst und wie du Logs mit journalctl effektiv auswertest.
systemd-Grundkonzepte: Units und Targets
systemd verwaltet Units – abstrakte Bausteine des Systems. Jede Unit ist eine Konfigurationsdatei.
Unit-Typen
| Typ | Dateiendung | Zweck |
|---|---|---|
| Service | .service |
Daemon/Dienst starten |
| Timer | .timer |
Geplante Ausführung |
| Socket | .socket |
Socket-Aktivierung |
| Mount | .mount |
Dateisysteme einhängen |
| Target | .target |
Gruppe von Units (wie Runlevel) |
| Path | .path |
Datei-/Verzeichnisänderungen überwachen |
Unit-Dateipfade
/usr/lib/systemd/system/ ← vom Paketmanager installierte Units (nicht ändern!)
/etc/systemd/system/ ← eigene und überschriebene Units (hier arbeiten!)
~/.config/systemd/user/ ← User-Units (ohne root)
Targets (früher: Runlevel)
# Aktuelles Target anzeigen
systemctl get-default
# graphical.target (Desktop-System)
# multi-user.target (Server ohne GUI)
# Verfügbare Targets
systemctl list-units --type=target
systemctl: Dienste verwalten
Die wichtigsten Befehle
# Status eines Dienstes
sudo systemctl status nginx
# Starten / Stoppen / Neu starten
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
# Neu laden ohne Unterbrechung (wenn der Dienst es unterstützt)
sudo systemctl reload nginx
# Aktivieren (beim Systemstart automatisch starten)
sudo systemctl enable nginx
# Deaktivieren
sudo systemctl disable nginx
# Aktivieren UND sofort starten (in einem Schritt)
sudo systemctl enable --now nginx
# Deaktivieren UND sofort stoppen
sudo systemctl disable --now nginx
Überblick über alle Services
# Alle aktiven Units anzeigen
systemctl list-units
# Nur Services
systemctl list-units --type=service
# Alle Services (auch inaktive)
systemctl list-units --type=service --all
# Fehlgeschlagene Units
systemctl --failed
# Unit-Datei anzeigen
systemctl cat nginx.service
# Abhängigkeiten anzeigen
systemctl list-dependencies nginx.service
System-Befehle
# System neu starten
sudo systemctl reboot
# Herunterfahren
sudo systemctl poweroff
# In Rescue-Modus wechseln (Single User)
sudo systemctl rescue
# systemd selbst neu laden (nach Unit-Datei-Änderungen)
sudo systemctl daemon-reload
Wichtig: Nach dem Erstellen oder Ändern einer Unit-Datei immer
sudo systemctl daemon-reloadausführen!
journalctl: Logs lesen und filtern
systemd schreibt alle Service-Logs in das Journal – eine strukturierte, binäre Log-Datenbank. journalctl ist das Auswertungstool.
Basis-Befehle
# Alle Logs (neuste zuletzt)
journalctl
# Neuste Logs zuerst
journalctl -r
# Letzte 50 Zeilen
journalctl -n 50
# Logs live verfolgen (wie tail -f)
journalctl -f
# Logs eines bestimmten Dienstes
journalctl -u nginx
journalctl -u nginx -f # Live
journalctl -u nginx -n 100 # Letzte 100 Zeilen
Zeitbasiertes Filtern
# Logs seit heute
journalctl --since today
# Logs der letzten Stunde
journalctl --since "1 hour ago"
# Logs in Zeitraum
journalctl --since "2026-02-18 10:00" --until "2026-02-18 12:00"
# Logs seit letztem Boot
journalctl -b
# Logs des vorletzten Boots
journalctl -b -1
# Alle verfügbaren Boots anzeigen
journalctl --list-boots
Priorität filtern
# Nur Fehler und kritisches
journalctl -p err
# Nur Warnungen und schlimmer
journalctl -p warning
# Prioritäten: emerg, alert, crit, err, warning, notice, info, debug
Ausgabeformate
# JSON-Format (für Scripting)
journalctl -u nginx -o json | head -5
# Kurzes Format (nur Nachricht)
journalctl -u nginx -o cat
# Verbose (alle Felder)
journalctl -u nginx -o verbose
Disk-Verbrauch
# Aktueller Journal-Speicherverbrauch
journalctl --disk-usage
# Altes Journal löschen (älter als 2 Wochen)
sudo journalctl --vacuum-time=2weeks
# Journal auf max. 500 MB begrenzen
sudo journalctl --vacuum-size=500M
Eigenen Service erstellen
Beispiel: Node.js-App als Service
Angenommen, deine App liegt in /opt/meine-app/ und wird mit node server.js gestartet.
# Service-Datei erstellen
sudo nano /etc/systemd/system/meine-app.service
[Unit]
Description=Meine Node.js Anwendung
Documentation=https://beispiel.de/docs
After=network.target # Erst starten wenn Netzwerk verfügbar
Wants=network-online.target
[Service]
Type=simple
User=www-data # Nicht als root laufen!
Group=www-data
WorkingDirectory=/opt/meine-app
# Umgebungsvariablen
Environment=NODE_ENV=production
Environment=PORT=3000
# Oder aus Datei laden:
# EnvironmentFile=/opt/meine-app/.env
# Startbefehl
ExecStart=/usr/bin/node /opt/meine-app/server.js
# Neustart bei Absturz
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=3
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=meine-app
[Install]
WantedBy=multi-user.target
# systemd informieren
sudo systemctl daemon-reload
# Aktivieren und starten
sudo systemctl enable --now meine-app
# Status prüfen
sudo systemctl status meine-app
# Logs prüfen
journalctl -u meine-app -f
Beispiel: Python/Gunicorn App
[Unit]
Description=Gunicorn für Django-App
After=network.target
[Service]
Type=notify
User=deploy
Group=deploy
WorkingDirectory=/opt/django-app
Environment=DJANGO_SETTINGS_MODULE=config.settings.production
EnvironmentFile=/opt/django-app/.env
ExecStart=/opt/django-app/.venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/gunicorn.sock \
--timeout 120 \
config.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
Beispiel: Einfaches Shell-Skript als Service
[Unit]
Description=Mein Backup-Skript
After=network.target
[Service]
Type=oneshot # Läuft einmal durch, dann fertig
User=root
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Service-Typen und Optionen
Service-Typen (Type=)
| Typ | Verhalten |
|---|---|
simple |
Prozess ist der Service (Standard) |
forking |
Elternprozess forkt und beendet sich |
oneshot |
Führt aus und beendet sich (für Skripte) |
notify |
Service sendet Ready-Signal (Gunicorn, etc.) |
idle |
Startet erst wenn andere Jobs fertig |
Sicherheitsoptionen (empfohlen)
[Service]
# Dateisystem schützen
ProtectSystem=strict # /usr, /boot read-only
ProtectHome=true # /home, /root, /run/user nicht sichtbar
PrivateTmp=true # Eigenes /tmp-Verzeichnis
# Netzwerk einschränken (wenn kein Netzwerk benötigt)
PrivateNetwork=true
# Capabilities einschränken
NoNewPrivileges=true
CapabilityBoundingSet=
# Schreibzugriff nur auf bestimmte Verzeichnisse
ReadWritePaths=/opt/meine-app/data
Restart-Optionen
Restart=no # Kein Neustart
Restart=always # Immer neu starten
Restart=on-failure # Nur bei Fehler
Restart=on-abnormal # Bei Signal oder Timeout
RestartSec=5s # Wartezeit vor Neustart
Ressourcenlimits
[Service]
# RAM-Limit
MemoryMax=512M
# CPU-Limit
CPUQuota=50%
# Maximale Dateien
LimitNOFILE=65535
# Maximale Prozesse
LimitNPROC=100
systemd Timer: Cron-Alternative
systemd Timer sind moderner als Cron: bessere Logging, Abhängigkeiten, Kalender-Ausdrücke.
Timer für Backup (täglich um 02:00 Uhr)
Service-Datei (/etc/systemd/system/backup.service):
[Unit]
Description=Tägliches Backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=root
Timer-Datei (/etc/systemd/system/backup.timer):
[Unit]
Description=Tägliches Backup um 02:00 Uhr
[Timer]
OnCalendar=*-*-* 02:00:00 # Täglich um 02:00
RandomizedDelaySec=300 # Bis zu 5 Min Zufallsverzögerung (verhindert Last-Spitzen)
Persistent=true # Nachholen wenn System aus war
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
# Status des Timers
systemctl status backup.timer
# Alle aktiven Timer anzeigen
systemctl list-timers
# Timer manuell auslösen
sudo systemctl start backup.service
Kalender-Ausdrücke
OnCalendar=*-*-* 02:00:00 # Täglich um 02:00
OnCalendar=Mon *-*-* 00:00:00 # Montags um Mitternacht
OnCalendar=weekly # Einmal pro Woche (Monntag 00:00)
OnCalendar=monthly # Einmal pro Monat (1. um 00:00)
OnCalendar=*-*-* *:00:00 # Jede Stunde
# Zeitpunkt testen
systemd-analyze calendar "Mon *-*-* 02:00:00"
OnBootSec / OnActiveSec (relativ)
[Timer]
OnBootSec=5min # 5 Minuten nach Boot
OnUnitActiveSec=1h # Jede Stunde nach letzter Ausführung
Service-Debugging
Wenn ein Service nicht startet
# 1. Status anzeigen (zeigt oft direkt den Fehler)
sudo systemctl status meine-app
# 2. Logs der letzten Starts
journalctl -u meine-app --no-pager -n 50
# 3. Fehler beim letzten Start
journalctl -u meine-app -b --priority=err
# 4. Service manuell im Vordergrund ausführen (für Debugging)
sudo -u www-data /usr/bin/node /opt/meine-app/server.js
Syntax-Check einer Unit-Datei
# Unit analysieren (zeigt auch Warnungen)
systemd-analyze verify /etc/systemd/system/meine-app.service
systemd-Analyse
# Boot-Zeit analysieren
systemd-analyze
# Welche Services verlangsamen den Boot?
systemd-analyze blame
# Kritischen Pfad anzeigen
systemd-analyze critical-chain
Häufige Probleme
Unit not found
sudo systemctl status nicht-existiert
# Unit nicht-existiert.service could not be found.
# Datei existiert?
ls /etc/systemd/system/meine-app.service
# daemon-reload vergessen?
sudo systemctl daemon-reload
Service läuft, aber App antwortet nicht
# Prüfe ob der Prozess läuft
sudo systemctl status meine-app | grep PID
ps aux | grep node
# Port offen?
sudo ss -ltnp | grep :3000
EnvironmentFile nicht gefunden
meine-app.service: Failed to load environment files: /opt/meine-app/.env: No such file or directory
# Datei erstellen oder Pfad korrigieren
sudo nano /opt/meine-app/.env
sudo systemctl restart meine-app
Permission denied beim Start
# Benutzer prüfen (User= in [Service])
sudo systemctl cat meine-app | grep User
# Existiert der Benutzer?
id www-data
# Hat er Zugriff auf WorkingDirectory?
sudo -u www-data ls /opt/meine-app
Fazit
systemd ist mächtig, konsistent und gut dokumentiert. Die wichtigsten Takeaways:
systemctl enable --now→ aktivieren und starten in einem Schrittjournalctl -u service -f→ Logs live verfolgensudo systemctl daemon-reload→ immer nach Unit-Datei-Änderungen!- Timer statt Cron für neue Tasks (bessere Logs, Dependencies)
- Sicherheitsoptionen (
PrivateTmp,ProtectSystem) immer setzen