Saltar al contenido
AZ Tools

Cómo funcionan realmente los horarios cron

Una expresión cron son cinco números pequeños que deciden cuándo se ejecuta tu tarea, y un número sorprendente de incidentes en producción empieza por leerlos mal. Esta guía cubre la sintaxis, la única regla que se comporta al revés de como se lee, las trampas de zona horaria y los hábitos que mantienen fiable un horario cuando ya nadie lo mira.

Los cinco campos

El cron estándar toma cinco campos, en orden: minuto (0–59), hora (0–23), día del mes (1–31), mes (1–12) y día de la semana (0–6, donde 0 es domingo). Cada campo acepta un valor único, una lista (`1,15`), un rango (`9-17`), un paso (`*/15`) o `*` para "cada uno". Así que `0 9 * * 1-5` significa las nueve de la mañana, de lunes a viernes.

Los pasos se aplican al rango que tienen delante, y por eso `*/15` en el campo de minutos significa "0, 15, 30, 45" y no "cada quince minutos desde que se creó la tarea". Un horario se dispara cuando la hora actual coincide con el patrón: cron no lleva la cuenta de intervalos, mira el reloj.

Día del mes y día de la semana son O, no Y

Esta es la regla que sorprende a todo el mundo. Cuando los campos de día del mes y día de la semana están *ambos* restringidos, cron ejecuta la tarea cuando coincide *cualquiera* de los dos, no cuando coinciden los dos. Así que `0 0 13 * 5` no es "viernes 13": se ejecuta el día 13 de cada mes y también todos los viernes.

El comportamiento solo aparece cuando ambos campos están restringidos. Si uno es `*`, el otro decide sin más. Para obtener una intersección real necesitas una comprobación dentro de la propia tarea — mira la fecha al principio del script y sal antes de tiempo — porque la expresión no puede expresarlo.

¿Sobre qué reloj se ejecuta?

Una expresión cron no lleva zona horaria. El horario se interpreta en la zona que use el ejecutor: la zona local de la máquina en el crond clásico, UTC en la mayoría de programadores gestionados y sistemas de CI. Los mismos cinco campos significan por tanto momentos reales distintos según dónde se desplieguen, y así es como un informe "de medianoche" acaba llegando a media tarde.

Si el ejecutor usa una zona con horario de verano, dos veces al año el horario falla de una de dos formas. La noche en que el reloj se adelanta, una tarea programada dentro de la hora saltada no se ejecuta en absoluto. La noche en que se atrasa, la hora repetida puede ejecutarla dos veces. Programar en UTC evita ambas cosas, a cambio de que la hora local se desplace una hora a lo largo del año: elige deliberadamente con qué fallo puedes vivir.

Extensiones, apodos y el sexto campo

Muchos programadores extienden la sintaxis clásica, y no de forma compatible. Las expresiones al estilo Quartz añaden un campo inicial de segundos, así que una expresión de seis campos significa algo completamente distinto de una de cinco y un horario copiado puede dispararse sesenta veces de más. Algunas variantes añaden `L` (último), `W` (día laborable más cercano) y `#` (enésimo día de la semana del mes); otras aceptan `?` en lugar de `*` en los campos de día.

La mayoría de implementaciones aceptan además apodos: `@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly` y a veces `@reboot`. Son más claros que sus equivalentes numéricos cuando encajan, pero ocultan el minuto exacto: `@daily` es medianoche, que es también cuando arrancan todas las demás tareas `@daily` de la máquina.

Diseñar horarios que sobrevivan

Da por hecho que alguna ejecución se perderá, se repetirá o se solapará, y diseña la tarea para que nada de eso corrompa datos. Hazla idempotente para que una ejecución doble sea inofensiva, toma un bloqueo para que una ejecución lenta no se solape con la siguiente y registra qué se procesó para que una ejecución perdida pueda ponerse al día en vez de dejar un hueco silencioso.

Reparte los horarios. Todo lo que esté en `0 0 * * *` arranca a la vez, y en un servidor compartido esa avalancha te la has provocado tú. Elige un minuto poco redondo, escalona las tareas relacionadas y añade un pequeño retardo aleatorio al inicio de todo lo que golpee una API compartida. Por último, alerta cuando la tarea *no* se haya ejecutado: un horario que deja de dispararse en silencio se ve exactamente igual que uno que no tenía nada que hacer.

  • `*/15 * * * *` — cada cuarto de hora, en punto de cuarto.
  • `0 9 * * 1-5` — 09:00 en días laborables, en la zona del ejecutor.
  • `0 0 13 * 5` — el día 13 *o* cualquier viernes, no el viernes 13.
  • `0 3 1 * *` — 03:00 del primer día del mes.

Herramientas relacionadas