Zum Inhalt springen

Systemd Unit Validator · Scheduling

Eine systemd-Unit prüfen, bevor systemd sie ignoriert.

.service-, .timer- und .socket-Dateien, geprüft gegen die Art, wie systemd sie selbst liest: die Direktive in der falschen Sektion, der Tippfehler, den es ohne Hinweis verwirft, das OnCalendar=, das bedeutet, dass Ihr Timer nie feuert. Jeder Fund nennt eine Zeile, sagt, was systemd selbst damit tut, und liefert den Fix — und nichts, was Sie einfügen, verlässt den Tab.

Läuft in Ihrem Browser — nichts, was Sie einfügen, verlässt diese Seite. Wie wir das belegen

Läuft in Ihrem Browser 35 Prüfungen Kein root nötig Aktualisiert am 31.07.2026

Systemd Unit Validator Playground

Examples

WantedBy= lives in [Install] and is what systemctl enable reads to create its symlinks. Type= tells systemd how to decide the service is up. Persistent=true makes a calendar timer run immediately at boot if it missed its window.

Scope

Detected: —

unit file input

Results update as you type — press Enter to run now.

Press Esc to release keyboard focus from the editor; /Ctrl + Enter checks and leaves the editor. Tap a Line badge to jump to that line. Nothing you paste is uploaded.

Findings

Paste a .service, .timer or .socket file above — or tap an example — to see line-numbered findings here, each with the reason it matters and the fix.

Die Lücke

systemd sagt Ihnen nicht, wenn es Sie ignoriert.

Eine Unit-Datei, die lädt, ist keine Unit-Datei, die funktioniert. Schreiben Sie User=deploy in [Unit] statt in [Service], läuft der Dienst als root; schreiben Sie WantedBy= dorthin, tut systemctl enable stillschweigend nichts, und die Unit kommt nach einem Reboot nicht wieder. Beides protokolliert eine Zeile — „Unknown key name … ignoring“ — in ein Journal, das an einem guten Tag niemand liest, und beides hinterlässt eine Unit, die in systemctl status gesund aussieht.

Das Prüfwerkzeug, das systemd selbst mitbringt, systemd-analyze verify, ist gründlicher, als diese Seite es je sein wird — und es braucht eine Maschine mit systemd, meistens root, und es löst Ihre Unit gegen die Benutzer, Pfade und Drop-ins DIESES Hosts auf. Nichts davon haben Sie, während Sie einen .service in einem Pull Request auf einem Laptop reviewen.

Und dann ist da generierter Output. Bitten Sie einen Assistenten um einen Timer, und Sie bekommen etwas Flüssiges und Plausibles: RestartSecs=5 (diese Direktive gibt es nicht), ExecReload= in [Unit], ein WantedBy= im falschen Block — oder den häufigsten Fall überhaupt: OnCalendar=*/15, cron-Syntax, die systemd komplett ablehnt, sodass ein Timer bleibt, der nie feuert und nie in systemctl list-timers auftaucht. Die Behauptung zu prüfen dauert eine Sekunde; zu wissen, was zu prüfen ist, ist der schwierige Teil. Genau dafür sind die 35 Prüfungen hier.

Migration von cron? Der Cron-zu-systemd-Konverter schreibt das Paar aus .timer und .service für Sie — fügen Sie das Ergebnis dann hier ein, um Ihre Handkorrekturen zu prüfen. Beide Tools teilen eine OnCalendar=-Grammatik und können sich über einen Zeitplan also nicht widersprechen.

Die Pipeline

So funktioniert es.

Vier Schritte, alle in Ihrem Browser-Tab, bei jeder Eingabe neu.

  1. So parsen wie systemd.

    Eine `\`-Fortsetzung klappt auf die nächste Zeile — und eine Kommentarzeile innerhalb dieser Fortsetzung wird verworfen, nicht eingefaltet. `#` und `;` beginnen einen Kommentar nur am Zeilenanfang: es gibt KEINE Kommentare am Zeilenende, `RestartSec=30 # später` setzt den Wert also wirklich auf `30 # später`.

  2. Auflösen, was tatsächlich gilt.

    Eine wiederholte Listen-Direktive hängt an; ein wiederholter Skalar bedeutet, dass der letzte still gewinnt; eine leere Zuweisung setzt die Liste zurück. Die Regeln lesen also den Wert, bei dem systemd landet, nicht den ersten in der Datei — und sagen, welche Zeile verloren hat.

  3. Namen, Werte und Semantik prüfen.

    Jeder Name wird in der Sektion gesucht, in der er steht, gegen 439 bekannte Direktiven. Werte werden nur dort geprüft, wo systemd streng ist: Enums case-sensitiv, Booleans case-insensitiv und OnCalendar= gegen die vollständige Grammatik aus systemd.time(7).

  4. Sagen, was systemd selbst tun würde.

    Jeder Fund nennt die physische Zeile und zitiert die Konsequenz — „verweigert das Laden der Unit“, „protokolliert Unknown key name und ignoriert die Zeile“. Wo diese Seite etwas nicht wissen kann, sagt sie das, statt zu raten.

Referenz

Welche Direktive wohin gehört.

systemd liest eine Direktive nur aus der Sektion, die sie besitzt, und vergleicht Sektions- wie Direktivennamen case-sensitiv. Dies sind die, die in echten Units vorkommen — der Validator kennt insgesamt 439 Namen, über fünf Sektionen, die er prüft, und sechs, die er benennt, aber nicht prüft.

[Unit]

Direktive Was sie tut
Description= Eine Zeile, sichtbar in systemctl status und in Logs.
Documentation= URIs, durch Leerzeichen getrennt — man:, https:, file:.
After= Nur Reihenfolge: nach diesen starten, falls sie auch starten.
Wants= Diese mitziehen, aber nicht mit ihnen scheitern.
Requires= Mitziehen und mit ihnen scheitern. Reihenfolge ist NICHT impliziert.
ConditionPathExists= Unit überspringen (nicht scheitern lassen), wenn der Pfad fehlt.
StartLimitIntervalSec= Fenster für das Start-Ratelimit. Steht hier, nicht in [Service].
StartLimitBurst= Erlaubte Starts in diesem Fenster, bevor systemd aufgibt.

[Install]

Direktive Was sie tut
WantedBy= Wohin `systemctl enable` verlinkt. Timer: timers.target.
RequiredBy= Wie WantedBy=, aber das Target scheitert mit dieser Unit.
Alias= Zusätzlicher Name, auf den die Unit nach dem Enable hört.
Also= Weitere Units, die mit dieser aktiviert und deaktiviert werden.

[Service]

Direktive Was sie tut
Type= simple, exec, forking, oneshot, dbus, notify, notify-reload, idle.
ExecStart= Absoluter Pfad. Nur eine Zeile, außer bei Type=oneshot. Keine Shell.
ExecStartPre= Läuft vor ExecStart=; ein Fehler bricht den Start ab.
ExecReload= Was `systemctl reload` ausführt. Gehört hierhin, nie in [Unit].
ExecStop= Optionaler sanfter Stopp; systemd sendet danach trotzdem die Kill-Signale.
PIDFile= Praktisch Pflicht bei Type=forking: benennt den echten Hauptprozess.
Restart= no, on-success, on-failure, on-abnormal, on-watchdog, on-abort, always.
RestartSec= Wartezeit vor einem Neustart. NICHT „RestartSecs“.
RemainAfterExit= Ein fertiges oneshot als aktiv melden statt als beendet.
User= / Group= Unter wem der Prozess läuft. [Service], nie [Unit].
DynamicUser= Wegwerf-UID pro Start. Zusammen mit StateDirectory= verwenden.
StateDirectory= systemd erstellt /var/lib/<name> und setzt bei jedem Start den Owner.
WorkingDirectory= Arbeitsverzeichnis der Prozesse. Beeinflusst die Exec*-Suche nicht.
Environment= KEY=value-Paare; wiederholbar, und $KEY expandiert in Exec*-Zeilen.
EnvironmentFile= Paare aus einer Datei lesen. Mit `-` davor ist ein Fehlen erlaubt.
TimeoutStartSec= Wie lange systemd auf das Ende des Starts wartet.

[Timer]

Direktive Was sie tut
OnCalendar= Wanduhr-Zeitplan, Syntax nach systemd.time(7). Wiederholbar.
OnBootSec= Monoton: so lange nach dem Boot.
OnUnitActiveSec= Monoton: so lange nach der letzten Aktivierung der Unit.
AccuracySec= Spielraum, den systemd zum Bündeln von Wakeups nutzen darf. Standard 1min.
RandomizedDelaySec= Streut den Feuerzeitpunkt gegen einen Thundering Herd.
Persistent= Nur Kalender-Timer: einen im ausgeschalteten Zustand versäumten Lauf nachholen.
Unit= Was getriggert wird. Standard: der .service mit demselben Basisnamen.
WakeSystem= Weckt die Maschine aus dem Suspend. Braucht passende Hardware.

[Socket]

Direktive Was sie tut
ListenStream= Port, Adresse:Port oder absoluter Pfad für einen UNIX-Socket.
ListenDatagram= Das Äquivalent für UDP / SOCK_DGRAM.
Accept= no: ein Service bekommt den Listening-Socket. yes: einer pro Verbindung.
SocketMode= Rechte für einen UNIX-Socket oder FIFO, z. B. 0660.
SocketUser= / SocketGroup= Eigentümer eines UNIX-Sockets im Dateisystem.
Service= Was aktiviert wird. Standard: der .service mit demselben Basisnamen.
FileDescriptorName= Benennt den FD, damit ein Service mehrere Sockets unterscheiden kann.

OnCalendar=-Syntax

Das eine Feld, in dem ein Fehler unsichtbar bleibt: ein Zeitplan, den systemd nicht parsen kann, heißt, dass die Unit nicht lädt — der Timer feuert also nie UND erscheint nie als defekt. Der Validator prüft die vollständige Grammatik aus systemd.time(7) — dasselbe Modul, gegen das der Cron-zu-systemd-Konverter schreibt.

Form Bedeutung
DOW YYYY-MM-DD HH:MM:SS Die volle Form. Wochentag, Datum und Sekunden sind optional; ein Datum allein bedeutet 00:00:00.
* Jeder Wert für diese Komponente.
a,b,c Eine Liste von Werten.
a..b Ein Bereich, kleinerer Wert zuerst. Wochentage laufen Mon..Sun, Sun kann also keinen Bereich beginnen.
a/n Alle n, beginnend bei a. Der Startwert ist NICHT optional — siehe unten.
a..b/n Alle n über einen Bereich.
*-*~03 Drittletzter Tag des Monats (das `~` zählt vom Monatsende zurück).
… UTC | … Europe/Berlin Eine Zeitzone am Ende. Braucht systemd 242 oder neuer; der Name wird gegen die tzdata des Hosts aufgelöst.
Kurzform Entspricht
minutely *-*-* *:*:00
hourly *-*-* *:00:00
daily *-*-* 00:00:00
monthly *-*-01 00:00:00
weekly Mon *-*-* 00:00:00
yearly *-01-01 00:00:00
annually *-01-01 00:00:00
quarterly *-01,04,07,10-01 00:00:00
semiannually *-01,07-01 00:00:00

Abgelehnt — die Wiederholung hat keinen Startwert

INI
[Timer]
OnCalendar=*/15
OnCalendar=0 3 * * *

Akzeptiert — alle 15 Minuten und täglich 03:00

INI
[Timer]
OnCalendar=*-*-* *:00/15:00
OnCalendar=*-*-* 03:00:00

Der Zaun

Was er bewusst nicht prüft.

Jeder Punkt wurde geprüft und verworfen, und dieselbe Liste steht als Kommentar am Kopf der Engine. Ein Validator, der rät, wäre schlimmer als einer, der sagt: „Ich lese eine Datei, als Text.“

Ob die Units existieren, von denen Sie abhängen

After=, Wants= und Unit= nennen andere Dateien. Die liegen nicht vor uns, ein „Unit nicht gefunden“ wäre also erfunden.

Ob ein Pfad, ein Benutzer oder ein Capability existiert

ExecStart=/usr/local/bin/x wird darauf geprüft, absolut zu sein, nicht darauf, da zu sein. Den Rest weiß nur Ihre Maschine.

Syntax von Zeitspannen

systemds Grammatik für `5min`, `1h30s` und `2 weeks` ist großzügig. Sie nachzubauen würde mehr Fehlalarme erzeugen als Treffer.

Drop-ins und Übereinstimmung mit dem Dateinamen

Ein .d/-Override und die Frage, ob der Dateiname zu dem passt, was Unit= erwartet, liegen beide außerhalb einer eingefügten Datei.

Reihenfolge ohne Abhängigkeit

After= ohne Wants= ist häufig Absicht. Das zu melden wäre eine Meinung im Gewand eines Funds.

Werte in [Mount], [Path], [Swap], [Automount], [Slice] und [Scope]

Ihre Direktiven-NAMEN sind bekannt — `What=` in [Service] wird also korrekt zugeordnet — aber ihre Werte werden nicht geprüft, und die Ergebnisse sagen das ausdrücklich.

ProtectSystem=, ProtectHome=, RestrictNamespaces= und Verwandte

Sie nehmen benannte Werte ODER einen Boolean, eine strenge Prüfung würde also gültige Eingaben ablehnen. Sie werden absichtlich als opake Strings geführt.

PrivateTmp= als reiner Boolean

systemd 257 hat `PrivateTmp=disconnected` ergänzt. Ein Validator, der einen gültigen Wert zum Fehler erklärt, ist genau der Fehlalarm, gegen den dieses Tool existiert.

Der nächste Feuerzeitpunkt eines Timers

Dafür braucht es eine Uhr und eine Zeitzonendatenbank. Ein als Fakt gedruckter, still falscher „nächster Lauf“ ist schlimmer als keine Antwort.

Alles über das laufende System

Kein systemctl, kein Journal, kein Drop-in-Baum, keine tzdata. Eine Datei, als Text gelesen — genau deshalb braucht es kein root und lädt nichts hoch.

Grenzen, genannt statt versteckt: der Validator liest bis zu 200.000 Zeichen, behält höchstens 20 Funde pro Prüfung und 200 insgesamt, rendert höchstens 50 Zeilen pro Schweregrad und nennt die echte Anzahl, sobald eine Grenze greift. Eine unbekannte Direktive ist immer ein Hinweis, nie ein Fehler.

Nächster Schritt

Den Bericht ins Review einfügen.

„Bericht kopieren“ gibt Ihnen den ganzen Lauf als Klartext — eine Zeile pro Fund, mit Zeilennummer, Prüfungs-ID und Fix. Und dann weiter: eine crontab-Zeile in einen echten Timer verwandeln oder einen cron-Zeitplan erst in Klartext nachlesen.

report.txt
Systemd Unit Validator — 2 errors, 1 warning across 8 lines
Scope: system

ERRORS (2)
  L3 wrong-section: WantedBy= belongs in [Install], not [Unit]. — fix: Move the line into the `[Install]` section.
  L8 typo-directive: ExecStrat= is not a systemd directive — did you mean ExecStart=? — fix: Write `ExecStart=`.

WARNINGS (1)
  missing-install: No [Install] section, so “systemctl enable” has nothing to do. — fix: Add `[Install]` with `WantedBy=multi-user.target` (or `default.target` for a user unit).

Checked client-side at opscanopy.com/systemd-unit-validator/ — 0 bytes uploaded.

FAQ

Fragen, beantwortet.

Tippen Sie auf eine Frage, um die Antwort aufzuklappen.

35 Prüfungen auf einem echten Parse der Datei, in vier Gruppen. Struktur: ein ungültiger Sektionskopf, ein klein geschriebenes [unit], eine unbekannte oder doppelte Sektion, eine Zeile ohne „=“, eine Zuweisung vor der ersten Sektion. Namen: eine Direktive in der falschen Sektion (User= in [Unit]), ein Tippfehler, den systemd wegwirft (ExecStrat=), ein veralteter Name, ein Name, den die Tabelle einfach nicht kennt. Werte: ein falsches Enum — systemd vergleicht die nämlich case-sensitiv —, ein Boolean, der keiner ist, ein Exec*-Pfad, der nicht absolut ist, Shell-Syntax in einer Exec*-Zeile, die systemd nicht interpretiert, ein unbekannter %-Specifier und ein „Kommentar“ am Zeilenende, der in Wahrheit Teil des Werts ist. Semantik: ein Service ohne ExecStart=, zwei ExecStart=-Zeilen bei Type≠oneshot, Type=forking ohne PIDFile=, Restart=always mit Type=oneshot, ein Timer ohne Trigger, ein OnCalendar=, das systemd nicht parsen kann, Persistent= auf einem monotonen Timer, ein Socket ohne Listen*, ein fehlendes [Install] und ein System-Target in einer User-Unit.

Nein, und das kann es auch nicht sein. systemd-analyze verify läuft auf einer Maschine mit systemd, braucht für alles im System-Suchpfad meist root und löst Ihre Unit vor allem gegen DIESEN Host auf: dessen Benutzer, Pfade, Drop-ins und andere Units. Das macht es zum gründlicheren Werkzeug — und zum falschen für den Moment, in dem Sie die Prüfung wirklich wollen: ein .service in einem Pull Request auf einem macOS-Laptop, oder eine Unit, die ein Assistent gerade für Sie geschrieben hat. Diese Seite liest eine Datei als Text, in Ihrem Browser, und benennt den Unterschied ausdrücklich — alles, was sie aus dem Text allein nicht wissen kann, steht weiter unten aufgelistet statt geraten.

Nein. Der Parser und alle 35 Prüfungen sind JavaScript, das in Ihrem Tab läuft — es gibt keinen Server, keinen API-Aufruf und kein Logging, also werden 0 Bytes hochgeladen. Das zählt hier besonders: Unit-Dateien enthalten interne Hostnamen, private Pfade, Namen von Dienstkonten, EnvironmentFile=-Orte und gelegentlich ein Credential, das niemand in einen Wert hätte schreiben sollen.

Die häufigste Ursache ist mit Abstand ein OnCalendar=, das systemd nicht parsen kann, weil cron-Syntax hineingeschrieben wurde. Zwei Formen erklären fast alle Fälle. Erstens ein nacktes „*/15“: eine Kalender-Wiederholung in systemd braucht einen ausdrücklichen Startwert, „*-*-* *:00/15:00“ (oder kurz „*:00/15:00“) wird also akzeptiert und „*/15“ komplett abgelehnt. Zweitens ein ganzer fünffeldiger crontab-Zeitplan („0 3 * * *“): systemd-Kalenderereignisse sind „DOW YYYY-MM-DD HH:MM:SS“ und keine fünf Felder — dieses Tool erkennt die Form und schickt Sie zum Konverter. Danach lohnt der Blick auf einen Timer ohne jeden Trigger und auf ein .timer ohne Unit=, dessen .service anders heißt. Eine Unit, die systemd nicht lädt, erscheint nie in systemctl list-timers — deshalb sieht nichts falsch aus.

Weil die Einstellung überhaupt nicht wirkt. systemd liest WantedBy= nur aus [Install]; überall sonst protokolliert es „Unknown key name 'WantedBy' in section 'Unit', ignoring.“ und macht weiter. Die Unit lädt, startet von Hand, sieht gesund aus — und systemctl enable tut stillschweigend nichts, sodass sie nach einem Reboot nicht wiederkommt. Auf dieser Seite heißt Fehler: systemd verweigert die Unit ODER verwirft die Einstellung vollständig; Warnung heißt: die Einstellung greift und ist eine Falle. Eine verworfene Einstellung gehört in die erste Gruppe, weil die Datei dann nicht mehr beschreibt, was die Maschine tun wird.

Weil diese Seite eine Direktiven-Tabelle mitbringt — 439 Namen — und Ihre Maschine ein systemd. Mit jedem Release kommen neue Direktiven hinzu, ein Name, den diese Tabelle nicht kennt, kann dort also völlig gültig sein, wo Sie ihn ausführen werden. Das als Fehler zu melden würde Sie darauf trainieren, den ganzen Bericht zu ignorieren — das Einzige, was ein Linter nicht überlebt. Ein Name, der einer echten Direktive knapp danebenliegt, ist ein anderer Fall: dort ist die Evidenz stark genug für die Aussage, dass systemd die Zeile wegwirft, und DAS wird als Fehler mit Korrektur gemeldet.

Drei Dinge, alle darüber, welcher Manager die Datei lädt. Im User-Scope wird ein WantedBy= mit einem System-Target (multi-user.target, graphical.target) gemeldet: der User-Manager hat kein solches Target, systemctl --user enable hat also nichts zu verlinken und die Unit startet nie — default.target ist gemeint, und timers.target existiert dort tatsächlich. Der Scope reist außerdem im Share-Link mit, damit ein verschickter Link dieselben Funde zeigt wie Ihr Bildschirm. Alles andere — Parse, Namen, Werte — ist identisch, denn systemd liest die Datei in beiden Fällen gleich.

Er liest genau eine Datei, also: ein Template wird normal geparst und %i als Hinweis gemeldet (es hat nur einen Wert, wenn die Datei name@.service heißt und als name@instance.service gestartet wird); ein Drop-in-Fragment parst für sich problemlos, aber hier weiß niemand, was es überschreibt, also sieht eine Teildatei aus wie eine Unit ohne ExecStart=; und eine in After=, Wants= oder Unit= genannte Unit wird nie aufgelöst, weil diese Datei nicht vor uns liegt. Bei allen drei zu raten wäre eine selbstbewusst falsche Antwort — schlimmer als keine.

Bis 200.000 Zeichen, also etwa viertausend Zeilen — zwei Größenordnungen mehr als jede echte Unit-Datei. Darüber verweigert das Tool mit einer Meldung, statt Ihren Tab einzufrieren, denn eine so große Eingabe ist ein Journal-Auszug und keine Unit. Funde sind ebenfalls begrenzt: 20 pro Prüfung und 200 insgesamt, und das Panel nennt die Grenze und die echte Anzahl, sobald eine greift. Die Ergebnisliste ist separat auf 50 Zeilen pro Schweregrad begrenzt, wieder mit der echten Zahl.

More free, private DevOps tools.

Der Systemd Unit Validator ist eines der Tools in OpsCanopy — einem wachsenden Dach aus browserbasierten Validatoren, Konvertern und Testern, die nie einen Server berühren.

39 free tools, every one offline-capable — opscanopy.com works with no signup and nothing uploaded.

Verwandt: Cron-zu-systemd-Konverter, um den Timer überhaupt zu schreiben, Cron-Expression-Tester, um die cron-Seite in Klartext zu lesen, der Dockerfile Linter und der GitLab CI Validator für dieselbe „vor dem Ausliefern erwischen“-Prüfung an anderer Konfiguration, und der Env Example Checker für die Variablen, die ein EnvironmentFile= liefern soll — oder durchsuchen Sie das vollständige Tool-Verzeichnis.

Nicht mit dem systemd-Projekt verbunden oder von ihm unterstützt. Direktiven- und Formatnamen beschreiben nur, was dieses Tool prüft; bestätigen Sie eine Unit immer gegen die systemd-Version, die sie laden wird.