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.
\nUnit-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
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!\nEine 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\nDie 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: Wiesimple, 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 meistPIDFile=. \n oneshot: Einmalige Aufgabe, die beendet werden soll. Richtig für Skripte mitRemainAfterExit=yes, wenn danach der Zustand als erreicht gelten soll. Auch für Vorbereitungsläufe mitBefore=. \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
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\nTimer 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.\nVorteile 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=trueholt einen ausgefallenen Lauf nach. \n - Status:
systemctl list-timerszeigt, wann jeder Job zuletzt lief und wann er als Nächstes läuft – bei cron muss man das aus Protokollen zusammensuchen. \n
Häufige Fehler
\n- \n
daemon-reloadvergessen. Änderungen an Unit-Dateien werden nicht wirksam. Das ist der häufigste Fehler überhaupt. \n - Kein absolut gesetzter Pfad in
ExecStart. systemd hat kein sinnvollesPATH. Immer den vollständigen Pfad angeben. \n - Umgebung nicht gesetzt. Variablen aus der Shell sind nicht verfügbar.
Environment=oderEnvironmentFile=verwenden. \n - Falscher
Type. Einforking-Daemon mitType=simplegilt sofort als beendet und wird endlos neugestartet. \n - Konflikte zwischen
After=(Reihenfolge) undRequires=(Abhängigkeit).Afterallein bedeutet nur Reihenfolge, nicht Abhängigkeit. Für „nur starten, wenn das läuft“ wirdRequires=oderWants=gebraucht. \n - Absicherungsoptionen zu weit getrieben.
ProtectSystem=strictmacht das gesamte Dateisystem schreibgeschützt – Schreibpfade müssen inReadWritePaths=freigegeben werden, sonst startet der Dienst nicht. \n - Geheimnisse in der Unit-Datei. Unit-Dateien sind für alle lesbar.
EnvironmentFile=mitchmod 600oder eineLoadCredential=-Angabe verwenden. \n - Timer mit falschem Zeitausdruck.
OnCalendarist nicht cron-Syntax. Mitsystemd-analyze calendarprüfen, bevor man sich auf einen täglichen Lauf verlässt. \n WantedBy=fehlt in der Unit. Dann lässt sich der Dienst zwar starten, aber nicht dauerhaft aktivieren (enableschlägt fehl). \n
Prüfliste für einen neuen Dienst
\n- \n
- Läuft er als eigener Benutzer ohne Anmelderechte? \n
- Sind Geheimnisse aus der Unit-Datei heraus und in einer geschützten Datei? \n
- Ist
Restart=sinnvoll gewählt – und verhindert eine Startgrenze eine Endlosschleife? \n - Beendet ein Signal den Prozess sauber, bevor hart abgebrochen wird? \n
- Ist die Absicherung aktiv (
ProtectSystem,NoNewPrivileges,PrivateTmp)? \n - Sind Protokollausgaben im Journal, und findet man Fehler mit
journalctl -u? \n - Wird der Dienst nach einem Neustart automatisch gestartet (
enable)? \n - Gibt es eine Ressourcenbegrenzung, damit der Dienst das System nicht übernehmen kann? \n
Verwandte Grundlagen: systemd, systemctl und journalctl, Linux, Patchmanagement.