Guide
Tutorial de expresiones cron: cómo leer y escribir programas crontab
Cinco campos, seis caracteres especiales y un error histórico que decide si tu tarea se ejecuta una o dos veces.
By Buğra SözeriPublished
Cron tiene cuarenta años y sigue siendo el programador predeterminado en casi todos los sistemas Unix enviados este año. La sintaxis cabe en una línea, que es la principal razón por la que sobrevivió — y también por qué se malinterpreta de manera confiable. Esta guía recorre los cinco campos, cada carácter especial, el error de día del mes vs. día de la semana y una pequeña biblioteca de expresiones que puedes copiar directamente en un crontab.
La anatomía de los cinco campos
Una expresión cron POSIX tiene cinco campos separados por espacios en blanco, en este orden:
# ┌──────────── minuto (0 - 59)
# │ ┌────────── hora (0 - 23)
# │ │ ┌──────── día del mes (1 - 31)
# │ │ │ ┌────── mes (1 - 12) o JAN-DEC
# │ │ │ │ ┌──── día de semana (0 - 6) o SUN-SAT, donde 0 y 7 son domingo
# │ │ │ │ │
* * * * * comando-a-ejecutarCada campo debe estar presente. No hay valores predeterminados ni atajos posicionales. Un campo con *significa “cada valor válido en este rango,” no “omitir.” Esa distinción importa en cuanto empiezas a combinar campos.
Algunas variantes añaden un sexto campo de segundos al inicio (Quartz, @Scheduled de Spring) o un séptimo campo de año al final. Esas son extensiones, no POSIX. Si escribes para portabilidad, quédate con cinco.
¿Por qué dos campos de día?
Cron permite decir “cada lunes” y “el primero del mes” en la misma línea, lo que sería ambiguo si ambos campos tuvieran que coincidir. La resolución histórica es la semántica OR cuando ambos campos están restringidos: la tarea se dispara cuando el día del mes o el día de la semana coincide. Volveremos a esto; es el error de cron más común.
Caracteres especiales
Cada campo acepta seis caracteres especiales. Su comportamiento es idéntico en todos los campos, pero el rango válido evidentemente difiere por campo.
* — cada valor
Coincide con cada valor válido en el campo. * * * * * significa cada minuto de cada hora de cada día. Esta es la única expresión que genuinamente se dispara sesenta veces por hora; todo lo más complejo suele dispararse menos.
, — lista
La coma separa valores discretos. 0,15,30,45 * * * * se dispara en el inicio, cuarto de hora, media hora y tres cuartos de cada hora. El orden no importa para el analizador, pero los lectores agradecen las listas ascendentes.
- — rango
El guión define un rango inclusivo. 0 9-17 * * 1-5 se dispara al inicio de cada hora de 09:00 a 17:00 inclusivo, de lunes a viernes. Ambos extremos están incluidos; eso son nueve ejecuciones por día, no ocho.
/ — paso
La barra aplica un paso a un rango. */5 en el campo de minutos es abreviatura de 0-59/5— cada cinco minutos a partir del minuto 0. Puedes combinar el paso con un rango explícito: 0-30/10 se dispara a 0, 10, 20, 30 y en ningún otro lugar.
La trampa: */N divide el rango completo, no desde tu hora actual del reloj. */7en minutos se dispara a 0, 7, 14, 21, 28, 35, 42, 49, 56 — luego 0 de la siguiente hora, una brecha de cuatro minutos. Cron no es un temporizador periódico.
? — sin valor específico (solo Quartz)
El carácter ? noes POSIX. Aparece en cron con sabor Quartz (y AWS EventBridge, que hereda la sintaxis de Quartz) y significa “sin valor específico — el otro campo de día es autoritativo.” Escribirás 0 9 ? * MON-FRI en EventBridge pero 0 9 * * MON-FRI en cron clásico.
Valores con nombre
Los meses aceptan JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC, los días de la semana aceptan SUN MON TUE WED THU FRI SAT. Son insensibles a mayúsculas en la mayoría de las implementaciones pero sensibles en algunas; las mayúsculas son la opción segura. Los valores con nombre no funcionan dentro de rangos o pasos en todas las implementaciones —MON-FRI funciona en Vixie cron pero no en todas las variantes BSD. En caso de duda, usa la forma numérica.
Recetas
La forma más rápida de internalizar cron es leer algunas docenas de expresiones junto con el inglés claro. Pega cualquiera de estas en nuestro constructor de expresiones cron para ver los próximos diez tiempos de disparo en tu zona local.
Cada N minutos
* * * * *— cada minuto*/5 * * * *— cada cinco minutos, en el minuto 0, 5, 10…*/15 * * * *— cada cuarto de hora0,30 * * * *— en la hora y en la media hora5,35 * * * *— desplazado cinco minutos — útil para escalonar tareas que compiten por la misma base de datos
Variantes por hora
0 * * * *— cada hora en punto0 */2 * * *— cada dos horas a partir de las 00:000 9-17 * * *— en punto, de 09:00 a 17:00 inclusivo
Variantes diarias
0 0 * * *— medianoche cada día (equivalente a@daily)30 3 * * *— 03:30 cada día — la clásica ventana de copia de seguridad0 9 * * 1-5— 09:00 los días de semana0 18 * * 5— 18:00 solo los viernes
Semanal, mensual, anual
0 9 * * 1— 09:00 cada lunes (equivalente al espíritu de@weeklypero con una hora sensible)0 0 1 * *— medianoche el primero de cada mes (equivalente a@monthly)0 0 1 1 *— medianoche el 1 de enero (equivalente a@yearly)0 0 28-31 * *— medianoche en los últimos cuatro días posibles del mes, combinado con un script que comprueba si es el último día
Horario comercial
0 9-17 * * 1-5— en punto, 09:00-17:00, lunes-viernes — nueve disparos por día laborable*/10 9-17 * * 1-5— cada diez minutos durante el horario comercial de EE. UU.0 9,13,17 * * 1-5— apertura, post-almuerzo, cierre — tres disparos por día laborable
El error OR de día del mes / día de la semana
Este es el comportamiento más preguntado en cron, y la especificación es inequívoca: si ambos el campo de día del mes y el campo de día de la semana están restringidos (es decir, no son *), la tarea se dispara cuandocualquiera de las condiciones coincide, no ambas.
Considera 0 0 1 * 1. Una lectura natural es “medianoche el primero del mes, pero solo si es lunes.” El comportamiento real es “medianoche el primero de cada mes, omedianoche cada lunes.” Eso son aproximadamente cinco disparos por mes, no los cero a uno que probablemente pretendías.
La solución: deja uno de los dos campos de día como *. Si genuinamente necesitas semántica AND — “primer lunes del mes” — tendrás que comprobarlo dentro del script:
# crontab: disparar medianoche los primeros siete días del mes
0 0 1-7 * * /usr/local/bin/maybe-run-monthly.sh
# dentro de maybe-run-monthly.sh: salir si hoy no es lunes
[ "$(date +%u)" = "1" ] || exit 0El cron con sabor Quartz (y EventBridge) esquiva el error con el carácter ? y tokens explícitos de enésimo día de la semana del mes como MON#1. El cron clásico no tiene ninguno de los dos.
Zonas horarias y horario de verano
Las expresiones cron se evalúan en la zona horaria local del programador. En Linux es lo que apunta /etc/localtime; en macOS es la preferencia del sistema. La expresión en sí nunca nombra una zona.
Eso hace que las transiciones de horario de verano sean interesantes. En una zona que observa el horario de verano, el reloj local salta hacia adelante (una hora omitida) en primavera y retrocede (una hora repetida) en otoño. Una tarea cron a 0 2 * * *puede no ejecutarse en absoluto el día del cambio de primavera — la hora de las 02:00 no existió — y puede ejecutarse dos veces en otoño.
La mayoría de los programadores en la nube aceptan una zona horaria explícita por regla; identificadores de zona IANA como America/New_York son correctos, EST no lo es (no observa el horario de verano). Si necesitas consistencia absoluta, programa entre las 03:00 y las 23:00 (lejos de cualquier ventana de horario de verano) o ejecuta el programador en UTC y vive con la aritmética de hora local.
Errores comunes
Tratar la barra como un período verdadero
Ya cubrimos esto. */N divide el rango del campo, por lo que el período entre el último disparo de una hora y el primer disparo de la siguiente es más corto que N. Si necesitas un espaciado estricto de N minutos, cron es la herramienta incorrecta.
Olvidar que el minuto 0 existe
0-59/15se dispara a 0, 15, 30, 45 — cuatro veces por hora, no tres. El rango comienza en 0. Los errores de uno en uno son raros en cron porque cada campo tiene extremos bien definidos, pero este aparece cuando las personas traducen del inglés humano (“cuatro veces por hora”) a expresión y olvidan el minuto cero implícito.
Poner un comentario después del comando
Cron trata toda la línea después del quinto campo como un único comando. # comentario en una línea de crontab es parte del comando de shell, que generalmente no produce nada porque #inicia un comentario en el shell — pero si está dentro de una cadena entre comillas, has cambiado tu comando. Pon los comentarios en su propia línea encima del horario.
Asumir el PATH de la tarea
Cron se ejecuta con un entorno mínimo. PATH es típicamente /usr/bin:/bin y tus alias de shell no existen. Usa rutas absolutas a cada binario o establece PATH=en la parte superior del crontab. Si una tarea “funciona cuando la ejecuto a mano pero no desde cron,” esta es casi siempre la causa.
Dejar que stdout llene la bandeja de entrada
Cron envía por email la salida de cada tarea al usuario local de forma predeterminada. En un servidor sin correo configurado, el archivo de cola crece silenciosamente hasta que algo más se rompe. Añade >>/var/log/mitarea.log 2>&1 a cada comando, y rota el registro.
Tarjeta de referencia rápida
| Expresión | Significado |
|---|---|
* * * * * | cada minuto |
*/5 * * * * | cada 5 minutos |
0 * * * * | inicio de cada hora |
0 9-17 * * 1-5 | por hora, 9-5, días laborables |
0 0 * * * | medianoche diariamente |
30 3 * * 0 | 03:30 cada domingo |
0 0 1 * * | medianoche el día 1 de cada mes |
0 0 1 1 * | medianoche el 1 de enero |
@reboot | una vez al inicio del programador (depende del sistema) |
Verifica antes de confirmar
La forma más barata de evitar una alerta a las 3am es pegar tu expresión en un analizador antes de guardar el crontab. Nuestro constructor de expresiones cron imprime los próximos diez tiempos de disparo en tu zona local y en UTC, más una descripción en lenguaje sencillo que detecta el error de día del mes / día de la semana al momento.
Cron es una herramienta pequeña y afilada. Lee la especificación una vez, construye memoria muscular para los cinco campos, pega nuevas expresiones en un analizador hasta que parezcan obviamente correctas — y presupuesta el hecho de que el modo de fallo de una expresión malinterpretada suele ser silencioso, no ruidoso.
Frequently asked questions
- ¿Usa cron la zona horaria del servidor o UTC?
- El cron clásico de Unix usa la zona horaria local del sistema, que es lo que apunta /etc/localtime. Muchos programadores en la nube (AWS EventBridge, GCP Cloud Scheduler) usan UTC de forma predeterminada y te permiten optar por una zona IANA con nombre por regla. Siempre imprime la zona horaria resuelta en la primera línea de registro de tu tarea — elimina toda una categoría de errores posteriores al cambio de horario de verano.
- ¿Por qué se ejecutó mi tarea dos veces la mañana en que terminó el horario de verano?
- Cuando los relojes retroceden de las 02:00 a la 01:00, la hora entre las 01:00 y las 02:00 ocurre dos veces en el reloj local. Una tarea programada a las 01:30 en una zona que observa el horario de verano se ejecuta ambas veces a menos que tu implementación de cron lo deduplique explícitamente. La solución es programar entre las 03:00 y las 23:00 (nunca dentro de la ventana ambigua) o ejecutar el programador en UTC.
- ¿Cuál es el intervalo más pequeño que admite cron?
- Un minuto. El primer campo son los minutos; no hay campo de segundos en crontab POSIX. Si necesitas programación sub-minuto, usa un proceso de larga ejecución con su propio bucle de espera, temporizadores de systemd con OnUnitActiveSec, o un programador dedicado como Quartz que admite un campo de segundos.
- ¿Son portátiles @hourly, @daily, @reboot?
- Las cadenas abreviadas (@yearly, @monthly, @weekly, @daily, @hourly) son compatibles con Vixie cron, cronie y la mayoría de los derivados modernos, por lo que son seguras en Linux y macOS. @reboot es ampliamente compatible pero su semántica difiere — algunas implementaciones se ejecutan una vez en el próximo arranque, otras en cada arranque. No uses @reboot en contenedores; usa el propio comando de inicio del contenedor.
- ¿Por qué */7 a veces omite el último intervalo?
- El operador de paso divide todo el rango permitido, no tu hora de inicio. Para los minutos (0-59), */7 se dispara a 0, 7, 14, 21, 28, 35, 42, 49, 56 — luego el siguiente minuto válido es 0 de la siguiente hora. La brecha entre 56 y 60 es solo cuatro minutos, no siete. Si necesitas un período estricto de siete minutos, usa un programador de larga ejecución con un temporizador de intervalo real, no cron.
- ¿Debo usar cron en 2026 o temporizadores de systemd?
- En un único host Linux sin orquestador, los temporizadores de systemd suelen ser una mejor opción predeterminada: registro estructurado a través de journalctl, ordenación de dependencias, randomización más fácil con RandomizedDelaySec y límites de recursos a nivel de unidad. Cron gana cuando necesitas portabilidad entre sistemas no systemd o cuando toda la automatización es una línea en un crontab. En entornos orquestados por contenedores, ninguno — usa el programador nativo de la plataforma (Kubernetes CronJob, ECS Scheduled Tasks, etc.).
Related
- Constructor y explicador de expresiones cronPega una expresión, ve las próximas ejecuciones y una descripción en lenguaje sencillo
- Glosario de zonas horarias IANAPor qué las zonas se escriben America/New_York, no EST
- Glosario de marcas de tiempo UnixSegundos desde 1970-01-01 UTC — la base de casi todo programador
- Convertidor de marcas de tiempoConvierte entre segundos epoch, ISO 8601 y tu zona horaria local
- Hoja de referencia de zonas horarias para equipos remotosMatemáticas de zona práctica para programación distribuida
Published May 31, 2026