Ein systemd-Unit ist die Beschreibungseinheit für alles, was systemd verwaltet: Dienste, Zeitgeber, Einhängepunkte, Geräte und mehr. Wer einen eigenen Dienst dauerhaft betreiben will, kommt um eine Unit-Datei nicht herum.

\n

Unit-Typen

\n
    \n
  • .service – Dienste und einmalige Befehle.
  • \n
  • .timer – zeitgesteuerte Auslösung (Ersatz für cron).
  • \n
  • .socket – warten auf Verbindungen und den Dienst bei Bedarf starten.
  • \n
  • .target – Gruppierung (vergleichbar mit einer Laufwerksebene).
  • \n
  • .mount / .automount – Einhängepunkte.
  • \n
  • .path – Überwachung von Dateien oder Verzeichnissen.
  • \n
\n

Wo Unit-Dateien liegen

\n
# Reihenfolge und Vorrang (spaeter gewinnt)\n/usr/lib/systemd/system/       Pakete - NICHT bearbeiten, wird ueberschrieben\n/etc/systemd/system/           eigene Units und Ueberschreibungen  <- HIER arbeiten\n/run/systemd/system/           zur Laufzeit erzeugt\n\n# Eigene Unit anlegen\n/etc/systemd/system/mein-dienst.service\n\n# Bestehende Unit aendern, OHNE die Originaldatei anzufassen:\nsystemctl edit mein-dienst.service\n#   erzeugt /etc/systemd/system/mein-dienst.service.d/override.conf\n#   dort nur die geaenderten Zeilen eintragen\n#   -> bleibt bei Paketaktualisierungen erhalten\n\n# Auch moeglich: eine Kopie der Unit nehmen und deren Pfade anpassen\ncp /usr/lib/systemd/system/nginx.service /etc/systemd/system/Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein.\n#   und darin die Name=-Zeile und Pfade aendern - nie beide starten!
\n

Eine Dienst-Unit von Hand

\n
[Unit]\nDescription=Beispiel-API-Server\nDocumentation=https://example.org/api\n# Erst starten, wenn diese Einheiten bereit sind\nAfter=network-online.target postgresql.service\nWants=network-online.target\n# Nicht starten, wenn diese laufen (Konflikt)\nConflicts=apache2.service\n\n[Service]\nType=simple\nUser=api\nGroup=api\nWorkingDirectory=/srv/api\nEnvironment=NODE_ENV=production\nEnvironment="PORT=8080"\n# Geheimnisse NICHT in der Unit - eigene Datei mit chmod 600\nEnvironmentFile=-/etc/api/api.env\n\nExecStart=/usr/bin/node /srv/api/server.js\n# Sanft beenden, dann hart - wichtig fuer Datenintegritaet\nExecReload=/bin/kill -HUP $MAINPID\nKillSignal=SIGTERM\nTimeoutStopSec=30\nRestart=on-failure\nRestartSec=5\n\n# Protokoll\nStandardOutput=journal\nStandardError=journal\nSyslogIdentifier=api\n\n# Absicherung - die wichtigsten Optionen\nNoNewPrivileges=true\nPrivateTmp=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/srv/api/data /var/log/api\nProtectKernelTunables=true\nProtectKernelModules=true\nProtectControlGroups=true\nRestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX\nRestrictNamespaces=true\nLockPersonality=true\nMemoryDenyWriteExecute=true\n\n# Ressourcen begrenzen\nLimitNOFILE=65536\nMemoryMax=1G\nCPUQuota=200%\n\n[Install]\nWantedBy=multi-user.target
\n

Die vier Type-Werte

\n
    \n
  • simple (Standard): Der Befehl startet und läuft weiter. systemd hält ihn ab dem Start für aktiv.
  • \n
  • exec: Wie simple, aber der Start gilt erst als erfolgreich, wenn der eigentliche Programmaufruf gelungen ist. Sicherer gegen Fehler in Wrapper-Skripten.
  • \n
  • forking: Für klassische Daemons, die sich selbst in den Hintergrund versetzen und die Startprozedur beenden. Braucht meist PIDFile=.
  • \n
  • oneshot: Einmalige Aufgabe, die beendet werden soll. Richtig für Skripte mit RemainAfterExit=yes, wenn danach der Zustand als erreicht gelten soll. Auch für Vorbereitungsläufe mit Before=.
  • \n
  • notify: Das Programm meldet seine Bereitschaft selbst (sd_notify). Am besten geeignet für Dienste, die einen längeren Anlauf haben – systemd wartet dann zuverlässig.
  • \n
\n

Steuern und Diagnose

\n
# Nach jeder Aenderung an Unit-Dateien PFLICHT\nsystemctl daemon-reload\n\n# Steuern\nsystemctl enable --now mein-dienst\nsystemctl disable --now mein-dienst\nsystemctl start | stop | restart | reload mein-dienst\nsystemctl status mein-dienst          # Zustand, letzte Zeilen, Prozess\n\n# Ist der Dienst aktiv?\nsystemctl is-active mein-dienst\nsystemctl is-enabled mein-dienst\nsystemctl is-failed mein-dienst\n\n# Warum laeuft er nicht? - die wichtigste Fragestellung\nsystemctl status mein-dienst\njournalctl -u mein-dienst -n 100 --no-pager\njournalctl -u mein-dienst -f                      # mitlesen\njournalctl -u mein-dienst --since '10 min ago'\njournalctl -u mein-dienst -p err                  # nur Fehler\n\n# Abhaengigkeiten und Startreihenfolge verstehen\nsystemctl list-dependencies mein-dienst\nsystemctl list-dependencies --reverse mein-dienst\nsystemctl show mein-dienst | grep -E 'After|Wants|Requires'\n\n# Was ist schiefgegangen?\nsystemctl show mein-dienst -p Restart -p RestartUSec -p NRestarts -p Result\njournalctl -u mein-dienst | grep -iE 'fail|error|denied|not found'\n\n# Alle fehlerhaften Units\nsystemctl --failed\nsystemctl list-units --state=failed\n\n# Analyse der Startzeit\nsystemd-analyze blame | head -20\nsystemd-analyze critical-chain
\n

Timer statt cron

\n
# /etc/systemd/system/backup.timer\n[Unit]\nDescription=Taegliche Sicherung\n\n[Timer]\nOnCalendar=*-*-* 03:30:00\n# Wenn der Rechner zur geplanten Zeit aus war:\nPersistent=true\n# Zufaellige Verzoegerung - entlastet gleichzeitige Jobs\nRandomizedDelaySec=15m\nUnit=backup.service\n\n[Install]\nWantedBy=timers.target\n\n# /etc/systemd/system/backup.service\n[Unit]\nDescription=Taegliche Sicherung ausfuehren\n\n[Service]\nType=oneshot\nUser=backup\nExecStart=/usr/local/bin/backup.sh\n# Nicht parallel starten\nRefuseManualStart=no\n\n# Aktivieren\nsystemctl daemon-reload\nsystemctl enable --now backup.timer\n\n# Pruefen - das wichtigste Kommando bei Zeitgebern\nsystemctl list-timers --all\nsystemctl list-timers backup.timer\njournalctl -u backup.service -n 50\n\n# Zeitausdruck pruefen, BEVOR man ihn einsetzt\nsystemd-analyze calendar '*-*-* 03:30:00'\nsystemd-analyze calendar 'Mon..Fri 08:00'\nsystemd-analyze calendar 'weekly'\n#   Die Ausgabe zeigt die naechsten Ausfuehrungszeitpunkte -\n#   so sind Tippfehler im Ausdruck sofort sichtbar.
\n

Vorteile gegenüber cron

\n
    \n
  • Protokoll: Ausgaben landen im Journal – mit Zeitstempel, Rückgabewert, Laufzeit. Kein Mailversand mehr, der ins Leere läuft.
  • \n
  • Abhängigkeiten: Ein Job kann verlangen, dass ein Dienst läuft oder ein Einhängepunkt bereit ist.
  • \n
  • Ressourcenkontrolle: Speicher- und CPU-Grenzen je Aufgabe.
  • \n
  • Verpasste Läufe: Persistent=true holt einen ausgefallenen Lauf nach.
  • \n
  • Status: systemctl list-timers zeigt, wann jeder Job zuletzt lief und wann er als Nächstes läuft – bei cron muss man das aus Protokollen zusammensuchen.
  • \n
\n

Häufige Fehler

\n
    \n
  1. daemon-reload vergessen. Änderungen an Unit-Dateien werden nicht wirksam. Das ist der häufigste Fehler überhaupt.
  2. \n
  3. Kein absolut gesetzter Pfad in ExecStart. systemd hat kein sinnvolles PATH. Immer den vollständigen Pfad angeben.
  4. \n
  5. Umgebung nicht gesetzt. Variablen aus der Shell sind nicht verfügbar. Environment= oder EnvironmentFile= verwenden.
  6. \n
  7. Falscher Type. Ein forking-Daemon mit Type=simple gilt sofort als beendet und wird endlos neugestartet.
  8. \n
  9. Konflikte zwischen After= (Reihenfolge) und Requires= (Abhängigkeit). After allein bedeutet nur Reihenfolge, nicht Abhängigkeit. Für „nur starten, wenn das läuft“ wird Requires= oder Wants= gebraucht.
  10. \n
  11. Absicherungsoptionen zu weit getrieben. ProtectSystem=strict macht das gesamte Dateisystem schreibgeschützt – Schreibpfade müssen in ReadWritePaths= freigegeben werden, sonst startet der Dienst nicht.
  12. \n
  13. Geheimnisse in der Unit-Datei. Unit-Dateien sind für alle lesbar. EnvironmentFile= mit chmod 600 oder eine LoadCredential=-Angabe verwenden.
  14. \n
  15. Timer mit falschem Zeitausdruck. OnCalendar ist nicht cron-Syntax. Mit systemd-analyze calendar prüfen, bevor man sich auf einen täglichen Lauf verlässt.
  16. \n
  17. WantedBy= fehlt in der Unit. Dann lässt sich der Dienst zwar starten, aber nicht dauerhaft aktivieren (enable schlägt fehl).
  18. \n
\n

Prüfliste für einen neuen Dienst

\n
    \n
  1. Läuft er als eigener Benutzer ohne Anmelderechte?
  2. \n
  3. Sind Geheimnisse aus der Unit-Datei heraus und in einer geschützten Datei?
  4. \n
  5. Ist Restart= sinnvoll gewählt – und verhindert eine Startgrenze eine Endlosschleife?
  6. \n
  7. Beendet ein Signal den Prozess sauber, bevor hart abgebrochen wird?
  8. \n
  9. Ist die Absicherung aktiv (ProtectSystem, NoNewPrivileges, PrivateTmp)?
  10. \n
  11. Sind Protokollausgaben im Journal, und findet man Fehler mit journalctl -u?
  12. \n
  13. Wird der Dienst nach einem Neustart automatisch gestartet (enable)?
  14. \n
  15. Gibt es eine Ressourcenbegrenzung, damit der Dienst das System nicht übernehmen kann?
  16. \n
\n

Verwandte Grundlagen: systemd, systemctl und journalctl, Linux, Patchmanagement.