Volver al blog

Publicado · 9 min de lectura

Expresiones cron desde cero: una guía visual

Aprende la sintaxis cron POSIX de 5 campos, los caracteres especiales, los valores de paso, las extensiones de Quartz, las diferencias del cron de AWS y una docena de ejemplos resueltos que puedes copiar y pegar hoy mismo en un crontab.

Por qué cron sigue estando en todas partes

Cron fue escrito por Brian Kernighan para Unix Versión 7 en 1979. La versión que la mayoría de los sistemas Linux modernos siguen incluyendo — Vixie cron, escrita por Paul Vixie en 1987 — añadió variables de entorno, crontabs por usuario y el cómodo atajo @reboot, pero mantuvo el formato original de cinco campos casi intacto. Esa estabilidad es la razón completa por la que una sintaxis diseñada antes de que existiera la World Wide Web sigue pegándose en manifiestos de Kubernetes, flujos de GitHub Actions, reglas de AWS EventBridge y anotaciones @Scheduled de Spring en 2026.

Si solo aprendes cron copiando líneas de Stack Overflow, tarde o temprano la planificación hará algo que no esperabas: tu copia diaria se ejecuta cada minuto, tu tarea de fin de semana dispara los martes, tu zona horaria salta en una frontera de horario de verano. Esta guía recorre la sintaxis desde la base, y luego muestra suficientes ejemplos resueltos para que puedas reconocer la forma de cualquier expresión de un vistazo.

El formato de 5 campos

Una línea de crontab POSIX es una planificación seguida de un comando. La planificación son exactamente cinco campos separados por espacios en blanco: minuto, hora, día del mes, mes, día de la semana. Cada campo acepta un número, una lista, un rango, un paso o el comodín *.

* * * * *  command-to-run
┬ ┬ ┬ ┬ ┬
│ │ │ │ │
│ │ │ │ └─── day of week   (0-6, Sunday = 0; 7 also = Sunday on most cron implementations)
│ │ │ └───── month          (1-12, or JAN-DEC)
│ │ └─────── day of month   (1-31)
│ └───────── hour           (0-23)
└─────────── minute         (0-59)

Caracteres especiales: * , - / y compañía

Cinco caracteres hacen casi todo el trabajo en el cron clásico. Aprende qué significa cada uno y la mayoría de las expresiones se leen como una frase.

  • * — todos los valores de este campo. * en el campo de minuto significa "cada minuto".
  • , — lista de valores discretos. 0,15,30,45 en el campo de minuto son cuatro minutos fijos por hora.
  • - — rango inclusivo. 1-5 en el campo de día de la semana significa de lunes a viernes.
  • / — valor de paso, escrito como rango/paso o */paso. */10 en minuto significa "cada 10 minutos empezando en 0".
  • L, W, # — extensiones de Quartz, no son parte de POSIX. L = último, W = día laborable más cercano, # = enésimo día de la semana del mes. Más sobre esto abajo.
0,30 * * * *      at :00 and :30 every hour
*/5 * * * *       every 5 minutes (0, 5, 10, 15 ...)
0 9-17 * * 1-5    every hour from 09:00 to 17:00, Mon-Fri
0 0 1,15 * *      midnight on the 1st and 15th of every month
15 14 * * 0       14:15 every Sunday

Los valores de paso no son lo que crees

El operador de paso / siempre actúa contra un rango, incluso cuando el rango es implícito. */15 en el campo de minuto es la forma corta de 0-59/15, que dispara a los 0, 15, 30 y 45. Por eso */7 en minuto NO significa "cada 7 minutos desde ahora" — significa "los múltiplos de 7 entre 0 y 59", lo que te da 0, 7, 14, 21, 28, 35, 42, 49, 56 y luego un hueco de 4 minutos antes del cambio de medianoche. Si necesitas un intervalo rodante real, usa un ejecutor de tareas o un temporizador de systemd con OnUnitActiveSec.

Los pasos también pueden empezar en medio de un rango. 5-59/10 en minuto dispara a los 5, 15, 25, 35, 45, 55. Combina rangos y pasos cuando quieras una planificación no simétrica sin escribir una lista enorme separada por comas.

*/15 * * * *      00, 15, 30, 45  (good)
*/7 * * * *       00, 07, 14, 21, 28, 35, 42, 49, 56, then a 4-min gap (probably not what you want)
5-59/10 * * * *   05, 15, 25, 35, 45, 55
0 */4 * * *       00:00, 04:00, 08:00, 12:00, 16:00, 20:00

Día del mes y día de la semana: la regla del OR

El mayor talón de Aquiles de cron: cuando tanto el día del mes como el día de la semana están restringidos (ninguno es *), Vixie cron los trata como un OR lógico, no AND. La línea 0 0 13 * 5 dispara a medianoche el día 13 de cada mes Y todos los viernes — no solo los viernes 13. Para obtener el comportamiento AND, restringe solo uno de los dos campos y filtra el otro en tu script, o usa un planificador como Quartz que soporta el marcador ? para "sin valor concreto".

0 0 13 * 5        midnight on the 13th OR any Friday  (Vixie cron OR rule)
0 0 * * 5         every Friday at midnight
0 0 13 * *        every 13th of the month at midnight
# Quartz: 0 0 0 13 * 5 ?   would match Friday the 13th specifically

Diez ejemplos para copiar y pegar

Imprime esta tabla y pégala al lado de tu monitor. La mayoría de las líneas de cron de producción que escribirás en tu vida son variaciones menores de estas.

* * * * *           every minute
0 * * * *           every hour at :00
*/15 * * * *        every 15 minutes
0 0 * * *           every day at midnight (server local time)
0 9 * * 1-5         weekdays at 09:00
30 14 * * 1-5       weekdays at 14:30
0 9 * * 1           every Monday at 09:00
0 22 * * 0          every Sunday at 22:00
0 0 1 * *           midnight on the 1st of every month
0 0 1 1 *           midnight on January 1st
0 3 * * 6           every Saturday at 03:00 (typical backup window)
*/5 9-17 * * 1-5    every 5 minutes during business hours, Mon-Fri

Atajos con nombre

Vixie cron, GNU mcron y la mayoría de sus descendientes aceptan un puñado de alias con prefijo @ que se mapean a planificaciones comunes. Son más fáciles de leer que sus cinco campos equivalentes y más difíciles de escribir mal.

@yearly    same as 0 0 1 1 *      (also @annually)
@monthly   same as 0 0 1 * *
@weekly    same as 0 0 * * 0
@daily     same as 0 0 * * *      (also @midnight)
@hourly    same as 0 * * * *
@reboot    run once at system startup

Quartz: el primo de 6 y 7 campos

Los planificadores Java — el propio Quartz, @Scheduled de Spring con cron =, y un puñado de plataformas en la nube — usan un dialecto distinto. Quartz antepone un campo de segundos y, opcionalmente, añade un campo de año al final, dándote seis o siete campos en total. También soporta L (último), W (día laborable más cercano), # (enésimo día de la semana del mes) y el marcador ? que significa "sin valor concreto, solo uno entre día del mes y día de la semana está fijado".

Si copias una expresión de Quartz dentro de un crontab de Linux, fallará al parsearla silenciosamente, porque el 0 inicial parece un valor de minuto normal para Vixie cron. Sé siempre consciente de qué dialecto espera tu planificador antes de guardar el archivo.

Quartz fields: second minute hour day-of-month month day-of-week [year]

0  0  12  *  *  ?           every day at noon
0  15 10  ?  *  MON-FRI     weekdays at 10:15
0  0  0   L  *  ?           last day of every month at midnight
0  0  0   LW *  ?           last weekday of every month
0  0  9   ?  *  2#1         first Monday of every month at 09:00
0  0  9   ?  *  6#3         third Friday of every month at 09:00
0  0  0   1  *  ?  2026     midnight on the 1st of every month, but only in 2026

Expresiones rate vs cron de AWS

EventBridge, CloudWatch Events y Lambda usan una sintaxis cron casi de Quartz con dos desviaciones importantes. No hay campo de segundos (seis campos, no siete) y debes usar ? en el día del mes o en el día de la semana — nunca ambos como *. AWS también ofrece rate(valor unidad) para intervalos simples, lo que evita por completo la trampa del OR.

AWS cron fields: minute hour day-of-month month day-of-week year

cron(0 12 * * ? *)        every day at 12:00 UTC
cron(0/15 * * * ? *)      every 15 minutes
cron(0 9 ? * MON-FRI *)   weekdays at 09:00 UTC
cron(0 0 1 * ? *)         midnight UTC on the 1st of every month

rate(5 minutes)           every 5 minutes
rate(1 hour)              every hour
rate(7 days)              every 7 days starting from rule creation

Zonas horarias, DST y las cosas que se rompen a las 02:30

Vixie cron respeta la zona horaria del sistema (variable de entorno TZ, o lo que apunte /etc/localtime). Cuando el horario de verano salta una hora hacia delante, cualquier tarea programada dentro del hueco se omite silenciosamente. Cuando el DST vuelve atrás, las tareas programadas dentro de la hora duplicada se ejecutan dos veces en algunas implementaciones y una vez en otras. Los dos valores por defecto seguros: ejecuta cron en UTC, o planifica fuera de la ventana de 01:00 a 04:00.

Los planificadores en la nube se comportan de manera distinta. EventBridge siempre corre en UTC. Los CronJobs de Kubernetes respetan el campo spec.timeZone añadido en la v1.27. Quartz usa el predeterminado de la JVM a menos que pases un CronTrigger con zona horaria. Lee siempre los documentos una vez para el planificador que estés configurando; no asumas que la convención del anterior se traslada.

Depurar una expresión que no dispara

Nueve de cada diez veces la expresión está bien y lo que está mal es el entorno. Recorre esta lista antes de culpar a la planificación.

  • Lee el log del demonio de cron: grep CRON /var/log/syslog en Debian/Ubuntu, journalctl -u cron en hosts con systemd. El log muestra la línea de comando resuelta que cron ejecutó realmente.
  • Confirma el usuario. crontab -l sin opciones muestra TU crontab; la planificación que te importa puede estar en /etc/crontab, /etc/cron.d/*, o el crontab de otro usuario.
  • Comprueba el PATH. Cron se ejecuta con un PATH mínimo (normalmente /usr/bin:/bin). Usa rutas absolutas o establece PATH= en la parte superior del crontab.
  • Comprueba el shell. Cron usa /bin/sh, no tu shell de login. Cualquier cosa específica de bash (sustitución de procesos, [[ )) necesita un envoltorio explícito bash -c.
  • Redirige la salida. Sin > /var/log/myjob.log 2>&1, una tarea que falla manda un correo a root localmente y nunca lo ves.
  • Confirma la zona horaria. date && date -u dentro de la tarea registra ambas. Si discrepan con lo que tu planificación asumía, ese es tu bug.
# A safer cron line:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""

*/5 * * * *  /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1

Cuándo cron es la herramienta equivocada

Cron es excelente para planificaciones fijas en reloj de pared e imbatible para tareas de una línea en un único host. Encaja mal cuando necesitas: coordinación distribuida entre muchas máquinas (usa una cola, un planificador con elección de líder, o CronJobs de Kubernetes con concurrencyPolicy: Forbid); reintentos y back-off en caso de fallo (envuelve el script o usa un motor de flujos de trabajo); granularidad por debajo del minuto (usa un temporizador de systemd o un disparador basado en eventos); o planificaciones que dependen de que la ejecución anterior termine primero (cron lanzará alegremente una segunda copia mientras la primera sigue corriendo). Reconocer el límite pronto ahorra muchas alarmas a las 3 de la madrugada.

Construye y verifica visualmente

Si escribes más de un par de líneas de cron al mes, el hábito con más impacto es comprobar cada expresión contra un parser antes de hacer commit. Multilities ofrece una /tools/cron-builder gratuita que decodifica una expresión de 5 o 6 campos en lenguaje claro y muestra las próximas diez ejecuciones en tu zona horaria local, que es exactamente la verificación cruzada que pilla la trampa del OR día-del-mes/día-de-la-semana y la sorpresa del paso al estilo */7. Combínalo con un test unitario rápido que verifique la próxima ejecución para una fecha ancla conocida y tus tareas planificadas se volverán aburridamente fiables — que es justo lo que quieres que sean.

Prueba estas herramientas