Comment écrire une expression cron : cinq champs et la règle du OU dont personne ne parle
Publié le 21/05/2026 · 10 min de lecture · Outils pour développeurs
Daniel Okonkwo — Développeur front-end et rédacteur Tech chez Allin
Performance web · Formats de fichiers
Vérifié à partir de 2 sources
Une expression cron, ce sont cinq champs séparés par des espaces, lus de gauche à droite : minute (0-59), heure (0-23), jour du mois (1-31), mois (1-12 ou noms de trois lettres) et jour de la semaine (0-6 en POSIX avec 0 pour dimanche, étendu à 0-7 par la plupart des implémentations de sorte que 7 vaut aussi dimanche). Un astérisque désigne toutes les valeurs de la plage du champ. Un tiret forme une plage inclusive : 8-11 dans le champ heure vaut 8, 9, 10 et 11. Une virgule forme une liste, et listes et plages se mélangent : 1-3,7-9. Une barre oblique ajoute un pas, et c'est là que ça dérape la première fois — un pas parcourt une plage, il ne fixe pas un intervalle : */15 dans le champ minute vaut donc 0, 15, 30 et 45, et */40 vaut 0 et 40 puis plus rien jusqu'à ce que l'heure suivante relance la plage. Le second piège, plus grave, est que le jour du mois et le jour de la semaine se combinent par OU et non par ET. Si les deux sont restreints, la tâche s'exécute dès que l'un des deux correspond. Ainsi 0 0 1 * 1 ne signifie pas le premier lundi du mois : cela se déclenche à minuit le 1er de chaque mois ET à minuit tous les lundis. Pour fixer une tâche à un jour de semaine précis, laisse un astérisque au jour du mois, et inversement. Tout le reste que tu croiseras — @daily, un sixième champ de secondes, le caractère ?, L, W et # — sort du standard et dépend entièrement du cron que tu exécutes.
Minute, heure, jour du mois, mois, jour de la semaine. Les pièges : un pas est une progression dans une plage et non un intervalle, et les deux champs de jour sont combinés par OU — si bien que 0 0 1 * 1 se déclenche le 1er et tous les lundis.
Cinq champs, et un pas n'est pas un intervalle
Lis les champs de gauche à droite et la notation tient dans la tête. L'astérisque représente toute la plage de son champ. Le tiret forme une plage inclusive : 8-11 dans le champ heure fait quatre heures, pas trois. La virgule forme une liste, et listes et plages se combinent librement dans le même champ : 1-3,7-9 est un ensemble légal de six valeurs. Les noms peuvent remplacer les nombres dans les champs mois et jour de la semaine, sur trois lettres, mais le cron Vixie classique n'accepte ni plage ni liste construite à partir de noms — 1-3 passe là où JAN-MAR peut échouer, bonne raison de s'en tenir aux nombres dans tout ce qui doit être portable.
La barre oblique est l'endroit où le modèle mental casse. Un pas parcourt une plage, ce n'est pas un intervalle de répétition, et la plage repart au début de chaque unité parente. Dans le champ minute, */15 parcourt 0, 15, 30, 45, puis l'heure bascule et il repart à 0 — ce qui revient justement à toutes les quinze minutes, personne ne remarque donc la nuance. Écris */40 et l'illusion s'effondre : il parcourt 0 et 40, puis l'heure bascule et il repart à 0, donnant des écarts de quarante minutes puis de vingt. Idem pour une plage bornée : 0-23/2 dans le champ heure donne les heures paires, et 1-9/2 donne 1, 3, 5, 7 et 9. Si tu as réellement besoin d'un intervalle qui ne divise pas son unité, cron ne peut pas l'exprimer en une ligne : il te faut deux entrées ou une garde à l'intérieur de la commande.
Les deux champs de jour se combinent par OU, pas par ET
C'est la règle que la plupart des explications ratent, et ce n'est pas une bizarrerie d'une implémentation : c'est écrit dans la spécification. POSIX énonce que si le jour du mois est donné comme élément ou liste et que le jour de la semaine l'est aussi, alors tout jour correspondant soit au jour du mois soit au jour de la semaine est retenu. Le manuel crontab(5) dit la même chose plus simplement et donne l'exemple canonique : 30 4 1,15 * 5 s'exécute à 4 h 30 du matin le 1er et le 15 de chaque mois, plus tous les vendredis. Pas le 1er ou le 15 lorsqu'ils tombent un vendredi. Les deux ensembles, réunis.
Les règles pratiques qui en découlent sont brèves. Si exactement un des deux champs de jour est un astérisque, l'autre gouverne et il n'y a pas d'ambiguïté — c'est le cas qu'il faut écrire presque à chaque fois. Si les deux sont des astérisques, tous les jours correspondent, ce qui est également sans ambiguïté. C'est seulement quand les deux sont restreints que l'union s'applique, et le résultat n'est presque jamais celui voulu : 0 0 1 * 1 se déclenche environ cinq fois par mois au lieu d'une. Il n'existe pas de syntaxe cron pour l'intersection : si tu as vraiment besoin du premier lundi du mois, l'astuce standard consiste à programmer l'union puis à la restreindre dans la commande — lancer sur 0 0 1-7 * 1 en ouvrant le script par un test vérifiant que le jour de la semaine est lundi, ou sur 0 0 * * 1 avec un test vérifiant que le jour du mois vaut 7 au plus. Les crons de style Quartz ajoutent un opérateur # pour cela, mais il n'est pas disponible dans le cron système.
Tout ce qui dépasse les cinq champs dépend de l'implémentation
POSIX définit cinq champs, l'astérisque, les plages, les listes, et rien d'autre. Chaque commodité vue au-delà est une extension, et les extensions varient. Le pas avec barre oblique est une extension Vixie aujourd'hui quasi universelle mais toujours absente du standard. Les chaînes raccourcies — @yearly et son synonyme @annually, @monthly, @weekly, @daily et son synonyme @midnight, @hourly, et @reboot — viennent de la même lignée, et @reboot en particulier n'a pas de sens fixe d'un système à l'autre, car ce qui compte comme redémarrage dépend du démon. Un sixième champ pour les secondes est courant dans les ordonnanceurs applicatifs comme Quartz, Spring et plusieurs bibliothèques Node, et absent du cron système : une expression copiée depuis la documentation d'un framework vers un crontab sera décalée d'un champ et programmera tout autre chose au lieu d'échouer bruyamment.
Le point d'interrogation appartient à la même famille. Dans Quartz, il signifie aucune valeur spécifique et existe précisément pour lever l'ambiguïté entre jour du mois et jour de la semaine en déclarant un champ non pertinent ; le cron système ne l'accepte pas du tout. Les opérateurs L, W et # pour dernier jour du mois, jour ouvré le plus proche et énième jour de semaine sont eux aussi propres à Quartz. Deux faits d'environnement comptent plus que toute cette syntaxe : le démon exécute les tâches dans le fuseau horaire pour lequel il est configuré, si bien qu'une expression correcte dans un déploiement peut se déclencher une heure trop tôt ou trop tard dans un autre, et lors d'un changement d'heure une tâche programmée dans l'heure supprimée peut ne pas s'exécuter du tout tandis qu'une tâche dans l'heure répétée peut s'exécuter deux fois. Et cron est un déclencheur, pas un exécuteur de tâches — pas de reprise, pas de contrôle de concurrence, aucune mémoire d'un échec précédent. Si deux invocations ne doivent pas se chevaucher, prends toi-même un verrou dans la commande.
| Position | Champ | Valeurs autorisées | L'erreur qu'il invite |
|---|---|---|---|
| 1er | Minute | 0-59 | Écrire * ici alors qu'on voulait 0 — une expression comme * 3 * * * s'exécute soixante fois, chaque minute de l'heure de 3 h |
| 2e | Heure | 0-23 | Raisonner en horloge de 12 heures : il n'y a ni 24 ni après-midi, minuit vaut 0 et 23 h vaut 23 |
| 3e | Jour du mois | 1-31 | Utiliser 31 en attendant douze exécutions par an : les mois plus courts ne correspondent tout simplement jamais, la tâche saute donc en silence février, avril, juin, septembre et novembre |
| 4e | Mois | 1-12, ou JAN à DEC | Croire que les noms passent partout : le cron Vixie classique accepte les noms de trois lettres mais refuse leurs plages ou listes, si bien que JAN-MAR peut échouer là où 1-3 passe |
| 5e | Jour de la semaine | 0-6 en POSIX avec 0 pour dimanche ; 0-7 dans la plupart des implémentations, où 7 vaut aussi dimanche | Restreindre ce champ et le jour du mois en même temps : les deux sont combinés par OU, la tâche se déclenche donc sur les deux ensembles de jours et non sur leur intersection |
Questions fréquentes
- Que signifie exactement */15 ?
- Dans le champ minute, il sélectionne les valeurs 0, 15, 30 et 45 — un pas de quinze à travers la plage 0 à 59, en partant de la première valeur de la plage. Ce n'est pas un minuteur qui se déclenche quinze minutes après la dernière exécution, et il ne survit pas à une division inexacte : */40 dans le champ minute ne sélectionne que 0 et 40, les écarts font donc quarante minutes puis vingt lorsque l'heure suivante relance la plage. Si tu veux un intervalle mesuré depuis la dernière exécution et non depuis le début de l'heure, cron n'est pas le bon outil : un minuteur systemd avec OnUnitActiveSec, ou une boucle dans un processus de longue durée, l'est.
- Comment lancer une tâche toutes les 90 minutes ?
- Pas en une seule ligne, car 90 ne divise pas 60 et un pas reste enfermé dans un seul champ. Le motif se répète toutefois toutes les trois heures, si bien que deux entrées le couvrent exactement : 0 */3 * * * donne 00 h 00, 03 h 00, 06 h 00 et ainsi de suite, et 30 1-23/3 * * * donne 01 h 30, 04 h 30, 07 h 30 et ainsi de suite. Ensemble, elles produisent une exécution toutes les quatre-vingt-dix minutes, et la séquence boucle proprement à minuit puisque vingt-quatre heures font exactement seize intervalles de quatre-vingt-dix minutes. La même astuce à deux entrées marche pour tout intervalle qui divise un nombre entier d'heures ; ce qui n'est pas le cas, comme toutes les 50 minutes, ne peut pas s'exprimer en cron du tout.
- Pourquoi ma tâche nocturne s'est-elle exécutée deux fois, ou pas du tout ?
- La cause habituelle est un changement d'heure. Cron compare l'heure du mur : quand l'horloge avance, une heure qui n'a jamais existé n'est jamais atteinte et une tâche programmée dedans ne s'exécute tout simplement pas ; quand l'horloge recule, une heure survient deux fois et une tâche programmée dedans peut se déclencher deux fois. Les implémentations diffèrent par l'effort de compensation, d'où un même crontab qui se comporte différemment sur deux distributions. Les corrections fiables : programmer les tâches sensibles hors de la fenêtre de transition, faire tourner le démon dans un fuseau sans changement d'heure, ou rendre la commande elle-même sûre à exécuter deux fois. Cette dernière vaut de toute façon : l'idempotence te protège aussi des reprises, des exécutions concurrentes et des relances manuelles.
Articles qui pourraient t'intéresser
Tous les guides →Outils similaires
Sources
Tu as repéré une erreur dans cet article ?