Expressions cron démystifiées : cinq champs, plages, pas et le piège OR du jour de semaine
Les cinq champs et leurs plages valides
Toute expression cron standard est composée exactement de 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) et jour de la semaine (0–7). La minute est toujours le champ le plus à gauche — une erreur courante est de lire une ligne cron de droite à gauche, ce qui en inverse complètement le sens. Le champ jour de la semaine accepte à la fois 0 et 7 pour désigner le dimanche, une redondance délibérée héritée de la tradition Unix.
Dans les implémentations suivant les conventions Vixie cron, le champ du mois accepte aussi les abréviations de trois lettres anglaises (jan à dec) et le jour de la semaine accepte sun à sat. L'utilisation de valeurs numériques est plus sûre pour la portabilité. Quartz Scheduler (utilisé dans les applications Java) ajoute un champ secondes en tête, et certains ordonnanceurs cloud comme AWS EventBridge ajoutent un septième champ optionnel année — ces formats étendus ne sont pas interchangeables avec le standard POSIX à cinq champs.
Caractères génériques, listes par virgule, plages par tiret et pas par barre oblique
Quatre caractères spéciaux contrôlent la correspondance d'un champ : l'astérisque (*) correspond à chaque valeur valide du champ — * * * * * se déclenche chaque minute. La virgule (,) crée une liste de valeurs discrètes : 1,3,5 dans le champ du mois signifie janvier, mars et mai. Le tiret (-) définit une plage inclusive : 9-17 dans le champ heure correspond à chaque heure de 9 à 17 inclus — utile pour les planifications 'heures ouvrées uniquement'. Ces trois caractères peuvent être combinés : 1-5,0 dans le champ jour de la semaine correspond du lundi au vendredi plus le dimanche.
La barre oblique (/) introduit une valeur de pas. Utilisée après un astérisque, */15 dans le champ des minutes produit l'ensemble {0, 15, 30, 45} — chaque multiple de 15 à partir du minimum du champ. Utilisée après une plage, 0-30/5 produit {0, 5, 10, 15, 20, 25, 30}. Le pas commence toujours à la borne gauche de la plage (ou à 0 pour *), jamais à un décalage aléatoire. C'est pourquoi */5 dans le champ des minutes se déclenche à 0, 5, 10 … quel que soit le moment où le démon cron a démarré.
Le piège OR entre jour du mois et jour de la semaine
Le comportement le plus surprenant de cron se produit quand les deux champs jour du mois et jour de la semaine sont définis avec des valeurs non génériques. Intuitivement, 0 2 15 * 5 semble signifier 'exécuter à 2h00 du matin uniquement les vendredis tombant le 15 du mois' — une condition ET. En pratique, la plupart des implémentations cron, y compris Vixie cron et dcron, traitent ceci comme une condition OU : la tâche s'exécute à 2h00 le 15 de chaque mois, et aussi à 2h00 chaque vendredi, quelle que soit la date. La page de manuel cron(5) le documente explicitement : 'si DOM et DOW sont tous deux spécifiés, la commande sera exécutée quand l'un ou l'autre champ correspond à l'heure actuelle'.
Ce comportement piège les développeurs qui tentent d'exprimer des conditions de date composées. Si vous avez réellement besoin du 'troisième vendredi de chaque mois', cron ne peut pas l'exprimer directement. La solution idiomatique consiste à planifier chaque vendredi (0 2 * * 5) et à ajouter un test shell au début de la commande : [ $(date +\%d) -ge 15 ] && [ $(date +\%d) -le 21 ] && /chemin/vers/script. Alternativement, utilisez un ordonnanceur de haut niveau supportant nativement les règles de récurrence calendaire (RFC 5545 RRULE), comme les directives OnCalendar= des minuteries systemd.
Raccourcis prédéfinis : @reboot, @hourly, @daily, @weekly, @monthly
Vixie cron a introduit un ensemble de raccourcis nommés remplaçant la syntaxe à cinq champs pour les planifications courantes. @reboot exécute la tâche une seule fois immédiatement après le démarrage du démon cron — utile pour des tâches légères au démarrage, mais pour les services de production, il est préférable d'utiliser un système init adapté (unité systemd, etc.). @hourly est équivalent à 0 * * * *. @daily (alias @midnight) équivaut à 0 0 * * * ; @weekly à 0 0 * * 0 (minuit le dimanche) ; @monthly à 0 0 1 * * (minuit le premier de chaque mois) ; et @yearly (alias @annually) à 0 0 1 1 * (minuit le 1er janvier).
Ces raccourcis améliorent la lisibilité des fichiers crontab, mais dépendent de la reconnaissance de la syntaxe @ par le démon cron — POSIX ne les définit pas, ils sont donc indisponibles dans certaines implémentations minimales. Lors de l'écriture d'expressions cron pour des ordonnanceurs cloud (AWS EventBridge, Google Cloud Scheduler, GitHub Actions), consultez la documentation du fournisseur, car la plupart des plateformes cloud n'implémentent pas les raccourcis @ et exigent le format explicite à cinq champs.
Valeurs de pas et le piège 'pas toutes les N unités à partir de maintenant'
Une incompréhension fréquente sur la syntaxe des pas est de traiter */N comme 'toutes les N unités depuis la création de cette tâche'. Ce n'est pas le cas. Le pas est un filtre modulo sur des valeurs de temps absolues. */5 dans le champ des minutes est un raccourci pour l'ensemble {x : x mod 5 = 0, 0 ≤ x ≤ 59} — douze positions d'horloge fixes par heure. Le démon cron compare ces positions à la minute courante de l'horloge ; il n'a aucune mémoire du moment où votre tâche a été enregistrée.
Cela produit un problème subtil avec 0 */6 * * *. Cette expression se déclenche aux heures 0, 6, 12 et 18 — minuit, 6h, midi et 18h — exactement quatre fois par jour. Si vous ajoutez cette tâche à 15h en attendant 'la première exécution six heures plus tard à 21h', vous serez surpris : la prochaine exécution est à 18h (heure 18). Pour une exécution à 21h, utilisez la liste explicite 0 9,15,21,3 * * * ou changez l'heure de départ avec 0 3/6 * * * — mais notez que la notation DEBUT/PAS est une extension Vixie cron ; pour une portabilité maximale, utilisez toujours la liste explicite séparée par des virgules.
Fuseaux horaires : le bug invisible dans les planifications cron
Les démons cron traditionnels exécutent toutes les tâches dans le fuseau horaire système de la machine. Sur un serveur configuré en UTC — valeur par défaut pour la plupart des images de base Docker et de nombreuses VMs cloud — 0 8 * * 1-5 ne signifie pas 8h dans votre heure locale ; cela signifie 8h UTC. Si votre équipe est en UTC+9 (Heure Standard du Japon), cette tâche se déclenche à 17h locale. Les expressions cron d'AWS CloudWatch Events (maintenant EventBridge) sont documentées comme étant UTC uniquement. on: schedule: de GitHub Actions est également UTC.
Le changement d'heure (heure d'été) introduit deux modes de défaillance supplémentaires. Quand les horloges avancent (par exemple de 01:59 à 03:00), toute tâche cron planifiée pendant l'heure sautée ne s'exécute tout simplement pas ce jour-là. Quand les horloges reculent (de 02:59 à 02:00), la même minute apparaît deux fois ; si la tâche s'exécute une ou deux fois dépend de l'implémentation du démon. La stratégie la plus sûre en production est de faire fonctionner les serveurs cron en UTC en permanence et de convertir en heure locale dans la couche applicative, ou d'utiliser un ordonnanceur acceptant un paramètre TimeZone explicite, comme les minuteries systemd avec TimeZone=, Kubernetes CronJob avec .spec.timeZone (ajouté dans Kubernetes 1.25), ou AWS EventBridge Scheduler avec des noms de fuseau IANA.