Modul 6 · 6.3
Deutsch
Eigene Dienste und Timer
Modul 6: Dienste und Logs · Lesezeit: 11 Minuten
Lernziel: Nach dieser Lektion können Sie ein eigenes Programm als systemd-Dienst unter einem eigenen Benutzer mit einigen Sicherheitsoptionen laufen lassen und Aufgaben mit einem systemd-Timer oder cron planen.
Kernbegriffe: Unit-Datei, [Unit], [Service], [Install], ExecStart, User=, WorkingDirectory, EnvironmentFile, Restart=, WantedBy, NoNewPrivileges, ProtectSystem, ProtectHome, PrivateTmp, systemd-analyze security, Timer, OnCalendar, Persistent, crontab
Warum nicht einfach "nohup" oder "screen"?
Wer ein eigenes Programm auf einem Server betreibt (eine kleine Webanwendung, einen Bot, ein Skript, das jede Nacht etwas erledigt), startet es beim ersten Mal oft von Hand: node server.js & oder in einer screen-Sitzung. Das funktioniert, bis Sie sich abmelden, der Server neu startet oder das Programm abstürzt. Dann ist es weg, und niemand bemerkt es.
Ein Programm, das immer laufen muss, gehört als systemd-Dienst eingerichtet. Dann:
- startet es automatisch beim Hochfahren;
- startet systemd es neu, wenn es abstürzt;
- landet die Ausgabe im Journal (Lektion 6.2);
- läuft es unter einem Benutzer, den Sie wählen, mit den Rechten, die Sie erlauben.
Eine eigene Unit-Datei
Angenommen, Sie haben eine kleine Node.js-Anwendung in /opt/factuurapp, die auf 127.0.0.1:3000 lauscht. nginx oder Caddy machen sie später nach außen erreichbar (Modul 7).
Schritt 1: ein eigener Benutzer ohne Shell (Lektion 3.2). Die Anwendung muss weder root sein noch sich anmelden können:
anna@web1:~$ sudo adduser --system --group --home /opt/factuurapp --no-create-home factuurapp
anna@web1:~$ sudo chown -R factuurapp:factuurapp /opt/factuurapp/dataDer Code selbst darf root gehören (die Anwendung muss sich nicht selbst ändern); nur das Verzeichnis, in das sie schreibt, gehört dem Dienstbenutzer.
Schritt 2: die Unit-Datei. Eigene Units legen Sie in /etc/systemd/system/ ab:
# /etc/systemd/system/factuurapp.service
[Unit]
Description=Factuurapp (Node.js)
After=network-online.target
Wants=network-online.target
[Service]
User=factuurapp
Group=factuurapp
WorkingDirectory=/opt/factuurapp
EnvironmentFile=/etc/factuurapp/env
ExecStart=/usr/bin/node /opt/factuurapp/server.js
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/factuurapp/data
[Install]
WantedBy=multi-user.targetDie drei Abschnitte:
- [Unit]: Beschreibung und Reihenfolge.
After=network-online.targetmitWants=sorgt dafür, dass der Dienst erst startet, wenn das Netzwerk bereit ist. - [Service]: wie das Programm läuft.
ExecStartmit dem vollständigen Pfad (systemd verwendet keinen Suchpfad wie Ihre Shell).User=undGroup=legen fest, unter wem es läuft.EnvironmentFileliest Einstellungen wie ein Datenbankpasswort aus einer separaten Datei, damit sie nicht in der Unit stehen. - [Install]: was
enablebewirkt.WantedBy=multi-user.targetbedeutet: beim normalen Hochfahren mitstarten.
Schritt 3: Geheimnisse getrennt, mit strengen Rechten.
anna@web1:~$ sudo mkdir /etc/factuurapp
anna@web1:~$ sudo install -m 600 -o root -g root /dev/null /etc/factuurapp/env
anna@web1:~$ sudoedit /etc/factuurapp/envInhalt zum Beispiel DATABASE_URL=postgres://factuur:...@127.0.0.1/factuur. systemd liest die Datei als root, bevor es zum Dienstbenutzer wechselt, daher genügt 600 mit root als Eigentümer.
Schritt 4: laden, starten, prüfen.
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now factuurapp
anna@web1:~$ systemctl status factuurapp
anna@web1:~$ journalctl -u factuurapp -fSicherheitsoptionen: wenig Aufwand, großer Unterschied
Die vier Zeilen unter RestartSec im Beispiel schränken ein, was der Dienst kann, auch wenn jemand eine Lücke in Ihrer Anwendung findet:
| Option | Wirkung |
|---|---|
NoNewPrivileges=true | der Prozess und seine Kindprozesse können niemals mehr Rechte erlangen (auch nicht über sudo oder setuid-Programme) |
ProtectSystem=strict | das gesamte Dateisystem ist für den Dienst schreibgeschützt, außer dem, was Sie mit ReadWritePaths= erlauben |
ProtectHome=true | /home, /root und /run/user sind unsichtbar |
PrivateTmp=true | ein eigenes, leeres /tmp, das andere Programme nicht sehen |
systemd kann Ihre Unit bewerten:
anna@web1:~$ systemd-analyze security factuurapp
...
→ Overall exposure level for factuurapp.service: 4.9 OK 🙂Je niedriger die Zahl, desto besser. Ein Dienst ohne Optionen erreicht etwa 9 ("UNSAFE"). Sie müssen nicht auf null kommen: Jede Option, die Sie hinzufügen, kann etwas blockieren, das die Anwendung braucht. Fügen Sie sie einzeln hinzu und testen Sie nach jedem Schritt. Funktioniert etwas nicht mehr, steht der Grund meist im Journal ("Read-only file system", "Permission denied").
Aufgaben planen: Timer
Für etwas, das zu festen Zeiten passieren soll (ein Backup, ein Aufräumskript, ein Bericht), brauchen Sie einen Timer. Ein systemd-Timer besteht aus zwei Units mit demselben Namen: einer .service, die festlegt, was passieren soll, und einer .timer, die festlegt, wann.
# /etc/systemd/system/opruimen.service
[Unit]
Description=Oude exportbestanden opruimen
[Service]
Type=oneshot
User=factuurapp
ExecStart=/usr/bin/find /opt/factuurapp/data/export -type f -mtime +30 -delete# /etc/systemd/system/opruimen.timer
[Unit]
Description=Dagelijks oude exportbestanden opruimen
[Timer]
OnCalendar=*-*-* 03:15:00
RandomizedDelaySec=10m
Persistent=true
[Install]
WantedBy=timers.targetType=oneshot bedeutet: Das Programm läuft und ist danach fertig. OnCalendar ist der Zeitplan: jeden Tag um 03:15. Sie können auch daily, weekly oder Mon *-*-* 02:00 schreiben; systemd-analyze calendar "Mon *-*-* 02:00" zeigt, wann das nächste Mal ist. Persistent=true ist wichtig: War der Server um 03:15 ausgeschaltet, läuft die Aufgabe direkt nach dem Hochfahren nachträglich. RandomizedDelaySec verteilt den Startzeitpunkt etwas.
Aktiviert wird der Timer, nicht der Service:
anna@web1:~$ sudo systemctl daemon-reload
anna@web1:~$ sudo systemctl enable --now opruimen.timer
anna@web1:~$ systemctl list-timers opruimen
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-11-10 03:19:42 CET 15h left - - opruimen.timer opruimen.service
anna@web1:~$ sudo systemctl start opruimen.service # jetzt einmal von Hand testen
anna@web1:~$ journalctl -u opruimen.serviceUnd cron?
cron ist das ältere, einfachere Werkzeug und funktioniert nach wie vor gut. Eine Zeile in der crontab eines Benutzers (crontab -e):
15 3 * * * /usr/local/bin/opruimen.shDie fünf Felder sind Minute, Stunde, Tag des Monats, Monat und Wochentag. Vor- und Nachteile im Vergleich:
| cron | systemd-Timer | |
|---|---|---|
| Einrichten | eine Zeile | zwei Dateien |
| Verpasster Lauf (Server aus) | wird übersprungen | mit Persistent=true nachgeholt |
| Ausgabe und Fehler | per Mail, wenn der Server mailen kann; sonst verloren | im Journal |
| Sicherheitsoptionen, Benutzer | über das Skript selbst | alles von systemd (siehe oben) |
| Sehen, wann er lief | umständlich | systemctl list-timers und das Journal |
Für einen Server, den Sie ernsthaft verwalten, sind Timer die bessere Wahl, besonders für Backups (Lektion 8.2): Sie wollen sicher wissen, dass sie gelaufen sind und was sie gemeldet haben. Eine cron-Zeile, die still fehlschlägt, bemerken Sie erst, wenn Sie das Backup brauchen.
Unter AlmaLinux und Rocky Linux
Units und Timer funktionieren genau gleich. Ein Unterschied: SELinux kann einen eigenen Dienst blockieren, der Dateien an einem Ort liest oder schreibt, den SELinux nicht erwartet, oder der auf einem ungewöhnlichen Port lauscht. Die Meldung steht in /var/log/audit/audit.log. Schalten Sie SELinux nicht ab, um das Problem zu lösen; suchen Sie das richtige Label oder die richtige Regel (zum Beispiel mit semanage).
Zusammenfassung
- Ein Programm, das immer laufen muss, gehört als systemd-Dienst eingerichtet: automatischer Start, Neustart bei einem Absturz, Logs im Journal.
- Eigene Units in
/etc/systemd/system/, mitUser=ein eigener Benutzer ohne Shell, Geheimnisse in einemEnvironmentFilemit Rechten 600. NoNewPrivileges,ProtectSystem=strictmitReadWritePaths,ProtectHomeundPrivateTmpbegrenzen den Schaden bei einer Lücke;systemd-analyze securitybewertet die Unit.- Ein Timer ist eine
.serviceplus eine.timermitOnCalendar;Persistent=trueholt verpasste Läufe nach. Aktivieren Sie den Timer, nicht den Service. - cron funktioniert noch, aber Timer liefern bessere Logs und verpassen keine Läufe.
Quiz
Aufgabe
Aufgabe O6.3: Ein eigener Dienst mit Timer. Erstellen Sie auf Ihrem Server einen kleinen eigenen Dienst mit Timer, zum Beispiel ein Skript, das jede Nacht den freien Speicherplatz und systemctl --failed in eine Datei oder ins Journal schreibt. Verwenden Sie einen eigenen Benutzer, Type=oneshot, mindestens zwei Sicherheitsoptionen und Persistent=true. Testen Sie mit systemctl start, sehen Sie sich die Ausgabe im Journal an und notieren Sie die Bewertung von systemd-analyze security. Fügen Sie Ihre .service und .timer ein und beantworten Sie die Fragen in der Checkliste.
Aufgabe O6.3: Ein eigener Dienst mit Timer
Diese Aufgabe machen Sie selbst; niemand prüft sie nach. Haken Sie ab, was Sie erledigt haben, und markieren Sie die Aufgabe als erledigt.
Mit Persistent=true wird die Aufgabe nach dem Hochfahren nachgeholt. Ein Wert unter 6 ist für einen ersten Dienst gut; Schreibrechte nur über ReadWritePaths.
Ihre Haken und Notizen bleiben nur in diesem Browser; wir senden sie nirgendwohin. Sind Sie angemeldet, speichern wir in Meine Kurse nur, dass Sie die Aufgabe erledigt haben.
Weiterführende Informationen
- DirectAdmin Anleitung 15.1: Cronjobs: Aufgaben automatisch ausführen lassen
- Handbuchseite
systemd.exec(5)(User=, ProtectSystem=, NoNewPrivileges=): https://manpages.debian.org/trixie/systemd/systemd.exec.5.en.html - Handbuchseite
systemd.timer(5)(OnCalendar=, Persistent=): https://manpages.debian.org/trixie/systemd/systemd.timer.5.en.html - Handbuchseite
systemd.time(7)(Zeitpläne): https://manpages.debian.org/trixie/systemd/systemd.time.7.en.html
