Sur la même machine, deux scripts Node.js tournent en arrière-plan. Le premier publie sur dev.to à 9h et sur LinkedIn à 10h — un cron, trois lignes de config. Le second surveille une file de jobs toutes les 30 secondes, maintient un état en mémoire, et réagit aux actions de l'interface utilisateur en quelques secondes — un daemon supervisé par systemd.
Même langage, même serveur, objectif apparent identique : exécuter des tâches en arrière-plan. Pourtant le choix n'est pas le même. Ce n'est pas une question de goût ou d'habitude — c'est que les deux problèmes ne se ressemblent que de loin.
Le cas simple : un cron pour la publication
Le cron de publication est d'une banalité assumée. Une entrée dans la crontab utilisateur :
0 9 * * * bash /home/folken/work/cv/scripts/devto-cron.sh >> logs/devto-cron.log 2>&1
0 10 * * * /usr/bin/node /home/folken/work/cv/scripts/linkedin-cron.js >> logs/linkedin-cron.log 2>&1
Le script shell charge les variables d'environnement depuis un fichier .env
et appelle le script Node.js. Ce dernier lit devto-schedule.json,
prend le premier article en statut drafted, appelle l'API dev.to,
met à jour le fichier, et s'arrête. Durée d'exécution : quelques secondes.
En cas d'échec, le log contient l'erreur, l'article reste en queue pour le lendemain.
Pourquoi le cron suffit ici ? Parce que la tâche est atomique. Elle n'a pas besoin de savoir ce qui s'est passé la veille. Elle ne doit pas réagir à un événement extérieur. Elle ne risque pas de se chevaucher avec elle-même. Un process démarre, fait son travail, s'arrête. C'est exactement ce pour quoi le cron a été conçu.
Là où le cron commence à coincer
Le système de veille automatisée pose un problème différent. Il y a quatre veilles actives (crypto, tech, Epstein, rétro), chacune avec sa fréquence propre (de 6h à 7 jours). L'interface d'administration permet de déclencher une veille manuellement depuis le navigateur. Un job "generate" peut être enfilé à tout moment pour reconfigurer une veille via Claude.
Avec un cron, on se heurte rapidement à plusieurs problèmes :
- Granularité minimale de 1 minute. Impossible de réagir en 5 ou 10 secondes à un job enfilé depuis l'UI. L'utilisateur clique, attend, ne voit rien se passer.
- Pas d'état entre les runs. Pour savoir quand a eu lieu la dernière exécution de chaque veille, il faut le lire depuis un fichier à chaque démarrage. Ce n'est pas dramatique, mais ça complique la coordination entre plusieurs veilles qui partagent les mêmes ressources.
- Pas de file de jobs. Si deux crons se déclenchent simultanément et que
l'un des deux tente d'écrire les mêmes fichiers, il faut gérer l'overlap avec
flockou équivalent — ce qui fonctionne, mais demande de l'attention. - Pas de log structuré natif. La sortie standard redirigée vers un fichier devient vite illisible quand plusieurs veilles écrivent en parallèle dans le même log.
Ces limites ne sont pas insurmontables individuellement. Mais empilées, elles indiquent qu'on essaie de faire faire au cron quelque chose qui n'est plus son terrain.
Le daemon : un process qui ne dort jamais
veille-daemon.js tourne en continu. Il poll jobs.json toutes les 30
secondes pour exécuter les jobs on-demand, et cron-config.json toutes les 60
secondes pour déclencher les veilles planifiées dont la fréquence est dépassée.
Il tient en mémoire le registre des veilles et l'état de ce qui tourne.
Quand l'utilisateur clique "Relancer" dans l'interface, le PHP écrit un job dans
jobs.json. Le daemon le détecte au prochain poll (au pire 30 secondes),
l'exécute, et met à jour le statut. L'interface peut afficher la progression en temps réel
via un endpoint /veille/status.
systemd supervise le daemon avec un fichier .service minimal :
[Unit]
Description=Veille daemon
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/folken/work/cv
ExecStart=/usr/bin/node scripts/veille-daemon.js
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
Ce que ça apporte concrètement : redémarrage automatique en cas de crash,
logs dans journald (journalctl --user -u veille-daemon -f),
démarrage automatique au boot avec systemctl --user enable veille-daemon.
Plus de script de relance manuel, plus de fichier de log à surveiller séparément.
systemd timer : le milieu du gué
Il existe une troisième option que l'on sous-estime souvent : le systemd timer.
C'est essentiellement un cron amélioré — une tâche périodique, mais supervisée par systemd.
Sur ce projet, crypto-veille.timer en est un exemple legacy :
# crypto-veille.service
[Unit]
Description=Crypto veille job
After=network.target
[Service]
Type=oneshot
WorkingDirectory=/home/folken/work/cv
ExecStart=/usr/bin/node scripts/crypto-veille.js
# crypto-veille.timer
[Unit]
Description=Crypto veille — toutes les 6h
[Timer]
OnBootSec=5min
OnUnitActiveSec=6h
[Install]
WantedBy=timers.target
Par rapport à un cron, le timer systemd ajoute : des logs natifs dans journald,
la possibilité de déclarer des dépendances (After=network.target),
un délai au boot avec OnBootSec (le cron peut se déclencher trop tôt
si la machine vient de redémarrer), et des expressions de calendrier plus lisibles
que la syntaxe cron classique.
La contrepartie : deux fichiers à gérer au lieu d'une ligne. Pour une tâche simple qui n'a pas besoin de logs structurés et dont la fréquence minimale est 1 minute, le cron reste plus direct.
Tableau comparatif
| cron | systemd timer | daemon | |
|---|---|---|---|
| Granularité | 1 min minimum | 1 seconde | libre (polling) |
| Logs | fichier manuel | journald natif | journald natif |
| État entre runs | aucun | aucun | en mémoire |
| Réaction on-demand | non | non | oui |
| Supervision crash | non | oui | oui |
| Complexité config | très faible | moyenne (2 fichiers) | élevée (code à écrire) |
| Cas idéal | tâche atomique périodique | tâche périodique + logs/deps | file de jobs, état, on-demand |
Trois questions pour choisir
En pratique, trois questions suffisent à orienter le choix :
1. La tâche doit-elle réagir à un événement en moins d'une minute ?
Si oui : daemon. Le cron ne peut pas faire mieux qu'une minute, et le timer systemd
non plus (il exécute des tâches planifiées, pas réactives).
2. A-t-elle besoin d'état entre les runs ?
Si oui : daemon. Lire et écrire un fichier à chaque run fonctionne jusqu'à un certain
point, mais dès qu'il y a plusieurs workers ou des décisions à prendre sur l'historique
récent, un process continu avec état en mémoire est plus propre.
3. Est-ce que la supervision et les logs structurés comptent ?
Si oui mais que les deux premières réponses sont non : systemd timer.
Il donne les bénéfices de systemd (journald, redémarrage, dépendances) sans
la complexité d'écrire un event loop.
Si les trois réponses sont non : cron. Trois lignes dans la crontab, une redirection vers un fichier log, zéro infrastructure supplémentaire. Ne pas sur-engineer ce qui n'en a pas besoin.
Conclusion
Les deux approches coexistent sans friction sur la même machine. Le cron publie des articles à heure fixe depuis des années sans jamais avoir eu besoin d'être relancé manuellement. Le daemon de veille a nécessité quelques jours de travail pour être robuste — gestion des crashes, TTL sur les états bloqués, récupération après redémarrage.
Ce n'est pas cron OU daemon. C'est reconnaître lequel des deux correspond au problème posé. La publication à heure fixe n'a pas besoin de réagir en 30 secondes. La veille interactive ne peut pas se permettre d'attendre une minute entre deux checks. Le choix est dans le problème, pas dans la technologie.