Programm auf einem VPS als systemd-Dienst einrichten
Erstellen Sie eine systemd-Unit für Autostart, Neustart nach Abstürzen und Logs im Journal. Mit Timer, systemctl-Befehlen und Optionen zur Absicherung.
Was ein systemd-Dienst ist und warum Sie einen verwenden sollten
Ein systemd-Dienst ist eine kleine Textdatei, die Ihrem Server vorgibt, wie ein Programm ausgeführt wird: beim Booten starten, bei einem Absturz neu starten und die Ausgabe an das Systemprotokoll senden. Das ist seine gesamte Aufgabe. Ein Programm, das Sie manuell in einer SSH-Sitzung starten, wird beendet, sobald Sie sich abmelden oder der Server neu startet. Ein Programm, das in einen systemd-Dienst eingebunden ist, läuft weiter, weil der Server selbst den Prozess verwaltet und nicht Ihre Shell.
systemd ist das Init-System unter Ubuntu, Debian, Fedora und den meisten modernen Linux-Servern. Es ist der erste gestartete Prozess und überwacht alle weiteren Prozesse. Das war nicht immer so. Lesen Sie wie systemd die vorher verwendeten Init-Skripte ersetzt hat, sobald Sie wissen, was eine Unit-Datei bewirkt. Wenn Sie eine Dienstdatei schreiben, übergeben Sie Ihr Programm dieser Überwachung. Dieser Leitfaden zeigt die kleinste funktionierende Unit, die drei Abschnitte jeder Unit, das Aktivieren und Auslesen ihrer Logs, die zeitgesteuerte Ausführung mit einem Timer und die Absicherung des Dienstes mit möglichst wenigen Berechtigungen.
Der kleinste funktionierende Dienst
Eine Servicedatei liegt in /etc/systemd/system/, endet mit .service und benötigt nur wenige Zeilen. Erstellen Sie eine solche Datei für ein Programm unter /usr/local/bin/myapp:
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=My application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.targetDas ist eine vollständige, funktionierende Unit. ExecStart ist der auszuführende Befehl. WantedBy=multi-user.target bedeutet, dass der Dienst gestartet wird, sobald der Server den normalen Multi-User-Betrieb erreicht. Dadurch wird der Dienst beim Booten gestartet. Alles Weitere ist eine Verfeinerung.
Die drei Abschnitte und ihr jeweiliger Zweck
Jede Unit-Datei ist in Abschnitte in eckigen Klammern unterteilt. Ein Service verwendet drei davon.
[Unit] beschreibt den Service und seine Abhängigkeiten. Die beiden Zeilen, die Sie am häufigsten verwenden werden:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription ist die Bezeichnung, die Sie in systemctl status sehen. After=network-online.target weist systemd an, Ihr Programm erst zu starten, wenn das Netzwerk verfügbar ist. Das ist für alles relevant, was an einen Port gebunden wird oder eine ausgehende Verbindung herstellt.
[Service] legt fest, wie das Programm ausgeführt wird. Hier werden die meisten Einstellungen vorgenommen:
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=infoUser=myapp führt das Programm unter einem nicht privilegierten Account statt unter root aus. Das ist die wichtigste einzelne Zeile für die Sicherheit. Es gibt hier keine Type=-Zeile. Daher verwendet systemd simple und geht davon aus, dass der ExecStart-Prozess im Vordergrund bleibt. Ein Programm, das sich stattdessen in den Hintergrund forkt, benötigt den passenden Type= für seine Startweise. Andernfalls meldet die Unit den Status active, obwohl der eigentliche Daemon bereits beendet ist. Restart=on-failure und RestartSec=5 erhalten weiter unten einen eigenen Abschnitt, weil sie der Grund sind, aus dem die meisten Benutzer überhaupt einen Service anlegen.
[Install] legt fest, was beim Aktivieren des Services geschieht:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target verknüpft den Service mit dem Bootvorgang, wenn Sie systemctl enable ausführen. Ohne einen [Install]-Abschnitt kann ein Service manuell gestartet werden. Nach einem Reboot wird er jedoch nicht automatisch gestartet.
Aktivieren und überwachen
Nach dem Schreiben oder Bearbeiten einer Unit-Datei laden Sie systemd neu, damit die Änderung eingelesen wird. Aktivieren und starten Sie den Dienst anschließend in einem Schritt:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload wird häufig vergessen: systemd cached Unit-Dateien, daher bleibt eine Bearbeitung wirkungslos, bis Sie systemd neu laden. enable --now aktiviert den Dienst für den Systemstart und startet ihn sofort. Prüfen Sie den Status:
sudo systemctl status myapp.service* myapp.service - My application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
Main PID: 4123 (myapp)Active: active (running) und enabled sind die erwarteten Werte. Um die Ausgabe des Programms zu lesen, fragen Sie das Journal nur für diese Unit ab:
sudo journalctl -u myapp.service -f-f folgt neuen Zeilen, sobald sie eintreffen, ähnlich wie tail -f. Alles, was Ihr Programm in die Standardausgabe oder die Standardfehlerausgabe schreibt, wird hier abgelegt. Sie benötigen dafür keine eigene Logging-Konfiguration.
Neustart bei Fehlern: der Grund, aus dem Sie hier sind
Der wichtigste Vorteil eines Dienstes besteht darin, dass systemd Ihr Programm nach einem Absturz neu startet. Dafür genügen zwei Zeilen:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure startet das Programm neu, wenn es mit einem Exit-Code ungleich null beendet wird oder durch ein Signal wie SIGKILL oder SIGSEGV abstürzt. Ein reguläres Beenden oder das Stoppen durch SIGTERM, SIGINT, SIGHUP oder SIGPIPE löst keinen Neustart aus. RestartSec=5 wartet zwischen den Versuchen fünf Sekunden. Dadurch läuft ein Programm, das sofort abstürzt, nicht in einer engen Schleife. Überprüfen Sie dies, indem Sie den Prozess beenden und beobachten, wie systemd ihn wieder startet. Verwenden Sie SIGKILL: Das standardmäßige SIGTERM gilt als reguläres Stoppen, daher würde on-failure den Dienst nicht neu starten:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceInnerhalb von fünf Sekunden zeigt der Status eine neue Main PID und erneut active (running). Das ist die gesamte Funktion. Deshalb ist ein Dienst besser, als ein Programm in tmux oder screen laufen zu lassen.
Führen Sie es als unprivilegierter Benutzer aus und härten Sie es ab
Ein Dienst, der als root läuft, kann bei einer Ausnutzung des Programms alles auf Ihrem Server tun. Führen Sie ihn unter einem eigenen Benutzer aus und geben Sie systemd einige Direktiven, die seinen Zugriff einschränken. Erstellen Sie zuerst ein Systemkonto ohne Login und ohne Home-Verzeichnis:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappSetzen Sie anschließend User=myapp und fügen Sie die Härtungszeilen zu [Service] hinzu:
[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=trueJede Zeile entzieht dem Programm etwas, das es nicht benötigt. NoNewPrivileges=true verhindert, dass der Prozess neue Berechtigungen erlangt, auch nicht über eine setuid-Binärdatei. PrivateTmp=true stellt ihm ein privates /tmp bereit, das kein anderer Prozess sehen kann. ProtectSystem=strict macht das gesamte Dateisystem schreibgeschützt, ausgenommen einige Pfade, die Sie mit ReadWritePaths= angeben. ProtectHome=true verbirgt /home vollständig vor dem Prozess. Dahinter steht dasselbe Prinzip der geringsten Rechte wie bei einem Dienst hinter einer Firewall: Geben Sie ihm nur, was er benötigt. Wenn Sie den Leitfaden zu Schließen der IPv6-Firewall-Lücke auf einem VPS gelesen haben, ist dies die entsprechende Maßnahme auf dem Host. Bei einem Dienst, der aus dem Internet erreichbar ist, sollten Sie diese Härtung mit Fail2ban vor SSH und einer standardmäßig alles ablehnenden Firewall kombinieren.
Statt dies alles manuell einzugeben und eine Direktive falsch in Erinnerung zu behalten, generieren Sie eine vollständige, gehärtete Unit und kopieren Sie sie heraus:
Timer: der moderne Ersatz für cron
Ein systemd-Timer startet einen Dienst nach einem Zeitplan. Er ist der moderne Ersatz für einen cron-Job. Ein Timer besteht aus zwei Dateien: einer .service, die die Aufgabe ausführt, und einer .timer, die den Zeitpunkt festlegt. Angenommen, Sie möchten täglich um 3am ein Backup erstellen. Der Dienst führt die Aufgabe einmal aus und beendet sich anschließend:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shType=oneshot teilt systemd mit, dass das Programm ausgeführt wird, endet und abgeschlossen ist, anstatt dauerhaft im Hintergrund zu laufen. Der Timer legt den Zeitplan fest:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetOnCalendar=*-*-* 03:00:00 bedeutet täglich um 3am. Testen Sie jeden Kalendarausdruck mit systemd-analyze calendar "*-*-* 03:00:00". Der Befehl prüft, ob der Ausdruck korrekt geparst werden kann, und gibt die nächsten Ausführungszeitpunkte aus. Persistent=true führt einen verpassten Job aus, sobald der Server wieder verfügbar ist, wenn er um 3am ausgeschaltet war. Das kann cron nicht. Beachten Sie, dass ein Timer mit timers.target aktiviert wird, nicht mit multi-user.target. Aktivieren Sie den Timer, nicht den Dienst:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timerslist-timers zeigt alle Timer mit ihrem nächsten und letzten Ausführungszeitpunkt an. So sehen Sie sofort, wann Ihr Job als Nächstes ausgeführt wird. Der obige Generator erstellt beim Aktivieren des Timer-Modus automatisch die zusammengehörigen .service und .timer. Im Vergleich zu einer cron-Zeile bietet ein Timer echte Logs im Journal, dieselben Hardening-Direktiven wie jeder andere Dienst und die Nachholung verpasster Ausführungen, die Persistent=true bereitstellt. Für eine einfache Aufgabe ist cron weiterhin geeignet. Sobald die Aufgabe wichtig ist, ist ein Timer jedoch das bessere Werkzeug.
FAQ
Was ist der Unterschied zwischen einem systemd-Dienst und einem cron-Job?
Ein Dienst hält ein langfristig laufendes Programm aktiv: Er startet beim Booten, wird bei einem Fehler neu gestartet und schreibt Logs in das Journal. Ein cron-Job führt nach einem Zeitplan einen kurzen Befehl aus und beendet sich anschließend. Wenn Sie die Zeitplanung benötigen, aber zusätzlich Journal-Logs, Härtung und das Nachholen verpasster Ausführungen wünschen, verwenden Sie einen systemd-Timer. Er verknüpft einen .timer-Zeitplan mit einem oneshot-Dienst und ersetzt cron bei den meisten Serveraufgaben.
Wo lege ich meine systemd-Service-Datei ab?
Legen Sie eigene Units in /etc/systemd/system/ ab. Der Dateiname muss mit .service enden. Dieses Verzeichnis ist für Units vorgesehen, die der Administrator hinzufügt. Es hat Vorrang vor Units, die von Paketen in /lib/systemd/system/ bereitgestellt werden. Führen Sie nach dem Erstellen oder Bearbeiten einer Datei dort sudo systemctl daemon-reload aus, damit systemd die Änderung übernimmt.
Wie lasse ich einen Dienst nach einem Absturz neu starten?
Fügen Sie Restart=on-failure und RestartSec=5 im Abschnitt [Service] hinzu. Führen Sie anschließend sudo systemctl daemon-reload aus und starten Sie den Dienst neu. systemd startet das Programm erneut, wenn es mit einem Exit-Code ungleich null beendet wird oder durch ein Absturzsignal beendet wird. Zwischen den Versuchen wartet systemd fünf Sekunden. Testen Sie dies mit sudo systemctl kill -s SIGKILL myapp.service. SIGTERM, das Standardsignal, gilt als reguläres Beenden und löst on-failure nicht aus. Überwachen Sie mit systemctl status, ob innerhalb weniger Sekunden eine neue PID angezeigt wird.
Wie führe ich einen systemd-Dienst als Benutzer ohne root-Rechte aus?
Erstellen Sie mit sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp ein Systemkonto. Fügen Sie anschließend User=myapp im Abschnitt [Service] hinzu. Ergänzen Sie NoNewPrivileges=true, PrivateTmp=true und ProtectSystem=strict, damit der Prozess nur die erforderlichen Zugriffsrechte erhält. Die Ausführung als unprivilegierter Benutzer ist die wichtigste einzelne Maßnahme, die Sie zur Absicherung eines Dienstes ergreifen können.
Warum konnte mein Dienst nicht starten?
Führen Sie systemctl status myapp.service für die Zusammenfassung und journalctl -u myapp.service für die vollständige Ausgabe aus. Die häufigsten Ursachen sind ein falscher Pfad in ExecStart, ein fehlendes WorkingDirectory, ein Berechtigungsfehler, weil User= eine Datei nicht lesen kann, oder ein vergessenes sudo systemctl daemon-reload nach einer Bearbeitung. Das Journal zeigt die eigene Fehlermeldung des Programms an. Sie benennt das Problem meist direkt.