Guides 8 min de lectura

Expresiones cron explicadas: sintaxis, ejemplos y errores habituales

Una expresión cron son cinco campos que indican cuándo se ejecuta un job. La sintaxis, los caracteres especiales y atajos como @daily, ejemplos listos para usar y los errores de zona horaria y PATH que rompen los jobs.

Una expresión cron son cinco campos separados por espacios: minuto, hora, día del mes, mes y día de la semana, seguidos del comando que se ejecuta. 30 2 * * * significa «a las 02:30 todos los días». Cada campo admite un número, un * para «cualquier valor», una lista separada por comas, un rango con guion o un intervalo con barra. La mayoría de los problemas con cron no vienen de la sintaxis en sí, sino de lo que la rodea: la zona horaria del servidor, la regla que combina el día del mes con el día de la semana y el entorno mínimo en el que cron ejecuta los comandos.

Esta guía explica cada campo y cada carácter especial, incluye una tabla de expresiones listas para usar y repasa los errores que hacen que un job se ejecute a la hora equivocada o que no llegue a ejecutarse.

Los cinco campos

Cada línea del crontab de un usuario sigue la misma estructura:

┌───────────── minuto (0–59)
│ ┌─────────── hora (0–23)
│ │ ┌───────── día del mes (1–31)
│ │ │ ┌─────── mes (1–12 o JAN–DEC)
│ │ │ │ ┌───── día de la semana (0–7 o SUN–SAT; 0 y 7 son domingo)
│ │ │ │ │
* * * * * /path/to/command

Un job se ejecuta cuando el minuto actual coincide con todos los campos, con una excepción para los dos campos de día que veremos más abajo. Los meses y los días de la semana pueden escribirse con su abreviatura inglesa de tres letras, sin distinguir mayúsculas y minúsculas.

Tu propio crontab se edita con crontab -e y se lista con crontab -l. Los archivos de sistema, /etc/crontab y los de /etc/cron.d/, tienen un sexto campo entre la programación y el comando: el usuario con el que se ejecuta el job. Copiar una línea de un formato al otro sin añadir o quitar ese campo es una forma clásica de romper un job.

Caracteres especiales

CarácterSignificadoEjemploResultado*Cualquier valor* * * * *Cada minuto,Lista de valores0 8,12,18 * * *A las 08:00, 12:00 y 18:00-Rango0 9 * * 1-5A las 09:00 de lunes a viernes/Intervalo*/15 * * * *En los minutos 0, 15, 30 y 45

Cómo funcionan realmente los intervalos

Un intervalo divide el rango de un campo empezando por su primer valor. */15 en el campo de minutos significa 0, 15, 30 y 45. También se puede aplicar a un rango: 0-30/10 significa los minutos 0, 10, 20 y 30.

Los intervalos vuelven a empezar al final del campo. */7 en el campo de minutos se ejecuta en 0, 7, 14 y así hasta 56, y luego otra vez en 0, de modo que el último hueco de cada hora es de solo cuatro minutos. */7 no es «cada siete minutos». Cron clásico tampoco puede expresar «cada 90 minutos» en una sola línea: necesitas dos líneas o una comprobación dentro del script.

Sintaxis que no es de cron clásico

Puede que veas L (último), W (día laborable más cercano), # (enésimo día de la semana del mes) y ? en alguna documentación. Vienen de Quartz y de algunos programadores de tareas en la nube y de CI, no del cron de un servidor Linux, y algunos de ellos añaden además un campo de segundos al principio. Muchos cron tampoco aceptan rangos de nombres, así que escribe 1-5 en lugar de MON-FRI.

Atajos como @daily

La mayoría de las implementaciones de cron en Linux aceptan un atajo con nombre en lugar de los cinco campos:

@reboot: una vez, cuando el demonio de cron arranca tras un reinicio.

@yearly o @annually: 0 0 1 1 *, medianoche del 1 de enero.

@monthly: 0 0 1 * *, medianoche del día 1 de cada mes.

@weekly: 0 0 * * 0, la medianoche entre el sábado y el domingo.

@daily o @midnight: 0 0 * * *.

@hourly: 0 * * * *.

Todos los atajos se disparan en punto o a medianoche, así que en un servidor con mucha carga todos los jobs compiten por los mismos recursos a las 00:00. Escribir la expresión completa con un minuto poco habitual, como 17 0 * * *, reparte la carga.

Expresiones cron listas para usar

ExpresiónSe ejecuta* * * * *Cada minuto*/5 * * * *Cada 5 minutos*/15 * * * *Cada 15 minutos0 * * * *Cada hora, en punto15 */2 * * *En el minuto 15 de cada dos horas: 00:15, 02:15, 04:15, etc.0 9-17 * * 1-5Cada hora de 09:00 a 17:00, de lunes a viernes*/10 8-18 * * 1-5Cada 10 minutos en horario de oficina, de lunes a viernes30 2 * * *Todos los días a las 02:300 8,12,18 * * *Tres veces al día: 08:00, 12:00 y 18:000 6 * * 1Todos los lunes a las 06:000 22 * * 5Todos los viernes a las 22:000 0 * * 0Todos los domingos a medianoche0 3 1 * *El día 1 de cada mes a las 03:000 4 1,15 * *Los días 1 y 15 de cada mes a las 04:000 5 1 */3 *Trimestral: el 1 de enero, abril, julio y octubre a las 05:000 0 1 1 *Una vez al año, a medianoche del 1 de enero@rebootUna vez después de cada arranque del sistema

Un error habitual con esta lista es * */2 * * *. No se ejecuta cada dos horas: se ejecuta cada minuto durante una de cada dos horas, sesenta veces seguidas. Cuando restrinjas la hora, fija siempre también el minuto.

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

Todos los campos se combinan con AND, salvo los dos campos de día. Cuando tanto el día del mes como el día de la semana están restringidos, es decir, ninguno es *, el job se ejecuta cuando coincide cualquiera de los dos. Así que esta línea:

0 9 1 * 1 /usr/local/bin/report.sh

se ejecuta a las 09:00 el día 1 de cada mes y a las 09:00 todos los lunes, no solo los lunes que caen en día 1. Por el mismo motivo, 0 9 1-7 * 1 no significa «el primer lunes del mes»: se ejecuta los días 1 a 7 y todos los lunes. Para el primer lunes, programa los días 1 a 7 y comprueba el día de la semana en el comando. Fíjate en el signo de porcentaje escapado, que explicamos más abajo:

0 9 1-7 * * [ «$(date +\%u)» = «1» ] && /usr/local/bin/report.sh

Cron clásico tampoco tiene «último día del mes». Con date de GNU, programa los días 28 a 31 y ejecuta solo cuando mañana sea día 1:

0 23 28-31 * * [ «$(date -d tomorrow +\%d)» = «01» ] && /usr/local/bin/month-end.sh

Zonas horarias y horario de verano

Cron usa la zona horaria del sistema en el que se ejecuta. Un job pensado para la hora de Estocolmo en un servidor configurado en UTC arranca una o dos horas desfasado, según la época del año. Comprueba la zona del servidor con timedatectl o date antes de elegir la programación. Usa la misma zona cuando monitorices el job: los monitores de cron de Uptimecheck toman la programación del crontab junto con una zona horaria, de modo que las ejecuciones esperadas coinciden con las del servidor.

Algunas implementaciones, como cronie, aceptan una variable CRON_TZ al principio del crontab para ejecutar las programaciones en otra zona. El soporte varía según la versión de cron, así que pruébalo en lugar de dar por hecho que funciona.

El horario de verano provoca los fallos más extraños. En primavera, una hora local no existe; en otoño, una hora ocurre dos veces. Un job programado aproximadamente entre la 01:00 y las 03:00 hora local puede saltarse, retrasarse o ejecutarse dos veces, y cada implementación de cron gestiona el cambio a su manera. Las opciones más seguras:

Ejecuta los servidores en UTC, que no tiene horario de verano, y haz la conversión de cabeza o en un comentario.

Si el servidor tiene que usar la hora local, deja los jobs importantes fuera de las horas en las que cambia el reloj.

Escribe la zona horaria en un comentario encima de cada job, para que la siguiente persona no tenga que adivinarla.

Haz que los jobs puedan ejecutarse dos veces sin problema, por ejemplo comprobando si la exportación de hoy ya existe.

Trampas del entorno y del PATH

Un comando que funciona en tu terminal y falla en cron casi siempre falla por el entorno. Cron no carga el perfil de tu shell, así que falta mucho de lo que das por supuesto:

El PATH es mínimo, a menudo solo /usr/bin:/bin. Los comandos de /usr/local/bin, o las herramientas instaladas con nvm, pyenv, rbenv o Composer, no se encuentran. Usa rutas absolutas o define PATH al principio del crontab.

La shell es /bin/sh, no bash. La sintaxis exclusiva de bash, como [[ ]] o source, falla. Define SHELL=/bin/bash o pon la lógica en un script con un shebang de bash.

Los signos de porcentaje son especiales. En una línea del crontab, % se convierte en un salto de línea. date +%F debe escribirse date +\%F, o trasladarse a un script.

El directorio de trabajo es el home del usuario, no tu proyecto. Las rutas relativas apuntan al sitio equivocado. Haz primero cd al proyecto.

Las variables de entorno desaparecen. Las claves de API y las URL de base de datos exportadas en .bashrc no existen. Cárgalas desde un archivo en el script.

La salida se pierde. Cron envía la salida por correo a MAILTO si el correo está configurado, y en la mayoría de los servidores no lo está. Redirige la salida a un archivo de log.

Las ejecuciones pueden solaparse. Si un job de cada cinco minutos a veces tarda siete, se ejecutan dos copias a la vez. flock -n se salta una ejecución mientras la anterior mantiene el bloqueo.

Algunas versiones de cron también ignoran la última línea si no termina con un salto de línea. Un crontab que tiene en cuenta todo esto queda así:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=»»

# Copia de seguridad nocturna de la base de datos. La hora del servidor es UTC.
30 2 * * * cd /var/www/fjordstudio && flock -n /tmp/backup.lock ./bin/backup.sh >> /var/log/backup.log 2>&1

Cómo probar una expresión cron

Léela en palabras. Pega la expresión en un intérprete de expresiones cron y comprueba que la descripción y las próximas ejecuciones coinciden con lo que querías.

Ejecuta el comando en el entorno de cron. Un entorno vacío reproduce la mayoría de los fallos del tipo «en mi terminal funciona» antes de esperar a la programación.

Prográmalo una vez cada minuto. Usa * * * * * temporalmente, comprueba que se ejecuta y luego pon la programación real.

Comprueba que cron lo ha lanzado. En Debian y Ubuntu, grep CRON /var/log/syslog o journalctl -u cron; en sistemas basados en Red Hat, /var/log/cron o journalctl -u crond.

Confirma que el crontab está instalado con crontab -l, como el usuario que debe ejecutarlo.

Para simular el entorno de cron desde una shell:

env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=»$HOME» /bin/sh -c ‘/usr/local/bin/backup.sh’

Acompaña cada job con un heartbeat

Una expresión correcta solo le dice a cron cuándo lanzar un job. No dice nada sobre si el job se ejecutó o terminó. Un reinicio, un crontab sobrescrito o una variable que falta fallan en silencio.

Un heartbeat cierra ese hueco. El job llama a una URL única cuando termina correctamente, y el servicio de monitorización te avisa cuando la llamada no llega a su hora:

30 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 –retry 3 https://heartbeat.example.com/your-unique-id > /dev/null

Configura el intervalo esperado del heartbeat con la misma programación que la expresión cron, con margen para las ejecuciones lentas: 24 horas y un poco más para un job nocturno, una hora y un poco más para uno horario. La monitorización de cron jobs de Uptimecheck funciona así, con alertas cuando falta un ping y un aviso de recuperación cuando el job vuelve a reportar. Sus monitores de cron toman la programación del crontab en tu zona horaria, aceptan pings de inicio y de fin en un mismo monitor, avisan cuando una ejecución supera su duración máxima y pueden registrar el código de salida y la salida del job. La guía sobre cómo monitorizar cron jobs con heartbeats explica la configuración en detalle, y la de cómo evitar tareas cron perdidas cubre los hábitos que la rodean.

Cuando falta un heartbeat, la causa suele estar en el servidor: un disco lleno, la memoria agotada o un reinicio imprevisto. La monitorización de servidores junto a tus heartbeats muestra esa causa en el mismo momento.

Lista de errores habituales

Restringir la hora y dejar el minuto como *.

Fijar los dos campos de día y esperar un AND en lugar de un OR.

Dar por hecho que el servidor está en tu zona horaria.

Programar jobs importantes en la hora del cambio de horario.

Usar rutas relativas, sintaxis de bash o % sin escapar.

Ejecutar un job sin heartbeat, de modo que nadie se entera cuando deja de funcionar.

Resumen

Cinco campos, cuatro caracteres especiales y unos pocos atajos cubren casi cualquier programación que vayas a necesitar. La expresión rara vez es el problema; lo son las zonas horarias, la regla del OR para los campos de día y el entorno mínimo de cron. Escribe la zona horaria junto a cada job, usa rutas absolutas, guarda la salida en un log y dale un heartbeat a cada job importante.

Los heartbeats de Uptimecheck están incluidos en todos los planes, también en el plan Free, junto con la monitorización de uptime, SSL, dominios y DNS. Empieza una prueba gratuita de 14 días, sin tarjeta de crédito.

Preguntas

¿Qué significa */5 * * * * en cron?

Cada cinco minutos: en los minutos 0, 5, 10 y así hasta el 55 de cada hora, todos los días.

¿Cómo ejecuto un cron job cada dos horas?

Usa 0 */2 * * *, que se ejecuta en el minuto 0 de cada dos horas. Si escribes * */2 * * *, en cambio, se ejecuta cada minuto durante una de cada dos horas.

¿Qué zona horaria usa cron?

La zona horaria del sistema en el que se ejecuta. Muchos servidores en la nube y contenedores usan UTC, así que compruébalo con timedatectl o date antes de elegir la programación. Algunas implementaciones admiten la variable CRON_TZ.

¿Por qué mi cron job funciona en la terminal pero no en cron?

Cron ejecuta los comandos con un PATH mínimo, /bin/sh como shell, el directorio home como directorio de trabajo y sin el perfil de tu shell. Usa rutas absolutas, escapa los signos % y guarda la salida en un log para ver el error.

Sigue leyendo

Guides 6 min de lectura

Cómo monitorizar cron jobs con heartbeats

Los cron jobs fallan en silencio. Un monitor de heartbeat te avisa cuando una tarea programada no da señales a tiempo. Cómo funcionan los heartbeats, cómo añadir uno al crontab y qué ajustes elegir.

Leer

Las caídas son cosa del pasado.

Configura tu primer monitor en menos de un minuto, o echa antes un vistazo a la demo en vivo.

14 días de prueba gratis · Después, gratis con 5 monitores · Sin tarjeta de crédito