Das Problem, das ich hatte
Meine Node.js-App lief auf einem Server. Jedes Mal nach einem Reboot musste ich:
- Per SSH einloggen
- Ins Verzeichnis wechseln
node app.jsausführen- tmux nutzen, damit es weiterläuft
Das nervte.
Dann hab ich systemd entdeckt: Ein Service-Manager, der Apps automatisch startet, überwacht und neu startet bei Abstürzen.
Was ist systemd?
Die kurze Antwort: systemd ist der Standard-Service-Manager in modernen Linux-Systemen. Er startet, stoppt und überwacht Programme.
Was systemd kann:
- ✅ Apps automatisch beim Systemstart starten
- ✅ Abstürze erkennen und App neu starten
- ✅ Logs zentral sammeln
- ✅ Ressourcen-Limits setzen
- ✅ Abhängigkeiten verwalten
Die Analogie: systemd ist wie ein Aufpasser für deine Apps:
- Startet sie beim Booten
- Überwacht sie während sie laufen
- Startet sie neu, wenn sie abstürzen
- Protokolliert alles, was passiert
Was du brauchst
- Linux-System mit systemd (Ubuntu 16.04+, Debian 8+, CentOS 7+)
- Root-Zugriff (sudo)
- Eine App, die du als Service laufen lassen willst
- 15 Minuten Zeit
Schritt 1: Einfachen Service erstellen
Szenario
Du hast eine Node.js-App unter /home/alex/myapp/app.js
Service-Datei erstellen
sudo nano /etc/systemd/system/myapp.service
Basis-Config
[Unit]
Description=My Node.js Application
After=network.target
[Service]
Type=simple
User=alex
WorkingDirectory=/home/alex/myapp
ExecStart=/usr/bin/node /home/alex/myapp/app.js
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Was bedeutet das?
[Unit]-Section:
Description– Name/Beschreibung des ServiceAfter– Startet nach Netzwerk (wichtig für Web-Apps)
[Service]-Section:
Type=simple– Einfacher Prozess (Standard)User– Als welcher User läuft die AppWorkingDirectory– In welchem Verzeichnis startet die AppExecStart– Der Befehl zum StartenRestart=on-failure– Neustart bei AbsturzRestartSec=10– Warte 10 Sekunden vor Neustart
[Install]-Section:
WantedBy=multi-user.target– Startet im normalen Multi-User-Modus
Service aktivieren und starten
# systemd neu laden (erkennt neuen Service)
sudo systemctl daemon-reload
# Service starten
sudo systemctl start myapp.service
# Status prüfen
sudo systemctl status myapp.service
# Autostart aktivieren (startet bei Boot)
sudo systemctl enable myapp.service
Testen:
# Server neu starten
sudo reboot
# Nach Reboot: Status prüfen
sudo systemctl status myapp.service
# Sollte "active (running)" zeigen
Schritt 2: Service-Befehle
Grundlegende Befehle
# Service starten
sudo systemctl start myapp.service
# Service stoppen
sudo systemctl stop myapp.service
# Service neu starten
sudo systemctl restart myapp.service
# Config neu laden (ohne Neustart)
sudo systemctl reload myapp.service
# Status anzeigen
sudo systemctl status myapp.service
# Autostart aktivieren
sudo systemctl enable myapp.service
# Autostart deaktivieren
sudo systemctl disable myapp.service
# Ist Autostart aktiv?
sudo systemctl is-enabled myapp.service
# Läuft der Service?
sudo systemctl is-active myapp.service
Logs anzeigen
# Logs anzeigen
sudo journalctl -u myapp.service
# Logs live verfolgen (wie tail -f)
sudo journalctl -u myapp.service -f
# Letzte 50 Zeilen
sudo journalctl -u myapp.service -n 50
# Logs seit heute
sudo journalctl -u myapp.service --since today
# Logs der letzten Stunde
sudo journalctl -u myapp.service --since "1 hour ago"
Service-Typen verstehen
Type=simple (Standard)
[Service]
Type=simple
ExecStart=/usr/bin/node app.js
Wann nutzen:
- App läuft dauerhaft (Web-Server, API, etc.)
- Prozess forkt nicht
Beispiel: Node.js, Python Flask, Ruby Rails
Type=forking
[Service]
Type=forking
PIDFile=/var/run/myapp.pid
ExecStart=/usr/bin/myapp --daemon
Wann nutzen:
- App startet im Hintergrund (fork)
- Klassische Daemon-Programme
Beispiel: Nginx, Apache (wenn manuell gestartet)
Type=oneshot
[Service]
Type=oneshot
ExecStart=/usr/bin/backup.sh
RemainAfterExit=yes
Wann nutzen:
- Script läuft einmal und beendet sich
- Backup-Scripts, Setup-Tasks
Beispiel: Backup-Scripts, Initialisierung
Type=notify
[Service]
Type=notify
ExecStart=/usr/bin/myapp
Wann nutzen:
- App unterstützt systemd-Notification
- Fortgeschrittene Apps
Beispiel: systemd-aware Apps
Erweiterte Konfiguration
Umgebungsvariablen setzen
[Service]
Environment="NODE_ENV=production"
Environment="PORT=3000"
Environment="API_KEY=secret123"
Oder aus Datei laden:
[Service]
EnvironmentFile=/etc/myapp/env
Datei /etc/myapp/env:
NODE_ENV=production
PORT=3000
API_KEY=secret123
Wichtig: Nie Secrets direkt in Service-Datei! Nutze EnvironmentFile mit chmod 600.
Ressourcen-Limits
[Service]
# Max. 512 MB RAM
MemoryLimit=512M
# Max. 50% CPU (1 Core = 100%)
CPUQuota=50%
# Max. 1000 Tasks (Threads/Processes)
TasksMax=1000
# Max. File Descriptors
LimitNOFILE=4096
Neustart-Strategien
[Service]
# Immer neu starten
Restart=always
# Nur bei Fehler neu starten
Restart=on-failure
# Bei Fehler und abnormalem Beenden
Restart=on-abnormal
# Niemals neu starten
Restart=no
# Warte 5 Sekunden vor Neustart
RestartSec=5
# Max. 5 Neustarts in 10 Sekunden
StartLimitBurst=5
StartLimitIntervalSec=10
Abhängigkeiten
[Unit]
# Startet nach diesen Services
After=network.target postgresql.service
# Benötigt diese Services (startet sie automatisch)
Requires=postgresql.service
# Sollte nach diesen Services starten (aber nicht zwingend)
Wants=redis.service
Praxisbeispiele
Beispiel 1: Node.js Express App
[Unit]
Description=Express API Server
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/api
EnvironmentFile=/etc/api/env
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=10
# Sicherheit
NoNewPrivileges=true
PrivateTmp=true
# Limits
MemoryLimit=1G
CPUQuota=100%
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=api
[Install]
WantedBy=multi-user.target
Beispiel 2: Python Flask App mit Gunicorn
[Unit]
Description=Flask App with Gunicorn
After=network.target
[Service]
Type=notify
User=flask
Group=flask
WorkingDirectory=/opt/flaskapp
Environment="PATH=/opt/flaskapp/venv/bin"
EnvironmentFile=/etc/flaskapp/env
ExecStart=/opt/flaskapp/venv/bin/gunicorn \
--workers 4 \
--bind 127.0.0.1:5000 \
wsgi:app
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Beispiel 3: Backup-Script (täglich)
Service-Datei:
sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Daily Backup Script
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
StandardOutput=journal
StandardError=journal
Timer-Datei:
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
# Täglich um 2 Uhr nachts
OnCalendar=daily
OnCalendar=*-*-* 02:00:00
# Falls Server aus war: Nach Boot nachholen
Persistent=true
[Install]
WantedBy=timers.target
Aktivieren:
sudo systemctl daemon-reload
sudo systemctl enable backup.timer
sudo systemctl start backup.timer
# Status prüfen
sudo systemctl list-timers
Beispiel 4: Docker Container als Service
[Unit]
Description=My Docker Container
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker run -d \
--name myapp \
--restart unless-stopped \
-p 8080:80 \
myapp:latest
ExecStop=/usr/bin/docker stop myapp
ExecStopPost=/usr/bin/docker rm myapp
[Install]
WantedBy=multi-user.target
Häufige Fehler (die ich gemacht habe)
Fehler 1: “Failed to start service”
Problem:
sudo systemctl start myapp.service
# Job for myapp.service failed. See 'systemctl status myapp.service'
Ursache: Syntax-Fehler in Service-Datei oder falscher Pfad.
Lösung:
# Status anzeigen
sudo systemctl status myapp.service
# Detaillierte Logs
sudo journalctl -xeu myapp.service
# Config prüfen
systemd-analyze verify /etc/systemd/system/myapp.service
Fehler 2: App läuft, stoppt aber sofort
Problem: Service startet, zeigt aber direkt “inactive (dead)”.
Ursache:
Type=simpleaber App beendet sich sofort- Fehlender
WorkingDirectory - Permissions-Problem
Lösung:
# Manuell testen
sudo -u alex /usr/bin/node /home/alex/myapp/app.js
# Falls Fehler: In Service-Datei anpassen
Fehler 3: “Permission denied”
Problem: Service startet nicht, Logs zeigen Permission-Fehler.
Lösung:
# User/Group in Service-Datei prüfen
# Permissions auf App-Verzeichnis setzen
sudo chown -R alex:alex /home/alex/myapp
sudo chmod -R 755 /home/alex/myapp
# Executable-Flag auf Script
sudo chmod +x /home/alex/myapp/app.js
Fehler 4: Umgebungsvariablen fehlen
Problem: App startet, aber findet .env-Variablen nicht.
Lösung:
# In Service-Datei:
[Service]
EnvironmentFile=/home/alex/myapp/.env
# ODER absoluten Pfad in ExecStart angeben
WorkingDirectory=/home/alex/myapp
Sicherheits-Best-Practices
1. Eigener User für Service
# User erstellen (ohne Login-Shell)
sudo useradd -r -s /bin/false myapp
# App-Verzeichnis zuweisen
sudo chown -R myapp:myapp /opt/myapp
In Service-Datei:
[Service]
User=myapp
Group=myapp
2. Sandboxing aktivieren
[Service]
# Kein Zugriff auf /home
ProtectHome=true
# Read-only /usr, /boot, /etc
ProtectSystem=strict
# Eigenes /tmp
PrivateTmp=true
# Keine neuen Privileges
NoNewPrivileges=true
# Schreib-Zugriff nur auf bestimmte Verzeichnisse
ReadWritePaths=/var/www/myapp/uploads
3. Secrets schützen
# EnvironmentFile nur für Root lesbar
sudo chmod 600 /etc/myapp/secrets.env
sudo chown root:root /etc/myapp/secrets.env
[Service]
EnvironmentFile=/etc/myapp/secrets.env
Service debuggen
Schritt 1: Status prüfen
sudo systemctl status myapp.service
Achte auf:
Active: active (running)– Läuft?Main PID– Prozess-ID- Letzte Log-Zeilen
Schritt 2: Logs anschauen
# Alle Logs
sudo journalctl -u myapp.service
# Nur Fehler
sudo journalctl -u myapp.service -p err
# Live verfolgen
sudo journalctl -u myapp.service -f
Schritt 3: Manuell testen
# Als Service-User ausführen
sudo -u alex /usr/bin/node /home/alex/myapp/app.js
# Falls Fehler: Pfade/Permissions prüfen
Schritt 4: Config verifizieren
# Syntax prüfen
systemd-analyze verify /etc/systemd/system/myapp.service
# Service-Abhängigkeiten anzeigen
systemctl list-dependencies myapp.service
Nützliche systemctl-Befehle
# Alle laufenden Services
systemctl list-units --type=service --state=running
# Alle geladenen Services
systemctl list-units --type=service
# Fehlgeschlagene Services
systemctl --failed
# Service neu starten und Logs live anzeigen
sudo systemctl restart myapp.service && sudo journalctl -u myapp.service -f
# CPU/RAM-Verbrauch aller Services
systemd-cgtop
# Boot-Zeit analysieren
systemd-analyze blame
Timer statt Cron
Warum Timer statt Cron?
Vorteile von systemd-Timern:
- ✅ Besseres Logging (journalctl)
- ✅ Abhängigkeiten möglich
- ✅ Systemd-integriert
- ✅ Persistent (führt versäumte Jobs nach)
Timer erstellen
Service:
[Unit]
Description=Cleanup Old Logs
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup-logs.sh
Timer:
[Unit]
Description=Run Cleanup Daily
[Timer]
# Täglich um 3 Uhr
OnCalendar=*-*-* 03:00:00
# Starte 10 Min nach Boot (falls Server um 3 Uhr aus war)
Persistent=true
[Install]
WantedBy=timers.target
Aktivieren:
sudo systemctl daemon-reload
sudo systemctl enable cleanup.timer
sudo systemctl start cleanup.timer
# Nächster Run?
sudo systemctl list-timers
Fazit
systemd hat meine Server-Verwaltung professionalisiert.
Vorher:
- ❌ Manuelle App-Starts nach Reboots
- ❌ Keine Überwachung bei Abstürzen
- ❌ Logs überall verstreut
- ❌ Cron-Jobs ohne Logging
Nachher:
- ✅ Apps starten automatisch beim Boot
- ✅ Auto-Restart bei Absturz
- ✅ Zentrale Logs via journalctl
- ✅ Timer statt Cron (mit besserem Logging)
Zeitaufwand zum Lernen: 2-3 Stunden Schwierigkeitsgrad: Anfänger-Mittel Lohnt sich? Absolut. systemd ist Standard in Production.
Mein Tipp: Fang mit einem einfachen Service an (Node.js, Python). Wenn das läuft, erweitere mit Logging, Limits, Sandboxing.
Bei Fragen: schneider@alexle135.de
Quellen:
- systemd Documentation
- DigitalOcean systemd Guide
- Arch Wiki - systemd
- Eigene Praxiserfahrung (14 Monate systemd)