Un compañero pegó este cron: 0 9 star star 1-5, diciendo que corre lunes a viernes a las 9 AM. Lo miré y le dije que no correría este lunes. No es broma — cada uno de los 5 campos tiene sus propias reglas. Si te equivocas en cualquiera, tu schedule no es lo que crees.
Esta guía es para que la próxima vez que leas una expresión cron, no necesites abrir el man page de crontab(5) para entender qué hace realmente.
Resumen en 30 segundos
- Una expresión cron tiene 5 campos, de izquierda a derecha: minute, hour, day-of-month, month, day-of-week.
- Cada campo puede ser star (cualquiera), un número, un rango (9-17), un paso (star/15), o una lista (1,15).
- Sunday en el campo day-of-week es 0 (y se acepta 7 como alias). Nunca escribas 7 por portabilidad — algunos schedulers lo rechazan.
- Cuando tanto day-of-month como day-of-week están restringidos (ninguno es star), la regla de disparo es OR: cualquier coincidencia cuenta.
- En un día de transición DST, cron puede perder una ejecución o disparar dos veces. Es una limitación de cron, no un bug.
5 escenarios reales: trampas campo por campo
Escenario 1: star/15 en minute — en realidad no sabes qué hace
Un compañero escribió star/15 star star star star pensando cada 15 minutos. Realidad: minute=0, 15, 30, 45.
Trampa: muchos programadores asumen que star/15 empieza a contar desde el minuto actual, luego cada 15 después. Error. Empieza desde el límite inferior del campo (0) y selecciona valores por paso. Así que un schedule que empieza a las 5:07:23 primero corre a las 5:15:00, no 5:22:23.
Fix: usa valores explícitos, o abre star/15 en un cron generator y revisa el preview. star/5 significa 0, 5, 10, …, 55 — cinco valores, intervalos de cinco minutos.
Escenario 2: rango de hora 9-17
Tutoriales viejos dicen que horas de trabajo son 9-17. La expresión 0 9-17 star star star significa que dispara en hour=9, 10, 11, 12, 13, 14, 15, 16, 17 — nueve horas diferentes, una vez cada una.
Trampa: no es 9 AM a 5 PM como ventana. No dispara a las 17:30 (la corrida de 17:00:00 es la última), y nada dispara después de las 18:00. Para cada 30 minutos durante horas de trabajo, escribe star/30 9-17 star star star — 9:00, 9:30, 10:00, 10:30, …, 17:00, 17:30 — 18 corridas por día a través de 9 horas.
Escenario 3: day-of-month star vs números específicos
Tarea: correr un reporte el 1 de cada mes. 0 0 1 star star parece correcto, y aquí no hay problema.
Trampa: a veces los equipos quieren último-día-del-mes y escriben 0 0 L star star. Pero L es una extensión de Quartz; Vixie cron (el cron por defecto de Linux) no la reconoce. Para soporte Quartz, necesitas o un scheduler Quartz, o manejarlo en el script.
Fix: un enfoque confiable de fin de mes es fallback de día de semana. Si el último día cae en fin de semana, corre el viernes anterior. Usa 0 0 star star 1-5 con rango day-of-month 28-31, y deja que el script decida si hoy es el último día laborable.
Escenario 4: Sunday en day-of-week es 0, no 7
Tarea: backup cada domingo a las 3 AM. El compañero escribe 0 3 star star SUN — sabes que eso es domingo. Pero si escribe 0 3 star star 7, ¿cómo lo interpretas?
Trampa: Vixie cron / cronie aceptan tanto 0 como 7 como Sunday. Algunos schedulers (Quartz temprano, algunas configs tempranas de AWS EventBridge) solo aceptan 0 y rechazan 7.
Fix: escribe siempre 0. Es el estándar RFC y tiene la mejor portabilidad. Trata el 7 como una extensión específica del parser.
Escenario 5: abreviatura de mes JAN FEB MAR … DEC
Ejemplo de documentación: 0 9 star JAN-MAR star. Sintaxis válida, equivalente a 0 9 star 1-3 star.
Trampa: las abreviaturas solo funcionan en el campo day-of-week (SUN MON TUE WED THU FRI SAT) y en el campo month (JAN … DEC). No puedes usar abreviaturas en minute / hour / day-of-month.
Fix: si tu equipo lee abreviaturas cómodo, úsalas; cuando dudes, vuelve a 0-6 para weekday y 1-12 para mes.
4 hechos contraintuitivos sobre cron
Hecho 1: day-of-month y day-of-week son OR, no AND
Tarea: disparar el domingo de cada semana, O el 1 de cada mes. 0 0 1 star 0 se lee como: solo cuando coincidan 1-del-mes y domingo. Incorrecto.
Regla real: cuando tanto day-of-month como day-of-week están restringidos (ninguno es star), se aplica la regla OR. Cualquier coincidencia dispara. Entonces 0 0 1 star 0 = corridas del 1-del-mes + corridas de cada domingo (puede producir 1-2 disparos en un mes dado dependiendo del calendario).
Para semántica AND estricta, usa un script o cambia de scheduler (k8s CronJob soporta semántica de campos más rica).
Hecho 2: star significa cosas diferentes en campos diferentes
star en minute = cualquier 0-59, cada minuto. star en hour = cualquier 0-23, cada hora en punto. La diferencia es la frecuencia — star de minute es por minuto, star de otros campos significa la frecuencia máxima permitida para ese campo.
Hecho 3: star/S es equivalente a 0/S
star/15 es lo mismo que 0/15, en el campo minute donde el límite inferior es 0. 9-17/2 produce 9, 11, 13, 15, 17 — cinco valores.
Trampa: escribir 1/15 en el campo minute da 1, 16, 31, 46 — cuatro valores, no cada-15-minutos empezando desde 1.
Hecho 4: días DST pueden perder una corrida o disparar dos veces
En las transiciones DST de Norteamérica (segundo domingo de marzo / primer domingo de noviembre), el tiempo local salta o repite una hora. Cron programa contra tiempo local wall-clock, y las implementaciones varían.
Fix: para tareas sensibles al tiempo, no uses cron plano. Usa systemd timer o k8s CronJob en UTC con timezone explícito — sin drift DST.
3 plantillas reales de schedule
Plantilla 1: cada 15 minutos en horas de trabajo
star/15 9-17 star star 1-5
Significado: lunes a viernes, 9 AM a 5 PM incluido, cada 15 minutos.
Frecuencia: 9 horas × 4 por hora = 36 corridas por día laborable, sin fines de semana. ¿Quieres menos? star/30 da 18 por día.
Plantilla 2: fin de semana viernes a las 6 PM
0 18 star star 5
Nota: el valor de day-of-week es 5, no FRI — son equivalentes. Prueba cuál acepta el cron de tu deployment; algunas implementaciones rechazan la abreviatura.
Frecuencia: una vez por semana, viernes 6 PM. Para el lunes por la mañana el reporte ya debería estar en el inbox.
Plantilla 3: backup mensual el 1 a medianoche, fallback al viernes anterior si el 1 cae en fin de semana
Esto no se puede expresar en cron plano — necesita un script wrapper.
Implementación de referencia: un bash script (a.sh) que primero verifique con date si hoy es el primero. Si sí, corre el backup. crontab entonces corre 0 0 28-31 star star 1-5 /path/to/a.sh — día laborable de la última semana de cada mes a medianoche, el script comprueba si hoy es el día antes del 1, y corre el backup si lo es.
Prácticas recomendadas
- No escribas cron a ciegas — abre crontab.guru o nuestro cron generator y visualiza los campos, verifica la descripción en lenguaje natural.
- Siempre revisa la lista de next-run antes de desplegar — piick cron-generator por defecto da las próximas 5 ejecuciones; pega tu expresión, mira si los tiempos coinciden con tu ventana esperada.
- Para tareas sensibles al tiempo usa UTC más TZ explícito — DST es el pecado original de cron; systemd timer o k8s CronJob con spec.timezone=UTC lo evita.
- No metas último-día-del-mes en day-of-month 31 — L es Quartz y poco confiable cross-platform; deja que el script decida.
- Tareas críticas necesitan timeout y retry — a cron no le importa si la tarea terminó; kill -9 timeout no detiene la tarea. Usa systemd OnFailure para reintentos.
Cron es una de las herramientas más prácticas de Unix — 5 campos, sintaxis simple, cross-platform. Construimos cron-generator para convertir los campos en dropdowns más preview en tiempo real más lista de next-run, así es más difícil caer en trampas al escribirlos.
Prueba nuestra herramienta cron generator, cambia a Visual Builder o Raw Editor, pega star/15 9-17 star star 1-5, mira si las próximas 5 ejecuciones caen en horas laborables entre 9-17. Si sí — felicitaciones, tu primer schedule cron aterriza. Si no — usa el feedback de next-runs para encontrar qué campo está mal.