SYSTEMD · 2026-11-03

systemd : redémarrer un service chaque jour, sans cron

Un timer systemd, pas cron, pour redémarrer un service chaque jour : OnCalendar=daily, RandomizedDelaySec, Persistent. Bench systemd 262 — reçus à l'appui.

HUMANpar @mrkeepalive1h7 min de lecture

Le timer, c'est le moyen intégré de redémarrer un service systemd à intervalle régulier. OnCalendar=daily choisit minuit, RandomizedDelaySec étale la charge après 00:00, Persistent=true rattrape après un arrêt. Cron peut lancer le même redémarrage — mais à l'aveugle, pile à minuit, en même temps que tous les autres jobs de la machine, sans aucune trace du dernier déclenchement. Sur le bench de ce soir, le timer a tiré trois fois en quatre-vingt-quinze secondes et le journal a nommé chacune au dixième de seconde près.

À quoi ressemble la recette du redémarrage quotidien ?

Deux fichiers. L'un ne connaît qu'un seul tour — redémarrer l'appli. L'autre décide du moment. Ni l'un ni l'autre ne touche à l'unité de l'appli elle-même.

# /etc/systemd/system/app-restart.service
[Unit]
Description=scheduled restart of app

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl try-restart app.service

# /etc/systemd/system/app-restart.timer
[Unit]
Description=restart app once a day

[Timer]
OnCalendar=daily
RandomizedDelaySec=45m
Persistent=true

[Install]
WantedBy=timers.target

vérification : sudo systemctl daemon-reload && sudo systemctl enable --now app-restart.timer && systemctl list-timers app-restart.timer — une colonne NEXT remplie signifie que le timer détient le calendrier.

try-restart, pas restart : si l'appli est déjà à l'arrêt, un simple restart la démarrerait en silence — ressuscitant peut-être ce que vous aviez volontairement stoppé. try-restart ne touche que ce qui tourne déjà. Les services qui ne doivent jamais rester à l'arrêt échangent contre restart et dorment tranquilles.

OnCalendar=dailyla roue calendaire · 00:00 RandomizedDelaySecjusqu'à 45 min, tiré à chaque feu Persistent=truele tampon · les jours manqués rattrapés au boot oneshot · try-restartseulement si l'appli tourne l'appli revient, fraîchemême unité, nouveau PID
Cinq pièces mobiles, un redémarrage par jour. Le tampon est le seul état sur disque — un fichier de zéro octet.

Pourquoi pas cron, tout simplement ?

Parce que la béquille cron marche une fois, puis s'appuie sur vous. @daily systemctl restart app tient en une ligne dans la crontab de root — et les questions commencent. Quand a-t-il tiré la dernière fois ? A-t-il seulement tiré pendant que la machine était éteinte ? Pourquoi tous les jobs de la machine ont-ils tamponné 00:00:00 ensemble ? Le man de cron définit @daily comme minuit pile ; chaque service planifié ainsi passe le même tourniquet à la même seconde, et le premier à passer, c'est le disque.

Le bilan honnête : cron redémarre l'appli, et c'est tout ce qu'il fait. Pas de colonne list-timers, pas de tampon sur disque, pas de rattrapage après un arrêt, pas d'unité dans le journal à lire — si tant est que le démon cron soit installé. L'image de base de notre VM de bench n'en embarque aucun. Sur une machine Arch moderne, les timers sont déjà là, sous le PID 1, à attendre.

la questioncron @dailyle timer
Quand a-t-il tiré la dernière fois ?grep les logs, peut-êtrelist-timers, colonne LAST
Machine éteinte à minuit ?sauté, en silencePersistent rattrape au boot
À quel moment exactement ?00:00:00 pile00:00 plus le délai tiré
Un démon en plus à faire tourner ?cron ou crondaucun — le PID 1 s'en charge déjà
Où sont les logs ?envoyés par mail, s'il y vajournalctl -u, structurés

Le cas général — quand un timer bat une ligne cron, et pourquoi — vit dans notre pièce précédente, Cron contre les timers systemd : cron exécute des lignes, les timers exécutent des unités. Celui-ci n'est que le cas particulier du redémarrage nocturne.

Comment arrêter la ruée de minuit ?

Une ligne : RandomizedDelaySec=45m. Après chaque échéance du calendrier, le timer tire un nouveau délai aléatoire, si bien qu'une douzaine de redémarrages nocturnes atterrissent n'importe où dans la fenêtre au lieu d'un unique instant. Le bench a tourné une version minute de la recette — calendrier à :30, délai plafonné à 20 secondes — et chaque tir a atterri ailleurs :

reçus du bench, non édités — systemd 262 sur Arch
Nov 03 05:13:48.974739 bench-arch systemd[1]: Starting scheduled restart of app...
RandomizedDelayUSec=20s
LastTriggerUSec=Tue 2026-11-03 05:14:40 UTC
NextElapseUSecRealtime=Tue 2026-11-03 05:15:43 UTC

Lisez l'arithmétique sur les lignes : calendrier 30, plafond 20 — les tirs sont tombés à 18,97 s, 10,42 s et 13 s. Trois feux, trois secondes différentes, toutes sous le plafond. Cron ne sait pas tirer ; son man planifie à la seconde fixe, pour toujours. Remontez à 45m et vos redémarrages nocturnes s'étalent dans la fenêtre, et le disque ne rencontre plus jamais la foule.

Et si la machine était éteinte à l'heure du redémarrage ?

Persistent=true, c'est la clause de rattrapage. Après chaque feu, le timer touche un tampon de zéro octet — /var/lib/systemd/timers/stamp-app-restart.timer — et le systemd du boot le compare au calendrier. Machine éteinte toute la nuit, boot à 09:00 : le redémarrage manqué s'exécute immédiatement, une fois. Sans la ligne, le feu manqué saute en silence et l'appli attend un jour complet son reset. Le tampon est une promesse de zéro octet : je te dois un redémarrage après le prochain boot.

le répertoire des tampons sur le bench, non édité
-rw-r--r-- 1 root root 0 Nov  3 05:13 stamp-app-restart.timer
-rw-r--r-- 1 root root 0 Oct 24 10:42 stamp-archlinux-keyring-wkd-sync.timer

La seconde ligne n'est pas la nôtre : l'image de base embarque ce timer déjà activé et déjà tamponné. Le motif est l'habitude du planificateur par défaut — les mises à jour du keyring d'Arch rattrapent après un arrêt exactement comme le fera votre appli.

Restart=always, ce n'est pas la même chose ?

Non, et confondre les deux, c'est ainsi qu'on invente des pannes de minuit. Restart=always est piloté par l'échec : le processus principal sort, systemd le relance après RestartSec. Tant que l'appli tourne en bonne santé, il ne tire jamais — il n'y a rien à quoi réagir. Il ne peut pas faire d'entretien calendaire, parce que la santé n'est pas une date. Le timer est piloté par le calendrier : 00:00 arrive, le redémarrage s'exécute, en forme ou non. Gardez les deux si vous avez besoin des deux — Restart=always pour les crashs, le timer pour le reset nocturne. Ils surveillent deux horloges différentes et ne se battent jamais.

Comment vérifier qu'il a réellement tiré ?

systemctl list-timers app-restart.timer --no-pager
journalctl -u app-restart.service --since today --no-pager

vérification : après minuit, NEXT est re-rempli pour demain et LAST se trouve dans votre fenêtre de délai. La ligne du journal qui compte se lit Starting scheduled restart of app... — suivie de l'arrêt et du démarrage de l'appli elle-même :

le journal de l'appli autour d'un feu du bench, non édité
Nov 03 05:13:48.990989 bench-arch systemd[1]: Stopping the app that gets restarted nightly...
Nov 03 05:13:48.993916 bench-arch systemd[1]: Started the app that gets restarted nightly.

Un redémarrage nocturne est un aveu : quelque chose fuit, et personne n'a encore trouvé le trou.

Basculez votre service ce soir avec la checklist et laissez le timer porter l'aveu jusqu'à ce que quelqu'un répare la fuite pour de vrai — puis supprimez les deux unités et laissez la machine dormir par-dessus minuit. C'est la condition de sortie, et le fichier tampon ne vous regrettera pas.

La méthode du bench, pour lire les reçus bruts

VM Arch jetable (image cloud quickemu, aucune modification de l'hôte) : un app.service qui exécute sleep infinity, la recette du timer ramenée à OnCalendar=*-*-* *:*:30 avec RandomizedDelaySec=20 pour qu'un feu tombe dans la minute d'attente, puis 95 secondes à regarder le journal. Trois feux, le fichier tampon, les colonnes de list-timers et la version de systemd sont dans un seul journal de reçus, imprimé non édité ci-dessus. Le troisième feu du bench est tombé sur une horloge que la VM finissait encore de caler au boot ; son arithmétique est la seule ligne que cet article ne défend pas, et le journal la garde.