Pourquoi votre tâche cron ne s’exécute pas
Cinq causes fréquentes : PATH minimal, signe % non échappé, mauvais crontab, sortie envoyée par e-mail et script dépendant de votre shell.
Pourquoi votre tâche cron ne s’exécute pas
Une tâche cron qui « ne s’exécute jamais » s’est presque toujours exécutée. Elle s’est exécutée dans un environnement différent de celui de votre shell, a échoué dès la première seconde, et son message a été envoyé à un emplacement que vous ne consultez pas. Cinq causes expliquent presque tous les signalements : le chemin de recherche, le signe pourcentage, le mauvais fichier crontab, la sortie envoyée par e-mail et un script qui attend une session de connexion.
cron est un daemon (un service en arrière-plan) qui lit les fichiers crontab et lance des commandes selon un calendrier. Il ne lit pas votre .bashrc, n’ouvre pas de terminal, ne démarre pas de shell de connexion et ne vous informe pas lorsqu’une commande échoue. Toutes les causes ci-dessous découlent de ces quatre faits.
Examinez-les dans l’ordre et commencez par la question qui les sous-tend toutes : cron s’est-il déclenché ? « cron n’a jamais lancé la tâche » et « la tâche a démarré puis s’est arrêtée » sont deux problèmes différents qui n’ont rien en commun. Répondez donc d’abord à cette question.
Cron s’est-il exécuté ?
Le daemon porte un nom d’unité différent selon la famille de distributions. Vérifiez les deux noms, puis consultez le journal.
systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"Debian et Ubuntu appellent cette unité cron. Fedora, Rocky et Alma l’appellent crond. Un seul de ces noms existe sur une machine donnée. Il est donc normal que l’une des deux commandes signale une unité inconnue ; ce n’est pas une erreur.
Lisez les entrées écrites par votre propre système. Ne cherchez pas une ligne copiée depuis un guide, car le texte diffère selon les implémentations de cron et la configuration de la journalisation. Vous devez vérifier deux points : une entrée existe-t-elle à la minute indiquée par votre planification, et cette entrée nomme-t-elle votre commande ? Une entrée qui nomme votre commande signifie que cron a effectué son travail et que l’échec se situe dans la commande. L’absence totale d’entrée signifie que cron n’a jamais reçu votre planification. Il s’agit de la cause 3 ci-dessous.
Certaines images transmettent les messages de cron à rsyslog, qui les écrit dans un fichier au lieu du journal. Recherchez dans /var/log un fichier nommé d’après cron ou syslog, puis affichez-en la fin.
ls -l /var/log
sudo tail -n 50 /var/log/syslogSi ni l’unité ni le journal n’existent, cron n’est peut-être tout simplement pas installé. Les images cloud minimales et les conteneurs en sont souvent dépourvus.
dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cronCause 1 : cron ne dispose pas de votre PATH
Votre shell interactif construit PATH à partir de /etc/profile, ~/.profile, ~/.bashrc et de tout ce que ces fichiers chargent. Rien de tout cela ne s’exécute pour une tâche cron. cron lance la commande avec son propre environnement minimal. Un programme situé en dehors des répertoires système standard n’est donc pas trouvé. Cela peut concerner des programmes installés dans /usr/local/bin, /opt, par un gestionnaire de versions de langage, dans un environnement virtuel Python ou dans un espace de travail Go. La tâche échoue dès sa première ligne. Le shell écrit alors une erreur du type « not found ». La formulation exacte dépend du shell utilisé.
Trouvez le chemin réel de chaque commande utilisée par votre tâche.
command -v docker
command -v node
readlink -f "$(command -v node)"Écrivez ensuite ces chemins absolus dans la tâche, ou définissez PATH une seule fois au début de la crontab.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool runObtenez cette liste sur votre propre machine avec echo "$PATH", puis supprimez tout ce qui n’existe que dans une session interactive. Une règle est importante : cron ne développe pas les variables dans ces lignes d’affectation. PATH=$PATH:/usr/local/bin contient le texte littéral $PATH:/usr/local/bin. La tâche se retrouve donc avec un chemin de recherche qui ne contient aucun répertoire utilisable. Écrivez la liste complète.
Un gestionnaire de versions nécessite plus qu’un chemin. nvm, pyenv, rbenv et asdf installent une fonction shell ou un répertoire de shims depuis votre .bashrc, mais une tâche cron ne lit jamais ce fichier. Appelez le binaire de la version souhaitée avec son chemin absolu, ou chargez le script d’initialisation du gestionnaire comme première ligne de votre propre script.
Cause 2 : le signe pourcentage termine votre commande
Dans le champ de commande d’une crontab, % n’est pas un caractère ordinaire. Le premier % non échappé termine la commande. Tout ce qui suit est transmis à la commande sur son entrée standard, et chaque % supplémentaire devient un retour à la ligne. Il s’agit d’une véritable fonctionnalité de cron pour fournir une entrée courte à un programme. C’est aussi pourquoi un nom de fichier contenant une date est l’exemple classique d’une entrée de crontab incorrecte.
Écrivez 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site et tar ne voit jamais de date formatée. cron coupe la ligne au premier %. Le shell reçoit donc une substitution de commande incomplète, et le reste de votre ligne lui arrive sur l’entrée standard. Échappez chaque signe pourcentage avec une barre oblique inverse.
0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/siteDeux couches lisent cette ligne, dans l’ordre. \% est une règle cron, appliquée par cron avant de démarrer quoi que ce soit. $(date +\%F) est une substitution de commande, appliquée ensuite par le shell lancé par cron. Il faut identifier la couche qui traite chaque caractère.
La méthode la plus sûre consiste à garder la logique en dehors de la crontab. Placez-la dans un script, où le signe pourcentage n’a aucune signification particulière.
#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/siteLa ligne de crontab ne contient alors qu’un chemin et une redirection. Une crontab lisible en un coup d’œil est plus facile à déboguer.
Cause 3 : quelle crontab avez-vous modifiée ?
Il n’existe pas une seule crontab. Il y a plusieurs fichiers, avec des propriétaires et un nombre de champs différents. Un job écrit dans le mauvais fichier est invisible.
crontab -emodifie la crontab de l’utilisateur qui exécute la commande.sudo crontab -emodifie celle de root. Deux personnes qui déboguent le même serveur finissent souvent par consulter deux fichiers différents.sudo crontab -l -u deployaffiche la crontab d’un autre utilisateur. C’est ainsi que vous vérifiez ce qui est réellement installé pour le compte qui doit exécuter le job./etc/crontabet tous les fichiers de/etc/cron.dcomportent un champ supplémentaire entre la planification et la commande : l’utilisateur à utiliser pour l’exécution. Si vous collez dans/etc/cron.dune ligne de crontab utilisateur à cinq champs, le premier mot de votre commande est interprété comme un nom d’utilisateur.- Les fichiers de
/etc/cron.ddoivent être nommés avec des lettres, des chiffres, des underscores et des tirets. Un fichier nommébackup.shousite.confest ignoré uniquement à cause de son nom. Renommez-le enbackup, puis vérifiez à nouveau le journal. - Les fichiers de
/etc/cron.ddoivent appartenir à root et ne doivent être inscriptibles ni par le groupe ni par les autres utilisateurs.ls -l /etc/cron.daffiche ces deux informations en une seule commande. - Les scripts placés dans
/etc/cron.dailyet dans ses répertoires frères suivent la même règle de nommage et doivent également avoir le bit d’exécution. Si ce bit manque, le script est ignoré sans message. /etc/cron.allowet/etc/cron.denydéterminent qui peut installer une crontab. Si l’un de ces fichiers existe sur votre serveur, consultez-le avant de supposer que votre utilisateur est autorisé à en installer une.
Installez une crontab utilisateur avec la commande crontab au lieu de modifier directement le fichier spool, car crontab analyse le fichier avant de l’installer. Après l’enregistrement, lisez le message affiché par la commande. Si elle refuse le fichier, la version précédente reste active et votre modification n’est jamais appliquée. Le résultat ressemble exactement à un cron qui vous ignore.
Le propriétaire détermine également les permissions. Un job dans la crontab de root crée des fichiers appartenant à root, que l’application qui les lit ne pourra peut-être pas modifier. Un job dans la crontab d’un utilisateur standard ne peut pas lire un répertoire réservé à root. Faites correspondre le propriétaire à la tâche : la maintenance d’une application doit être exécutée par le compte de cette application. C’est le principe qui sous-tend le remplacement de wp-cron de WordPress par un job cron système. Le mode des fichiers créés par votre job provient de l’umask qu’il hérite. Cette valeur n’est pas nécessairement celle de votre shell. Consultez donc comment umask définit les permissions des fichiers si la sortie d’un job devient illisible.
Cause 4 : la sortie est envoyée vers une boîte mail que personne ne consulte
cron récupère tout ce qu’une tâche écrit sur la sortie standard et la sortie d’erreur standard. Si la tâche écrit quoi que ce soit, cron transmet ce texte au système de messagerie local. Le message est adressé au propriétaire de la crontab ou à la valeur indiquée par MAILTO. Sur un VPS minimal, aucun MTA (mail transfer agent) n’est généralement installé. Rien ne distribue donc le message. Votre erreur a existé un instant, puis elle a disparu. C’est la raison pour laquelle une tâche défaillante semble silencieuse.
Redirigez plutôt la sortie vers un fichier que vous contrôlez.
0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1>> ajoute la sortie standard au fichier. 2>&1 redirige la sortie d’erreur standard vers la destination actuelle de la sortie standard. Il doit donc apparaître après la redirection. Dans l’ordre inverse, avec 2>&1 >> file, la sortie d’erreur standard conserve sa destination initiale. L’erreur que vous recherchez est alors précisément la partie qui n’atteint jamais le fichier.
Le journal est l’autre bonne destination. logger écrit dans syslog avec le tag de votre choix.
0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-siteRelisez les messages avec journalctl -t backup-site. La sortie de la tâche reste ainsi à côté des entrées cron, ce qui facilite le suivi de la chronologie. Si vous devez également savoir quelle personne a exécuté quelle commande sur la machine, il s’agit d’un autre système. La section auditer les commandes utilisateur sur votre serveur le présente.
MAILTO="" au début d’une crontab désactive les mails pour les tâches qui suivent. Définir MAILTO avec une adresse réelle n’est utile que si un MTA fonctionnel est installé. Vérifiez donc d’abord que les mails quittent bien la machine avant de compter dessus.
Pendant le débogage, suivez une règle : n’ajoutez jamais > /dev/null 2>&1. C’est la ligne la plus courante dans les crontab, et elle supprime la seule preuve dont vous disposez. Vous pourrez la remettre plus tard, une fois la tâche fonctionnelle.
Cause 5 : le script suppose un environnement que cron ne lui fournit pas
Une fois la commande trouvée et sa sortie capturée, il reste tout ce que votre session vous fournit implicitement.
- Le shell n’est peut-être pas bash. Vérifiez-le avec
ls -l /bin/sh. Sur Debian et Ubuntu, il pointe vers dash. Le test avec doubles crochets, les tableaux etsourceéchouent alors avec une erreur de syntaxe. Ajoutez une ligne#!/bin/bashau script et exécutez le script, ou définissezSHELLau début de la crontab. - Le répertoire de travail n’est pas celui dans lequel vous vous trouviez. Utilisez des chemins absolus partout, ou utilisez
cdpour vous placer dans le répertoire voulu sur la première ligne du script. Un chemin relatif est la cause la plus fréquente lorsqu’une tâche « fonctionne quand je l’exécute manuellement ». - La locale n’est pas celle de votre session. Toute commande qui formate une date ou un nombre, ou qui trie du texte, peut produire une sortie différente avec une autre
LANG. Si une étape ultérieure analyse cette sortie, définissez la locale dans le script au lieu de compter sur l’environnement. - Aucun TTY (terminal) n’est disponible. Une commande qui demande une confirmation, ouvre un éditeur ou affiche une barre de progression peut rester bloquée ou quitter. Ajoutez l’option non interactive proposée par l’outil.
- Aucun agent SSH n’est disponible.
SSH_AUTH_SOCKn’est pas présent dans l’environnement de cron. Une commandesshoursyncqui fonctionnait parce que votre agent était chargé échoue alors à s’authentifier. Attribuez à la tâche sa propre clé, appartenant à l’utilisateur qui l’exécute. - Aucun bus de session utilisateur n’est disponible.
systemctl --userexécuté depuis une tâche cron échoue tant queXDG_RUNTIME_DIRn’est pas défini. Une unité systemd est préférable.
Sur Fedora, Rocky et Alma, il existe un autre suspect. SELinux restreint les tâches cron. Une tâche qui accède à un chemin portant un label inattendu est donc refusée, même si les permissions du fichier semblent correctes. Recherchez les refus avec sudo ausearch -m avc -ts recent, puis consultez les bases de SELinux pour un serveur avant de désactiver quoi que ce soit.
La sonde d’une minute qui montre l’environnement de cron
Ne devinez plus ce que contient l’environnement de cron : lisez-le. Écrivez un script qui consigne tout, planifiez-le toutes les minutes, attendez, puis lisez le fichier.
cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.shAjoutez une ligne à la crontab de l’utilisateur qui exécute réellement la tâche, avec des chemins absolus de part et d’autre.
* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1Attendez une minute, puis lisez /home/deploy/cron-probe.log et comparez-le avec les mêmes commandes exécutées dans votre propre shell. La ligne PATH, le répertoire de travail et la locale expliquent généralement l’échec à eux seuls. Notez deux détails de cette configuration : les signes pourcentage se trouvent dans le script, où la règle de cron ne s’applique pas, et le chemin du journal est accessible en écriture par l’utilisateur qui exécute la tâche.
Supprimez cette ligne de la crontab dès que vous avez trouvé la cause. Une tâche exécutée toutes les minutes et ajoutant des données à un fichier remplira un petit disque, sans attirer l’attention.
La planification est-elle bien celle que vous vouliez ?
Une ligne de crontab utilisateur commence par cinq champs : minute, heure, jour du mois, mois, jour de la semaine. Deux de ces champs interagissent d’une manière qui surprend souvent.
Lorsque le jour du mois et le jour de la semaine sont tous les deux restreints, c’est-à-dire qu’aucun des deux ne vaut *, cron exécute la tâche lorsque l’un ou l’autre des champs correspond. 0 0 13 * 5 ne signifie pas « vendredi 13 ». La tâche s’exécute à minuit le 13 de chaque mois et à minuit chaque vendredi. Pour cibler un seul jour précis, laissez l’un des deux champs à * et testez l’autre dans le script.
cron utilise le fuseau horaire du système. De nombreuses images VPS sont configurées sur UTC (temps universel coordonné). Une tâche planifiée à 03:00 s’exécute donc à 03:00 UTC, ce qui peut correspondre au milieu de votre après-midi. timedatectl affiche le fuseau horaire réellement utilisé par votre serveur. Consultez cette valeur au lieu de supposer qu’elle correspond à celle de votre ordinateur portable.
Deux autres pièges liés à la planification sont à connaître. @reboot s’exécute lorsque cron démarre lui-même. Ce n’est pas nécessairement le moment où le réseau est prêt. Une tâche qui nécessite le DNS ou un hôte distant peut donc échouer au démarrage, puis réussir à chaque exécution manuelle. Rien n’empêche non plus une tâche lente de redémarrer alors que l’exécution précédente est encore en cours. Utilisez un verrou.
*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1flock -n abandonne immédiatement lorsque le verrou est déjà pris. L’exécution concurrente s’arrête donc au lieu de s’ajouter à la première.
Quand un timer systemd est le meilleur choix
cron est efficace pour une seule tâche : exécuter cette commande à cette heure. Il est limité pour tout le reste. Un timer fournit le journal sans redirection, un code de sortie que vous pouvez consulter ultérieurement, un ordonnancement par rapport à network-online.target et un délai aléatoire. Ainsi, une centaine de serveurs ne démarrent pas tous à la même seconde. Lorsque votre tâche nécessite l’un de ces éléments, un service et un timer systemd sur un VPS demandent moins de travail que la maintenance d’une ligne crontab. La rédaction de la partie service vous oblige à répondre à une question que cron ne pose jamais : comment l’unité sait-elle que le travail a réellement démarré ? Lisez donc d’abord la signification de Type= pour les unités simple, forking et notify. Avec le type par défaut, un script qui se daemonize lui-même laisse l’unité active alors qu’aucun processus ne reste en arrière-plan. Le comportement en cas de nouvelle tentative se configure également à cet endroit, car les politiques de redémarrage de systemd déterminent ce qui se passe après un échec. cron n’apporte aucune réponse à cette question.
Conservez cron pour les petites tâches. Déplacez vers un timer toute tâche qui dépend d’autres services ou qui nécessite une politique de nouvelle tentative. Les deux peuvent fonctionner sur le même serveur. Vous n’avez donc pas à terminer cette migration en une seule fois.
FAQ
Pourquoi mon cron fonctionne-t-il à la main, mais échoue-t-il depuis cron ?
Parce que votre shell et l’environnement de cron sont différents. Votre login shell lit /etc/profile et ~/.bashrc, qui définissent PATH, la locale et les variables de votre agent. cron lance la commande sans ces éléments, depuis un autre répertoire de travail et parfois avec un autre shell. Utilisez des chemins absolus pour chaque commande. Définissez les variables nécessaires au début de la crontab ou dans le script. Planifiez une tâche de test exécutée toutes les minutes avec env | sort, pwd et id, et redirigez sa sortie vers un fichier journal. Vous pourrez ainsi examiner l’environnement réel de cron au lieu de le déduire.
Comment vérifier que cron a réellement exécuté ma tâche ?
Consultez le journal du daemon. Utilisez journalctl -u cron sur Debian et Ubuntu, ou journalctl -u crond sur Fedora, Rocky et Alma. Sur certaines images, rsyslog envoie plutôt ces messages vers un fichier situé sous /var/log. Recherchez une entrée à la minute indiquée par votre planification et vérifiez qu’elle mentionne votre commande. L’absence d’entrée signifie que cron n’a jamais reçu cette planification. Vérifiez donc que vous avez modifié la bonne crontab. Une entrée sans résultat signifie que la commande a démarré puis s’est arrêtée. Capturez sa sortie avec une redirection.
Pourquoi date +%Y ne fonctionne-t-il pas dans une crontab ?
cron traite % de manière spéciale dans le champ de commande. Le premier % non protégé termine la commande. Tout ce qui suit est transmis à cette commande sur son entrée standard. Chaque % supplémentaire devient un retour à la ligne. Un nom de fichier contenant une date formatée n’atteint donc jamais le programme auquel il était destiné. Échappez chaque signe de pourcentage avec \%, ou placez la commande dans un script et appelez ce script depuis cron. Dans un script, le signe de pourcentage n’a aucune signification spéciale.
Où va la sortie de ma tâche cron ?
Elle est envoyée au système de messagerie local, à l’utilisateur propriétaire de la crontab ou à l’adresse indiquée par MAILTO. La plupart des images VPS n’ont aucun agent de transfert de courrier installé. Le message est donc supprimé et la tâche semble silencieuse. Redirigez la sortie vers un fichier avec >> /path/to/log 2>&1, en conservant cet ordre pour que la sortie d’erreur standard suive la sortie standard. Vous pouvez aussi la transmettre à logger -t myjob, puis la relire avec journalctl -t myjob. N’utilisez pas > /dev/null 2>&1 pendant le diagnostic.
Dois-je utiliser cron ou un timer systemd ?
Utilisez cron pour une commande simple exécutée à heure fixe, notamment si vous devez pouvoir la déplacer vers une machine qui n’exécute pas systemd. Utilisez un timer lorsque vous voulez conserver la sortie dans le journal sans redirection, consulter le code de sortie, ordonnancer l’exécution après la disponibilité du réseau, ajouter un délai de démarrage aléatoire ou réessayer après un échec. Les deux solutions peuvent fonctionner sur le même serveur. Vous pouvez donc déplacer les tâches progressivement.