Sintaxis de Expresiones Cron Explicada
Las expresiones cron son uno de esos temas que los desarrolladores aprenden una vez, olvidan los detalles y vuelven a buscar cada seis meses. Una cadena de cinco caracteres como 30 9 * * 1-5 puede codificar una programación sorprendentemente específica — "todos los días de semana a las 9:30 AM" — pero la sintaxis tiene suficientes casos límite como para generar confusión real: campos con índice 0 frente a índice 1, el domingo siendo a la vez 0 y 7, la diferencia entre el estándar Unix y la extensión de seis campos de Quartz, y el comportamiento sutil de los valores de paso combinados con rangos.
Esta guía cubre la sintaxis cron desde los primeros principios y luego repasa cada carácter especial con ejemplos concretos. Ya sea que estés escribiendo un cron job para un daemon de Linux, configurando un schedule de GitHub Actions o configurando una regla de AWS EventBridge, los mismos conceptos subyacentes aplican — con algunas diferencias importantes entre plataformas explicadas a lo largo del camino.
Los Cinco Campos Estándar
Una expresión cron estándar tiene cinco campos separados por espacios, leídos de izquierda a derecha: minuto, hora, día del mes, mes y día de la semana. Cada campo restringe cuándo se ejecuta el job en esa dimensión. Un job se ejecuta solo cuando los cinco campos coinciden simultáneamente.
# Campo Posición Rango Caracteres especiales
# ─────────────────────────────────────────────────────────────
# Minuto 1 0-59 * / , -
# Hora 2 0-23 * / , -
# Día del mes 3 1-31 * / , - ? L W
# Mes 4 1-12 o ENE-DIC * / , -
# Día de la semana 5 0-7 (0=7=Dom) o DOM-SAB * / , - ? L #
Leyendo una expresión como 30 9 * * 1-5: minuto 30, hora 9, cualquier día del mes, cualquier mes, día de la semana del 1 al 5 (lunes a viernes). Resultado: 9:30 AM todos los días de semana.
Campo 1: Minuto (0–59)
El campo de minuto especifica en qué minuto(s) de la hora debe ejecutarse el job. 0 significa el inicio de la hora, 30 significa la media hora, 59 significa un minuto antes de que cambie la hora. Usar * ejecuta el job cada minuto.
Campo 2: Hora (0–23)
Las horas usan formato de 24 horas. 0 es medianoche, 12 es mediodía, 23 es las 11:00 PM. No existe AM/PM en cron — un job a las 9:00 AM es la hora 9, y un job a las 9:00 PM es la hora 21.
Campo 3: Día del Mes (1–31)
A diferencia de los demás campos, el día del mes empieza en 1, no en 0. Los valores válidos son del 1 al 31. Si especificas un día que no existe en un mes determinado (por ejemplo, el día 31 en abril), el job simplemente no se ejecuta ese mes. En Quartz, el carácter especial L representa el último día del mes, independientemente de cuántos días tenga.
Campo 4: Mes (1–12)
Los valores de mes van de 1 (enero) a 12 (diciembre). Muchos programadores también aceptan abreviaturas de tres letras: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC. No distinguen entre mayúsculas y minúsculas en la mayoría de las implementaciones.
Campo 5: Día de la Semana (0–7)
Este es el campo con más carga histórica. Tanto 0 como 7 representan el domingo — esto es una peculiaridad de compatibilidad del Unix original. El lunes es 1, el martes 2, hasta el sábado 6. Los nombres de tres letras también funcionan aquí: SUN, MON, TUE, WED, THU, FRI, SAT.
Cuando se especifican tanto el día del mes como el día de la semana (ninguno es *), la mayoría de los daemons cron de Unix usan lógica OR: el job se ejecuta si coincide con cualquiera de los dos campos. Esto sorprende a muchos desarrolladores. Si quieres "el día 15, pero solo si es un día de semana", necesitas manejar eso en el script del job, no en la expresión cron.
Caracteres Especiales
Asterisco (*) — Todos los Valores
El asterisco coincide con todos los valores válidos para un campo. En el campo de minutos, * significa los minutos del 0 al 59. En el campo de mes, significa los 12 meses. Es el carácter más común en las expresiones cron.
Barra (/): Valores de Paso
La barra define un intervalo de paso. */5 en el campo de minutos significa "cada 5 minutos". Más precisamente, significa "cada valor del rango que es divisible por 5 empezando desde el primer valor del rango". Así, */5 en minutos resulta en 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55.
Los pasos se pueden combinar con rangos: 10-30/5 en el campo de minutos significa cada 5 minutos entre el minuto 10 y el minuto 30, resultando en 10, 15, 20, 25, 30. El paso empieza desde el límite inferior del rango.
Coma (,) — Lista de Valores
Las comas crean una lista de valores específicos. 1,3,5 en el campo de mes significa enero, marzo y mayo. 9,17 en el campo de horas significa las 9:00 y las 17:00. Puedes listar tantos valores como necesites y combinarlos con otras construcciones.
Guion (-) — Rango
Un guion especifica un rango inclusivo. 1-5 en el campo día de la semana significa de lunes a viernes. 9-17 en el campo de horas significa de las 9 AM a las 5 PM. Los rangos son inclusivos en ambos extremos.
Signo de Interrogación (?) — Sin Valor Específico (Solo Quartz)
El cron Unix estándar no soporta ?. En Quartz Scheduler, se usa en los campos día del mes o día de la semana para significar "no me importa este campo". Como Quartz no siempre puede resolver conflictos entre día del mes y día de la semana, uno de los dos debe estar en ? cuando el otro está especificado. 0 0 15 * ? significa "el día 15 de cada mes, cualquier día de la semana". 0 0 ? * MON significa "cada lunes, cualquier día del mes".
L — Último (Solo Quartz)
L en el campo día del mes significa el último día del mes. L en el campo día de la semana significa sábado, o cuando se combina con un número (por ejemplo, 5L), la última ocurrencia de ese día de la semana en el mes. 5L es el último viernes del mes.
W — Día Hábil Más Cercano (Solo Quartz)
W en el campo día del mes encuentra el día hábil más cercano (lunes a viernes) al día dado. 15W significa el día hábil más cercano al día 15. Si el día 15 cae en sábado, el job se ejecuta el día 14 (viernes). Si cae en domingo, se ejecuta el día 16 (lunes). W no cruza los límites del mes.
Almohadilla (#) — N-ésimo Día de la Semana del Mes (Solo Quartz)
# especifica la N-ésima ocurrencia de un día de la semana en un mes. 5#2 significa el segundo viernes del mes. 2#1 significa el primer martes. Si la ocurrencia especificada no existe en un mes determinado, el job no se ejecuta ese mes.
Patrones Cron Comunes
# Cada minuto
* * * * *
# Cada día a medianoche (00:00)
0 0 * * *
# Cada día a las 9:30 AM
30 9 * * *
# Cada lunes a las 8:00 AM
0 8 * * 1
# El primer día de cada mes al mediodía
0 12 1 * *
# Cada 15 minutos
*/15 * * * *
# Días de semana a las 6:00 PM
0 18 * * 1-5 Puedes probar y validar cualquiera de estos usando la herramienta Cron Parser, que muestra las próximas cinco horas de ejecución para cualquier expresión y resalta la sintaxis inválida. Usa el Convertidor de Timestamp si necesitas verificar a qué corresponde un timestamp Unix específico en tu zona horaria local.
La Extensión de 6 Campos: Quartz Scheduler
Quartz Scheduler — muy usado en el ecosistema Java y adoptado por muchos programadores empresariales — añade un campo de segundos obligatorio al inicio de la expresión. El formato se convierte en: segundo minuto hora día-del-mes mes día-de-la-semana.
# Quartz 6 campos: segundo minuto hora día mes día-semana
# Ejecutar exactamente en el segundo 00, minuto 30, cada hora
0 30 * * * ?
# Ejecutar a medianoche cada día
0 0 0 * * ?
# Ejecutar cada 5 segundos
0/5 * * * * ?
# Último día del mes a las 10:00 AM
0 0 10 L * ?
# Último viernes del mes a las 3:00 PM
0 0 15 ? * 6L
Diferencias clave respecto al cron estándar: el campo de segundos (0–59) es el primero y es obligatorio; el día de la semana usa 1 (domingo) a 7 (sábado), no 0–6; ? es obligatorio en día del mes o día de la semana cuando el otro está especificado; y L, W y # son caracteres especiales válidos.
Nunca pegues una expresión Quartz en un crontab de Unix sin eliminar el campo de segundos y ajustar la numeración del día de la semana. Los parsers no son intercambiables.
Cron en Diferentes Sistemas
Linux/Unix crontab (Vixie cron)
La forma original y más común. Cinco campos separados por espacios, seguidos del comando. Las variables de entorno como CRON_TZ o TZ establecen la zona horaria. Los crontabs de usuario se editan con crontab -e; los crontabs del sistema se encuentran en /etc/cron.d/. Los atajos @reboot, @daily, @hourly, @weekly y @monthly son ampliamente soportados como alias para patrones comunes.
GitHub Actions
GitHub Actions usa la sintaxis cron estándar de 5 campos, pero la zona horaria es siempre UTC — no hay manera de especificar una zona horaria local en la expresión. Las expresiones se evalúan a nivel de repositorio, no por job. Ten en cuenta que los workflows programados pueden retrasarse varios minutos durante períodos de alta carga en la infraestructura de GitHub.
# Sintaxis de schedule en GitHub Actions (5 campos, solo UTC)
on:
schedule:
# Ejecutar cada día a las 02:30 UTC
- cron: '30 2 * * *'
# Ejecutar cada lunes a las 09:00 UTC
- cron: '0 9 * * 1' La documentación oficial de cron de GitHub Actions indica que el intervalo mínimo es de 5 minutos — las expresiones que se ejecutarían con mayor frecuencia que eso se ignoran silenciosamente.
AWS EventBridge (CloudWatch Events)
AWS EventBridge soporta tanto expresiones de tasa (rate(5 minutes)) como expresiones cron. Su variante cron es de 6 campos pero difiere de Quartz: el día de la semana usa nombres Dom–Sab o 1–7 donde 1 es domingo, todas las horas son UTC, y al menos uno de los campos día del mes o día de la semana debe ser ?. AWS no soporta los caracteres W ni #.
# AWS EventBridge (cron en UTC, variante de 6 campos)
# Se ejecuta cada día a las 10:00 AM UTC
cron(0 10 * * ? *)
# Se ejecuta a las 6:00 PM el último día hábil de cada mes
cron(0 18 L-1 * ? *) Kubernetes CronJob
Kubernetes CronJob usa la sintaxis cron estándar de 5 campos. La zona horaria por defecto es la del proceso kube-controller-manager (normalmente UTC en clústeres gestionados). Kubernetes 1.25 añadió el campo timeZone a la especificación CronJob, permitiendo la configuración de zona horaria por job sin depender de la zona horaria del sistema.
Node.js (node-cron / cron npm)
El popular paquete node-cron soporta expresiones estándar de 5 campos más un 6.º campo de segundos opcional añadido al inicio. El paquete cron es compatible con Quartz. Ambos soportan cadenas de zona horaria (por ejemplo, America/New_York) como opción del constructor. Consulta la documentación de tu paquete específico para confirmar qué formato espera.
Errores Comunes
Suposiciones de zona horaria
Cron se ejecuta en la zona horaria del servidor o contenedor, que con frecuencia es UTC en entornos cloud. Un job destinado a ejecutarse "a las 9 AM hora de trabajo" configurado como 0 9 * * * se ejecutará a las 9 AM UTC — lo que podría ser las 4 AM, las 5 AM o las 11 AM en tu zona horaria local dependiendo del desplazamiento y el horario de verano. Establece siempre CRON_TZ, usa el campo de zona horaria del programador, o convierte explícitamente tu hora objetivo a UTC. El Convertidor de Zona Horaria puede ayudarte a encontrar el equivalente UTC para cualquier hora local en cualquier zona horaria.
Lógica OR en día del mes / día de la semana
En el cron Unix, cuando tanto el día del mes como el día de la semana son distintos de *, el daemon ejecuta el job si coincide con cualquiera de los campos. Así, 0 9 15 * 1 se ejecuta el día 15 de cada mes Y cada lunes — no solo en los lunes que coincidan con el día 15. Para lograr lógica AND, debes implementar la verificación adicional dentro del script del job.
Confundir valores de paso con rangos
*/5 significa "cada 5 minutos empezando desde 0" (0, 5, 10...). 5/5 significa "cada 5 minutos empezando desde 5" (5, 10, 15...). 5-30/5 significa "cada 5 minutos entre el minuto 5 y el minuto 30" (5, 10, 15, 20, 25, 30). Mezclar estos con rangos puede producir resultados inesperados si no eres deliberado con el valor de inicio.
La trampa del "cada segundo"
La resolución mínima del cron en formato estándar de 5 campos es un minuto. No puedes programar un cron job para ejecutarse cada segundo o cada pocos segundos usando un crontab. Para programación sub-minuto, usa un temporizador a nivel de lenguaje (setInterval en Node.js, APScheduler en Python) o un sistema de colas especializado en lugar de cron. Si necesitas cada 30 segundos, ejecuta dos cron jobs: uno a * * * * * y otro que espera 30 segundos antes de ejecutarse.
Olvidar la numeración del mes
En el cron estándar, los meses van de 1 (enero) a 12 (diciembre). En algunas API de fechas en lenguajes de programación, los meses tienen índice 0 (0 = enero, 11 = diciembre). Si estás generando expresiones cron programáticamente a partir de objetos de fecha, comprueba que no estás desfasado en uno en el campo de mes.
Valida tus Expresiones
El Cron Parser en Toova muestra las próximas cinco horas de ejecución programadas para cualquier expresión, soporta tanto la sintaxis de 5 campos como la de 6 campos de Quartz, y señala valores inválidos en línea. Para comprobaciones rápidas, crontab.guru es una herramienta interactiva conocida que describe las expresiones en inglés llano. Ambas son útiles para tener en marcadores.
Cuando trabajes con timestamps y horas programadas, el Convertidor de Timestamp traduce entre timestamps Unix y fechas legibles por humanos en cualquier zona horaria. Para jobs que implican programación sensible a la zona horaria, combínalo con el Convertidor de Zona Horaria para verificar desplazamientos, transiciones de horario de verano y equivalentes UTC antes de escribir tu expresión final.