Ubuntu 24.04 : Cron s’exécute manuellement mais pas selon le planning — pièges PATH/environnement et corrections

Cet article vous a aidé ?

Vous lancez le script à la main. Il fonctionne. Vous le programmez dans cron. Il disparaît dans la nuit comme un collègue quand sonne le téléphone d’astreinte.

Sur Ubuntu 24.04, ce n’est généralement pas que « cron est cassé ». C’est que cron est d’une honnêteté implacable : il exécute votre commande avec un environnement minimal, un shell non interactif, des hypothèses de répertoire de travail différentes, et il n’a aucune patience pour le « ça marche sur mon terminal ». Rendre cron ennuyeux, voilà l’objectif.

Ce qui change quand cron exécute votre commande

Quand vous lancez une commande manuellement, vous amenez avec vous beaucoup de bagages invisibles : votre shell interactif, vos fichiers de configuration, vos ajustements de PATH, votre répertoire courant, votre agent SSH chargé, peut-être votre ticket Kerberos, et cette variable exportée de la semaine dernière que vous aviez oubliée.

Cron n’apporte rien de tout cela. La vision de cron ressemble plutôt à : « Voici une heure. Voici une chaîne de commande. Je l’exécuterai avec un petit ensemble de variables d’environnement et je ne devinerai pas ce que vous vouliez dire. » Ce n’est pas de la cruauté. C’est de la fiabilité.

Sur Ubuntu, cron exécute typiquement avec /bin/sh (dash), pas bash, sauf indication contraire. Il s’exécute en mode non interactif. Il ne source pas votre ~/.bashrc. Le PATH par défaut est restreint (souvent /usr/bin:/bin plus quelques chemins sbin selon le contexte). Et si vous comptez sur des chemins relatifs, cron s’exécutera volontiers depuis votre répertoire home ou depuis / suivant la manière dont la tâche est lancée, et vos chemins relatifs pointeront vers le vide.

Règle opérationnelle : considérez cron comme un petit conteneur sans personnalité. Si votre tâche a besoin de quelque chose, déclarez-le explicitement.

Guide de diagnostic rapide

Quand vous êtes d’astreinte, vous n’avez pas de temps pour un débat philosophique avec cron. Vous avez besoin du chemin le plus court vers la vérité. Voici l’ordre qui permet généralement d’identifier le goulet d’étranglement le plus vite.

1) Prouvez que cron a essayé de l’exécuter

  • Vérifiez l’état du service cron (systemd status).
  • Consultez les logs pour la minute exacte où la tâche aurait dû s’exécuter.
  • Confirmez que vous avez modifié le bon crontab (utilisateur vs root vs /etc/cron.*).

2) Capturez l’environnement et stderr/stdout

  • Redirigez la sortie vers un fichier de log avec horodatages.
  • Dumppez env dans un fichier quand la tâche s’exécute.
  • Utilisez des chemins absolus pour tout : interpréteur, script, binaires, fichiers.

3) Éliminez les différences de PATH/shell

  • Définissez un PATH connu dans le crontab (ou en haut du script).
  • Forcer le shell en bash si vous avez utilisé des bash-ismes.
  • Utilisez /usr/bin/env avec prudence (le PATH de cron peut ne pas inclure ce que vous attendez).

4) Vérifiez permissions et identité

  • Confirmez que la tâche s’exécute en tant qu’utilisateur que vous pensez.
  • Vérifiez l’appartenance aux groupes et les permissions des fichiers.
  • Surveillez les échecs silencieux de sudo (cron n’a pas de TTY).

5) Vérifiez l’heure et les spécificités de l’ordonnancement

  • Mauvais fuseau horaire (système vs attentes utilisateur).
  • Problèmes DST (les tâches autour de 02:00 sont maudites deux fois par an).
  • Erreur de syntaxe cron : logique jour-du-mois vs jour-de-la-semaine.

Faits intéressants et contexte historique

  1. « Environnement minimal » de cron est une fonctionnalité, pas un bug. Les premières automatisations Unix supposaient que les scripts devaient déclarer explicitement leurs dépendances, parce que les shells de connexion variaient selon les utilisateurs.
  2. /bin/sh d’Ubuntu est dash, pas bash, pour des raisons de vitesse. Cette décision existe depuis des années et surprend encore des scripts qui « fonctionnaient bien » en mode interactif.
  3. Le cron traditionnel précède systemd de plusieurs décennies. Le modèle mental de cron est « exécuter une commande à une heure », tandis que les timers systemd ajoutent « exécuter avec ordre de dépendances et journald pour les logs ».
  4. Vixie Cron a façonné l’écosystème cron moderne. De nombreuses distributions Linux ont hérité de son comportement, y compris le schéma de livraison par MAILTO.
  5. Le format d’ordonnancement de cron est volontairement compact. Il est optimisé pour la saisie humaine, pas pour le débogage humain ; d’où les erreurs fréquentes de champs inversés.
  6. Historiquement, cron écrivait dans syslog. Sur les Ubuntu modernes, syslog peut encore exister, mais journald est souvent le premier endroit où vous trouverez le trace.
  7. Il existe plusieurs points d’entrée pour cron. Les crontabs utilisateurs, /etc/crontab et les répertoires /etc/cron.* se comportent différemment (notamment : le champ utilisateur dans le crontab système).
  8. Anacron existe parce que les ordinateurs portables se mettent en veille. « Exécuter les tâches quotidiennes même si la machine était éteinte » a résolu un vrai problème des postes des années 1990 et reste pertinent pour les serveurs et VM intermittents.
  9. Certaines implémentations de cron supportent les secondes ; le cron classique non. Si vous avez besoin d’un planning sous la minute, vous êtes déjà hors du domaine de confort de cron.

Tâches pratiques (commandes, sorties, décisions)

Ci-dessous des vérifications testées sur le terrain. Chacune inclut une sortie d’exemple réaliste et la décision à prendre. Exécutez-les dans l’ordre quand vous le pouvez ; sélectionnez quand vous ne pouvez pas.

Task 1: Confirm the cron service is running

cr0x@server:~$ systemctl status cron
● cron.service - Regular background program processing daemon
     Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-29 08:11:02 UTC; 3h 14min ago
       Docs: man:cron(8)
   Main PID: 742 (cron)
      Tasks: 1 (limit: 18639)
     Memory: 2.2M
        CPU: 1.023s
     CGroup: /system.slice/cron.service
             └─742 /usr/sbin/cron -f

Ce que ça signifie : S’il n’est pas active (running), votre tâche n’a jamais eu sa chance. S’il tourne, passez à l’étape suivante.

Décision : Si inactif, démarrez/activez le service. S’il est actif, la défaillance se situe dans l’ordonnancement, l’environnement, les permissions ou votre script.

Task 2: Verify you edited the right crontab

cr0x@server:~$ crontab -l
# m h  dom mon dow   command
*/5 * * * * /home/cr0x/bin/report.sh

Ce que ça signifie : C’est le crontab de l’utilisateur courant. Le crontab root est séparé.

Décision : Si vous vouliez root, lancez sudo crontab -l. Si vous vouliez une tâche système, vérifiez /etc/crontab et /etc/cron.d.

Task 3: Check the job’s minute in logs (journald)

cr0x@server:~$ sudo journalctl -u cron --since "2025-12-29 10:00" --until "2025-12-29 10:10"
Dec 29 10:05:01 server CRON[18342]: (cr0x) CMD (/home/cr0x/bin/report.sh)
Dec 29 10:05:01 server CRON[18341]: (CRON) info (No MTA installed, discarding output)

Ce que ça signifie : Cron a bien lancé la commande à 10:05. Il a aussi rejeté la sortie parce qu’aucun MTA n’est installé.

Décision : S’il n’y a pas de ligne « CMD » à l’heure attendue, vous avez un problème d’ordonnancement/installation. S’il a tourné, capturez maintenant stdout/stderr vous-même.

Task 4: Check syslog-style cron entries (if rsyslog is used)

cr0x@server:~$ grep -i cron /var/log/syslog | tail -n 5
Dec 29 10:05:01 server CRON[18342]: (cr0x) CMD (/home/cr0x/bin/report.sh)
Dec 29 10:00:01 server CRON[18112]: (cr0x) CMD (/home/cr0x/bin/report.sh)

Ce que ça signifie : Même vérité que journald, simplement depuis syslog. Certains environnements gardent les deux ; d’autres non.

Décision : Si /var/log/syslog n’a rien lié à cron, ne paniquez pas ; fiez-vous à journald.

Task 5: Add explicit logging to the crontab entry

cr0x@server:~$ crontab -l | tail -n 3
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /home/cr0x/bin/report.sh >>/var/log/report.log 2>&1

Ce que ça signifie : Vous forcez le shell et le PATH, et vous conservez la sortie.

Décision : Si la tâche « ne fait rien », cela crée un artefact. Si le log reste vide, elle ne s’exécute pas (ou ne peut pas ouvrir le chemin du log).

Task 6: Confirm the log file is writable by that user

cr0x@server:~$ ls -l /var/log/report.log
-rw-r--r-- 1 root root 0 Dec 29 10:04 /var/log/report.log

Ce que ça signifie : Votre utilisateur ne peut pas ajouter à un fichier possédé par root dans /var/log par défaut.

Décision : Écrivez dans un chemin possédé par l’utilisateur (comme ~/cron-logs) ou configurez correctement permissions/rotation. N’« corrigez » pas ça avec chmod 777 sauf si vous aimez les revues d’incident.

Task 7: Capture the cron runtime environment

cr0x@server:~$ mkdir -p /home/cr0x/cron-debug
cr0x@server:~$ crontab -l | tail -n 2
*/5 * * * * env | sort > /home/cr0x/cron-debug/env.txt
*/5 * * * * /usr/bin/date -Is >> /home/cr0x/cron-debug/ticks.log 2>&1
cr0x@server:~$ cat /home/cr0x/cron-debug/env.txt
HOME=/home/cr0x
LANG=C.UTF-8
LOGNAME=cr0x
PATH=/usr/bin:/bin
PWD=/home/cr0x
SHELL=/bin/sh
USER=cr0x

Ce que ça signifie : Le PATH de cron est minimal et le shell est /bin/sh.

Décision : Si votre script dépend de /usr/local/bin, ~/.local/bin, pyenv, rbenv, nvm, conda, etc., vous savez maintenant pourquoi il échoue selon le planning.

Task 8: Prove “command not found” is the failure

cr0x@server:~$ tail -n 5 /home/cr0x/cron-logs/report.log
/home/cr0x/bin/report.sh: line 7: jq: command not found

Ce que ça signifie : Votre shell interactif trouve jq mais cron non.

Décision : Utilisez le chemin absolu (/usr/bin/jq) ou définissez PATH dans le crontab/script. Vérifiez aussi que le paquet est bien installé là où vous le pensez.

Task 9: Locate binaries as cron would

cr0x@server:~$ command -v jq
/usr/bin/jq
cr0x@server:~$ /usr/bin/env -i PATH=/usr/bin:/bin command -v jq
/usr/bin/jq
cr0x@server:~$ /usr/bin/env -i PATH=/usr/bin:/bin command -v aws
/usr/bin/env: ‘command’: No such file or directory

Ce que ça signifie : Avec un environnement nettoyé, même command peut ne pas exister en tant que binaire autonome ; c’est un builtin du shell. De plus, les outils installés dans des chemins utilisateur ne seront pas trouvés.

Décision : Testez votre script dans un environnement lavé. N’assumez rien. Si vous avez besoin de aws depuis ~/.local/bin, définissez le PATH ou appelez-le explicitement.

Task 10: Run the script as cron would (non-interactive, minimal env)

cr0x@server:~$ /usr/bin/env -i HOME=/home/cr0x USER=cr0x LOGNAME=cr0x PATH=/usr/bin:/bin SHELL=/bin/sh /bin/sh -c '/home/cr0x/bin/report.sh' ; echo $?
127

Ce que ça signifie : Le code de sortie 127 est le classique « commande introuvable ». C’est la reproduction propre que vous voulez.

Décision : Corrigez les dépendances (installez les paquets manquants, utilisez des chemins absolus, ou définissez PATH). Ne déboguez plus cela dans votre shell interactif ; il vous ment.

Task 11: Confirm the script has a valid shebang and is executable

cr0x@server:~$ head -n 1 /home/cr0x/bin/report.sh
#!/usr/bin/env bash
cr0x@server:~$ ls -l /home/cr0x/bin/report.sh
-rwxr-xr-x 1 cr0x cr0x 1842 Dec 29 09:55 /home/cr0x/bin/report.sh

Ce que ça signifie : Il est exécutable et a un shebang. Mais notez : /usr/bin/env cherchera bash dans le PATH. Dans cron, le PATH peut ne pas inclure l’endroit où bash se trouve (généralement oui), mais soyez explicite si vous renforcez la sécurité.

Décision : Pour des tâches cron en production, préférez #!/bin/bash si vous dépendez de fonctionnalités bash. C’est ennuyeux et stable.

Task 12: Verify the schedule is what you think it is

cr0x@server:~$ grep -n . /var/spool/cron/crontabs/cr0x | sed -n '1,5p'
1 # DO NOT EDIT THIS FILE - edit the master and reinstall.
2 # m h  dom mon dow   command
3 5 * * * * /home/cr0x/bin/report.sh

Ce que ça signifie : Cela s’exécute à la minute 5 de chaque heure, pas toutes les 5 minutes. Les gens lisent cela de travers constamment.

Décision : Corrigez l’ordonnancement. Si vous vouliez toutes les 5 minutes, utilisez */5 * * * *.

Task 13: Check timezone assumptions

cr0x@server:~$ timedatectl
               Local time: Mon 2025-12-29 11:25:41 UTC
           Universal time: Mon 2025-12-29 11:25:41 UTC
                 RTC time: Mon 2025-12-29 11:25:41
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active

Ce que ça signifie : Votre serveur est en UTC. Si des humains attendent l’heure locale, la tâche « manquera » de plusieurs heures et tout le monde accusera cron.

Décision : Décidez si vous voulez l’heure système en UTC (en général oui) et adaptez les attentes, ou ajustez l’ordonnancement pour correspondre à l’UTC.

Task 14: Detect “sudo needs a tty” failures

cr0x@server:~$ tail -n 10 /home/cr0x/cron-logs/report.log
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required

Ce que ça signifie : Cron n’a pas de TTY interactif, donc les invites de mot de passe échouent.

Décision : Arrêtez d’utiliser sudo interactif dans cron. Utilisez une entrée cron appartenant à root, une règle sudoers restreinte pour une commande spécifique, ou repensez la tâche pour qu’elle n’ait pas besoin d’élévation de privilèges.

Task 15: Confirm file permissions and group membership at runtime

cr0x@server:~$ id cr0x
uid=1000(cr0x) gid=1000(cr0x) groups=1000(cr0x),27(sudo),113(lxd)
cr0x@server:~$ ls -l /data/reports
drwxr-x--- 2 root analytics 4096 Dec 29 08:00 /data/reports

Ce que ça signifie : cr0x n’appartient pas au groupe analytics, donc écrire dans /data/reports échouera.

Décision : Corrigez l’appartenance aux groupes et les permissions correctement, ou écrivez la sortie dans un endroit accessible à l’utilisateur de la tâche.

Task 16: Verify cron allow/deny rules aren’t blocking the user

cr0x@server:~$ sudo ls -l /etc/cron.allow /etc/cron.deny
ls: cannot access '/etc/cron.allow': No such file or directory
-rw-r--r-- 1 root root 0 Apr  5  2024 /etc/cron.deny

Ce que ça signifie : Si /etc/cron.deny existe et contient le nom d’utilisateur, cron refusera le crontab de cet utilisateur.

Décision : Assurez-vous que l’utilisateur est autorisé. Dans beaucoup d’environnements, cela n’est pas utilisé, mais quand c’est le cas, c’est une mine silencieuse.

Pièges PATH et environnement (et corrections)

Si votre tâche s’exécute manuellement mais pas selon le planning, partez du principe PATH/environnement. C’est la cause la plus fréquente, et elle est généralement facile à prouver.

Cron ne lit pas vos dotfiles

Votre shell interactif peut définir PATH dans ~/.profile, ~/.bashrc, ou un « init shell » d’entreprise qui charge des runtimes de langages. Cron ne source pas ces fichiers. Il n’est pas impoli ; il est déterministe.

Correction : Placez les variables d’environnement nécessaires soit :

  • en haut du crontab (idéal quand vous avez besoin de paramètres différents selon la tâche), ou
  • dans le script (idéal quand vous voulez que le script soit portable en dehors de cron).

Définir explicitement PATH (et le garder sobre)

La plupart des tâches cron en production devraient définir un PATH connu puis appeler les binaires critiques avec des chemins absolus. Oui, les deux. Ce n’est pas redondant ; c’est une défense en profondeur contre les déplacements futurs de paquets, les modifications administratives et les scripts de profil « utiles ».

PATH recommandé :

  • /usr/local/sbin:/usr/local/bin pour les outils administrateurs installés localement
  • /usr/sbin:/usr/bin:/sbin:/bin pour les paquets système

Si vous dépendez de ~/.local/bin, ajoutez-le explicitement. Mais demandez-vous pourquoi une tâche cron de production dépend d’installations pip par utilisateur. Ce n’est pas interdit ; c’est juste fragile.

Surprises de locale et d’encodage

En sessions interactives vous avez peut‑être une locale riche ; dans cron vous pouvez obtenir LANG=C ou C.UTF-8. Si votre script analyse des dates, trie des chaînes ou traite du texte non-ASCII, cela compte.

Correction : Définissez LANG et LC_ALL explicitement dans le script. Si vous traitez de l’UTF‑8, décidez-le dès le départ.

Variables d’environnement présentes manuellement mais absentes dans cron

Exemples classiques :

  • SSH_AUTH_SOCK (votre agent SSH) : disponible dans votre terminal ; absent dans cron.
  • AWS_PROFILE / AWS_REGION : dans votre shell ; manquants dans cron.
  • PYTHONPATH / activation de virtualenv : présentes manuellement ; pas dans cron.
  • HTTP_PROXY / NO_PROXY : présents sur les postes ; absents sur les serveurs.

Correction : Injectez les variables requises dans la ligne de crontab ou dans le script. Mieux : ne dépendez pas de l’état d’un agent ou d’une authentification interactive pour des tâches planifiées ; utilisez des identifiants dédiés avec le principe du moindre privilège.

Blague #1 : Cron n’« oublie » pas votre PATH. Il ne l’a jamais appris.

Répertoire de travail, chemins relatifs et umask

Les chemins relatifs sont une commodité jusqu’à ce qu’ils deviennent une stratégie de production. Cron les rend douloureux parce qu’il ne garantit pas un répertoire de travail correspondant à vos attentes de terminal.

Utilisez toujours des chemins absolus dans les tâches cron

Si votre script fait ./bin/tool, ../data ou écrit dans logs/output.log, c’est comme jouer à la loterie : parfois ça marche (quand vous testez depuis le répertoire du script), parfois ça échoue (quand cron l’exécute ailleurs).

Options de correction :

  • Utilisez des chemins absolus partout.
  • Ou cd dans un répertoire connu au début du script (cd /opt/app || exit 1).

Les différences de umask changent les permissions des fichiers

Votre shell interactif peut définir un umask qui produit des fichiers inscriptibles par le groupe ; cron peut utiliser autre chose. Les symptômes sont subtils : des fichiers sont créés mais d’autres processus ne peuvent pas les lire, ou une tâche suivante échoue.

Correction : Définissez explicitement umask dans le script. Décidez quelles permissions vous voulez réellement au lieu d’hériter des « vibes ».

HOME et expansion du tilde

Cron définit généralement HOME, mais ne misez pas là-dessus. De plus, utiliser ~ dans les entrées de crontab peut être incohérent selon le shell et le quoting. Votre script ne devrait pas dépendre de l’expansion du tilde pour trouver des fichiers critiques.

Correction : Utilisez /home/username (ou mieux : un répertoire applicatif dédié) dans cron. L’ennuyeux gagne.

Permissions, utilisateurs, groupes et sudo dans cron

Le moyen le plus rapide pour qu’une tâche cron « marche » est de l’exécuter en root. Le moyen le plus rapide pour obtenir un incident plus tard est de le laisser ainsi.

Sachez quel utilisateur exécute la tâche

Plusieurs endroits permettent de définir des tâches cron :

  • Crontab utilisateur via crontab -e : s’exécute en tant que cet utilisateur.
  • Crontab root via sudo crontab -e : s’exécute en tant que root.
  • /etc/crontab et /etc/cron.d/* : incluent un champ utilisateur explicite.
  • /etc/cron.hourly, daily, weekly, monthly : scripts exécutés en root, mais l’environnement d’exécution diffère (souvent via run-parts).

N’utilisez pas sudo dans cron sauf si c’est conçu

sudo dans cron échoue pour des raisons prévisibles : pas de TTY, invites de mot de passe, réinitialisation d’environnement et restrictions de politique. Si vous avez vraiment besoin de privilèges, préférez :

  • une entrée cron root appelant un script root avec des permissions serrées, ou
  • une règle minimale sudoers autorisant une commande spécifique sans mot de passe, avec des chemins explicites et sans jokers.

Le « truc du wildcard dans sudoers » finira par être utilisé comme une échelle. Vous aimerez peut-être pas qui l’utilise.

Appartenance aux groupes et groupes supplémentaires

Quand cron s’exécute en tant qu’utilisateur, il utilise les memberships de groupe de cet utilisateur. Si vous avez ajouté l’utilisateur à un groupe récemment, il peut être nécessaire d’ouvrir une nouvelle session pour que vos tests manuels reflètent la réalité ; cron peut déjà utiliser la liste de groupes mise à jour, ou votre shell peut ne pas l’avoir. Ce décalage crée des comportements confus « ça marche chez moi ».

Correction : Validez l’accès avec sudo -u user et vérifiez explicitement les permissions. N’inférez pas.

Différences de shell : /bin/sh vs bash, et pourquoi ça compte

Beaucoup de cas « cron échoue mystérieusement » sont en réalité « le script utilise la syntaxe bash, mais cron l’exécute sous sh/dash ». Les échecs peuvent être silencieux si vous ne capturez jamais stderr.

bash-ismes courants qui cassent sous sh

  • [[ ... ]] au lieu de [ ... ]
  • Les tableaux
  • source au lieu de .
  • Substitution de processus : <( ... )
  • Différences de comportement de set -o pipefail

Correction : Écrivez soit des scripts POSIX sh et exécutez-les sous /bin/sh, soit déclarez bash et écrivez en bash. Le mi‑chemin est comment on reçoit des pages à 3h du matin.

Forcer le shell dans le crontab (quand approprié)

Ajoutez en haut du crontab :

  • SHELL=/bin/bash

Mais n’en restez pas là. Utilisez aussi un shebang correct dans le script. Le script doit être exécutable en dehors de cron, et l’interpréteur doit être explicite.

Journalisation : où va la sortie de cron sur Ubuntu 24.04

Cron a deux façons de vous dire ce qui s’est passé : des logs d’exécution, et la sortie depuis votre tâche. Les gens confondent, puis accusent cron d’être « silencieux ». Cron n’est pas silencieux ; vous n’avez juste pas branché le micro.

Logs d’exécution : journald et/ou syslog

Sur Ubuntu 24.04, journalctl -u cron est souvent la source de vérité la plus rapide : il montre que cron a démarré une commande et quel utilisateur l’a lancée. Il ne montrera pas la sortie de votre script sauf si vous la redirigez.

Sortie de la tâche : mail (si vous avez un MTA) ou vos redirections

Traditionnellement, cron envoie stdout/stderr par mail à l’utilisateur (ou à MAILTO). Mais beaucoup de serveurs n’ont pas d’MTA installé. Dans ce cas vous verrez le message « No MTA installed, discarding output ». C’est votre signal : redirigez la sortie vous‑même.

Mon avis fort : les tâches cron en production devraient toujours rediriger la sortie vers un fichier de log ou vers syslog/journald explicitement. Le mail est utile en second canal, pas en canal primaire.

Préférez des logs structurés quand vous pouvez

Si votre tâche est importante (pipelines de données, sauvegardes, exports de facturation), faites mieux que de déverser du texte. Émettez horodatages, codes de sortie, et un résumé sur une seule ligne. Si possible, envoyez les logs à votre système centralisé. Mais même sans cela, un log local avec rotation est un énorme progrès par rapport à « je crois que ça a tourné ».

Cron vs systemd timers : quand basculer

Cron est toujours un cheval de trait. Mais sur Ubuntu 24.04, les timers systemd conviennent souvent mieux quand vous tenez à l’ordre des dépendances, à l’isolation et à la journalisation unifiée.

Restez avec cron quand

  • La tâche est simple et locale.
  • Vous avez besoin de compatibilité avec des patterns plus anciens et la flotte existante.
  • Vous avez déjà une bonne journalisation et détection d’échecs.

Préférez systemd timers quand

  • Vous voulez des logs dans journald automatiquement.
  • Vous avez besoin de retries, de dépendances (network-online), ou de contrôles de ressources.
  • Vous voulez une unité claire que vous pouvez systemctl status et surveiller.

Un modèle mental utile : cron est « déclenche et oublie ». Les timers systemd sont « déclarez l’intention et observez l’état ». En production, l’observabilité l’emporte souvent.

A minimal timer pattern (conceptual)

Même si vous restez sur cron aujourd’hui, apprenez l’approche timer. C’est une issue quand les problèmes d’environnement cron deviennent une corvée répétée. Et oui, vous pouvez toujours exécuter le même script ; vous gagnez juste de meilleurs outils autour.

Trois mini-récits d’entreprise issus de la production

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Une équipe proche des finances avait une tâche d’export nocturne : collecter des chiffres, formater un CSV, le déposer dans un dossier SFTP. Le développeur la lançait manuellement sans problème. En staging aussi. En production, elle échouait « au hasard » deux fois par semaine.

La mauvaise hypothèse était petite : le script utilisait ~/exports et attendait que le répertoire de travail soit la racine du dépôt. Quand quelqu’un testait manuellement, il le lançait toujours depuis le dépôt et dans un shell interactif qui configurait le PATH et quelques variables.

Dans cron, la tâche s’exécutait avec PWD=/home/user et SHELL=/bin/sh. L’expansion du tilde se comportait différemment selon le quoting, et un chemin relatif pointait vers un répertoire inexistant. Le script échouait tôt, écrivait une erreur sur stderr, et cron a essayé de l’envoyer par mail. Il n’y avait pas d’MTA. L’erreur a donc été rejetée. Le seul signal fut l’absence de fichiers, découverte par des humains le lendemain matin.

La correction fut ennuyeuse et décisive : chemins absolus, PATH et locale explicites, et redirection de la sortie vers un fichier de log. Puis ils ont ajouté un simple fichier marqueur « dernier run réussi » et une alerte si celui-ci n’était pas mis à jour avant 02:00. Après cela, la tâche réussissait ou échouait bruyamment, ce qui est l’essentiel.

Mini-récit 2 : L’optimisation qui s’est retournée

Une équipe infra a voulu accélérer une tâche de maintenance qui purgeait d’anciens artefacts. Ils ont remplacé un script prudent par une one-liner plus rapide utilisant find pipé vers xargs -P pour des suppressions parallèles. En test rapide, c’était bluffant. Ça réduisait aussi le temps d’exécution de minutes à secondes dans un petit répertoire.

En production, le mode d’échec prévisible est apparu : espaces et nouvelles lignes dans les noms de fichiers. Le pipeline de suppression parallèle ciblait parfois le mauvais chemin. La plupart du temps c’était sans conséquence. Une fois, il a supprimé un ensemble d’artefacts « courants » parce que la logique de sélection a été corrompue.

L’autre effet indésirable fut la contention des ressources. Les suppressions parallèles ont généré des rafales d’opérations méta qui ont fait monter la latence I/O pour des charges non liées. Rien n’a planté, mais ça a déclenché des timeouts et des retries dans un service dépendant exactement à la minute où la purge tournait. Classique « on a optimisé une chose et payé ailleurs ».

Ils sont revenus à une approche plus lente mais sûre : find -print0 avec délimiteurs nuls, pas de parsing dangereux, et une limitation délibérée du débit. Ce n’était pas glamour. Ça a arrêté de casser la production. Le postmortem a retenu la vraie leçon : si c’est un job cron qui supprime des choses, traitez la rapidité comme une contrainte que vous gagnez, pas comme un défaut.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une équipe plateforme données exécutait un import ETL quotidien via cron. Elle avait une pratique peu sexy : chaque exécution écrivait un log horodaté et un résumé final sur une seule ligne incluant temps d’exécution, nombre d’enregistrements et code de sortie. Elle écrivait aussi un marqueur « .ok » seulement après la réussite de toutes les étapes. Et elle tournait avec set -euo pipefail en bash, avec gestion soignée des exceptions.

Un matin, des tableaux de bord en aval ne montraient pas de données fraîches. Les gens ont tout de suite soupçonné le cluster d’entrepôt. Mais les logs cron ont raconté l’histoire en moins d’une minute : la tâche a démarré, résolu le DNS, puis a échoué sur un appel API spécifique avec une erreur claire de validation TLS. Le code de sortie était non nul, et le fichier marqueur n’avait pas été mis à jour. Pas de mystère.

La cause était un changement de certificat sur un endpoint en amont et un bundle CA obsolète dans une image de container utilisée par le script. L’équipe a mis à jour le bundle, relancé la tâche, et le marqueur est passé au vert. L’incident a été circonscrit parce que la tâche était observable et déterministe.

Il n’y a pas eu de moment héroïque. Juste une équipe qui traite cron comme de la production, pas comme un calendrier magique avec des vibes.

Erreurs fréquentes : symptôme → cause → correction

Cette section est celle que vous voudrez garder ouverte pendant une session de dépannage. Les schémas se répètent entre entreprises, équipes et niveaux de séniorité.

1) Symptom: “It runs in terminal, but cron doesn’t create output files.”

Cause racine : Chemins relatifs ou répertoire de travail inattendu. Le script écrit dans ./output mais cron s’exécute depuis HOME ou /.

Correction : Utilisez des chemins absolus ou cd en haut du script. Ajoutez des logs montrant pwd et ls des répertoires attendus.

2) Symptom: “Cron log shows CMD ran, but nothing happens.”

Cause racine : La sortie a été rejetée parce qu’il n’y a pas d’MTA, ou vous n’avez jamais redirigé la sortie.

Correction : Redirigez stdout/stderr vers un fichier de log. Si vous voulez le mail, installez/configurez un MTA, mais ne comptez pas uniquement dessus.

3) Symptom: “/bin/sh: 1: source: not found” or “[[: not found”

Cause racine : Le script utilise des fonctionnalités bash mais s’exécute sous /bin/sh (dash).

Correction : Ajoutez un shebang bash et/ou définissez SHELL=/bin/bash dans le crontab. Ou réécrivez le script en POSIX sh.

4) Symptom: “command not found” for tools you definitely have installed

Cause racine : Le PATH de cron est minimal ; votre PATH interactif est plus large.

Correction : Définissez explicitement PATH. Préférez les chemins absolus pour les commandes critiques.

5) Symptom: job runs but behaves differently (sorting, parsing, regex)

Cause racine : Différences de locale (LANG/LC_ALL) entre session interactive et cron.

Correction : Définissez la locale explicitement dans le script. Ne parsez pas des sorties humain‑formatées ; utilisez des formats machine quand possible.

6) Symptom: “Permission denied” only under cron

Cause racine : Utilisateur différent, appartenance à un groupe manquante, différences de umask, ou écriture dans des répertoires root comme /var/log.

Correction : Assurez la bonne propriété/permissions et l’appartenance aux groupes. Écrivez les logs dans des chemins appartenant à l’utilisateur ou créez un répertoire de logs dédié avec permissions contrôlées.

7) Symptom: sudo fails in cron

Cause racine : sudo demande un mot de passe ; cron n’a pas de TTY. Ou la politique sudo l’interdit.

Correction : N’utilisez pas sudo interactif. Exécutez en tant qu’utilisateur approprié (root si nécessaire) ou créez des entrées sudoers minimales pour des commandes spécifiques.

8) Symptom: job “doesn’t run” around DST changes

Cause racine : DST peut sauter ou dupliquer des heures locales ; les horaires proches de 02:00 peuvent être manqués ou répétés.

Correction : Gardez les serveurs en UTC. Si vous devez utiliser l’heure locale, évitez les heures fragiles et ajoutez l’idempotence à la tâche.

Blague #2 : L’heure d’été : parce que parfois votre job cron mérite de rater deux fois pour la même erreur.

Listes de vérification / plan pas à pas

Checklist A: Make a cron job production-grade in 15 minutes

  1. Utilisez des chemins absolus pour le script et tous les fichiers qu’il lit/écrit.
  2. Définissez explicitement l’interpréteur dans le script : #!/bin/bash (ou #!/usr/bin/python3, etc.).
  3. Définissez PATH soit dans le crontab soit en haut du script vers une valeur connue et fiable.
  4. Définissez la locale si vous parsez/triez du texte : LC_ALL=C.UTF-8 ou une alternative délibérée.
  5. Journalisez stdout/stderr en pensant à la rotation. Commencez par ~/cron-logs si vous n’avez pas encore de stratégie de logs.
  6. Retournez un code non nul en cas d’échec et ne masquez pas les erreurs avec || true sauf si c’est géré intentionnellement.
  7. Ajoutez un « marqueur de succès » (fichier ou métrique) pour pouvoir alerter sur des exécutions obsolètes.

Checklist B: Debug a failing job without guessing

  1. Confirmez que cron a déclenché : journalctl -u cron.
  2. Redirigez la sortie et reproduisez l’échec dans un fichier de log.
  3. Dumppez l’environnement de cron (env | sort) et comparez avec votre environnement interactif (env | sort dans un shell).
  4. Exécutez la tâche avec un environnement nettoyé via /usr/bin/env -i.
  5. Corrigez PATH, shell, permissions, répertoire de travail dans cet ordre.
  6. Ensuite seulement, déboguez la logique applicative.

Checklist C: Decide whether to move to a systemd timer

  1. Si vous avez besoin d’ordre de dépendances (réseau, points de montage), préférez systemd.
  2. Si vous voulez une journalisation cohérente et des vérifications d’état faciles, préférez systemd.
  3. Si vous avez besoin de « relancer les jobs manqués après une panne », envisagez anacron ou systemd avec des timers persistants.
  4. Si c’est simple et stable, cron suffit—durcissez juste l’environnement.

FAQ

1) Why does my cron job run fine when I paste it into a terminal?

Parce que votre session terminal a un environnement plus riche : PATH, locale, agents d’authentification et fichiers de configuration. Cron tourne avec un environnement minimal et un shell non interactif. Rendre les dépendances explicites.

2) Where do I see cron logs on Ubuntu 24.04?

Commencez par journalctl -u cron. Selon la configuration de logs, cron peut aussi écrire dans /var/log/syslog. La sortie de votre script n’y apparaîtra pas sauf si vous la redirigez.

3) Why am I seeing “No MTA installed, discarding output”?

Cron a tenté d’envoyer stdout/stderr de votre tâche par mail mais aucun MTA n’est installé/configuré. Redirigez la sortie vers un fichier (ou installez un MTA si l’envoi de mail fait partie de votre modèle d’exploitation).

4) Should I set PATH in crontab or in the script?

Si plusieurs tâches cron partagent le même environnement, définir PATH en haut du crontab est pratique. Si le script peut s’exécuter en dehors de cron (CI, systemd, manuel), définissez PATH dans le script aussi. En production, je fais souvent les deux et j’utilise toujours des chemins absolus pour les binaires critiques.

5) My script uses bash arrays. How do I make cron use bash?

Utilisez un shebang bash (#!/bin/bash) et/ou définissez SHELL=/bin/bash dans le crontab. Puis capturez stderr pour voir les erreurs de syntaxe si quelque chose casse encore.

6) Why does cron fail to access network resources that work manually?

Causes courantes : variables proxy manquantes, identifiants absents, DNS non prêt au boot, ou la tâche s’exécute avant qu’un montage réseau soit disponible. Cron ne gère pas l’ordre des dépendances ; systemd timers le font.

7) How do I test exactly what cron will do without waiting?

Utilisez une reproduction avec environnement nettoyé : /usr/bin/env -i avec un ensemble minimal de variables et exécutez le même interpréteur que cron. Cela détecte rapidement les problèmes de PATH et de locale.

8) Is it safe to write logs to /var/log from a user cron job?

Pas par défaut. /var/log est typiquement possédé par root. Soit loggez dans un répertoire appartenant à l’utilisateur, soit créez un répertoire de logs dédié avec permissions contrôlées et règles logrotate. Évitez les logs world-writable.

9) My job runs twice sometimes. Is cron duplicating it?

Cron lui-même ne duplique pas intentionnellement une entrée, mais vous pouvez avoir défini la tâche à deux endroits (crontab utilisateur plus /etc/cron.d, par exemple). DST peut aussi dupliquer des heures locales. Recherchez votre configuration cron partout et gardez les serveurs en UTC.

10) When should I stop fighting cron and use a systemd timer?

Quand vous avez besoin d’ordre de dépendances, de journalisation cohérente, d’inspection d’état facile, de contrôles de ressources ou de sémantique « exécuter après une panne ». Cron convient pour des tâches périodiques simples ; systemd est meilleur pour une planification de production avec observabilité.

Conclusion : étapes pratiques suivantes

Si votre tâche cron sur Ubuntu 24.04 fonctionne manuellement mais pas selon le planning, ne commencez pas par réécrire le script. Commencez par prouver ce que cron a fait, capturez l’environnement, et rendez l’exécution déterministe : chemins absolus, PATH explicite, shell explicite, journalisation explicite.

Ensuite rendez-la observable : conservez un log, enregistrez les codes de sortie, et ajoutez un marqueur de succès ou une alerte. La fiabilité consiste surtout à enlever le mystère, pas à ajouter de l’ingéniosité.

Une idée directrice à garder sur un post‑it : « L’espoir n’est pas une stratégie. » — Gene Kranz

← Précédent
Infobulles sans bibliothèques : positionnement, flèche et modèles compatibles ARIA
Suivant →
10 mythes sur les GPU qui refusent de disparaître

Laisser un commentaire