Programar tareas con cron: crontab, el entorno de cron, registros y flock

Cómo programar tareas con cron para que tus scripts de Bash se ejecuten solos: cómo se lee una línea del crontab, cómo instalarlo y revisarlo, por qué un script que funciona en la terminal falla en cron, cómo guardar un registro y cómo evitar que dos ejecuciones se pisen con flock.

Qué es cron y cómo se lee una línea

cron es el servicio de Linux que ejecuta órdenes a horas fijas: cada noche, cada lunes, cada cuarto de hora. Cada usuario tiene su propia lista de tareas, el crontab, y cada línea de esa lista tiene cinco campos de tiempo seguidos de la orden:

  • CampoValoresEjemplo
  • Minuto0-5930: en el minuto 30
  • Hora0-232: a las 2 de la madrugada
  • Día del mes1-311: el día 1
  • Mes1-12*: todos los meses
  • Día de la semana0-7 (0 y 7 son domingo)1-5: de lunes a viernes

Un * significa «todos», */15 «cada 15», 1-5 un rango y 1,15 una lista. Así se leen las líneas más típicas:

  • LíneaCuándo se ejecuta
  • 30 2 * * *Todos los días a las 2:30
  • */15 * * * *Cada 15 minutos
  • 0 9 * * 1-5De lunes a viernes a las 9:00
  • 0 0 1 * *El día 1 de cada mes a medianoche
  • @rebootUna vez, al arrancar el equipo

Si tienes dudas con una expresión, pégala en nuestro traductor de cron a español, que te dice qué significa y cuándo serán las próximas ejecuciones.

Ver, instalar y quitar tu crontab

La orden es crontab: -l lo muestra, -e lo abre en el editor y -r lo borra entero (sin preguntar, así que cuidado con confundirla con la -e, que está al lado en el teclado). También se puede instalar desde un archivo, que es lo más cómodo para guardarlo en un repositorio:

terminal$ crontab -l
no crontab for ana
$ crontab tareas.cron
$ crontab -l
# m  h  dom mes dow  orden
30   2  *   *   *    /home/ana/bin/respaldo.sh /home/ana/documentos /mnt/copias 14 >> /home/ana/respaldo.log 2>&1
*/15 *  *   *   *    /home/ana/bin/comprobar-web.sh
0    9  *   *   1-5  /home/ana/bin/informe-diario.sh
$ crontab -r
$ crontab -l
no crontab for ana
En Ubuntu y Debian el servicio se llama cron y viene instalado; compruébalo con systemctl status cron. En WSL no arranca solo: hay que activarlo con sudo service cron start o configurar systemd. En macOS cron sigue funcionando, aunque Apple prefiere launchd.

El error más común: el entorno de cron

Un script que funciona perfectamente desde la terminal falla en cron sin decir nada. Casi siempre es por lo mismo: cron no carga tu .bashrc ni tu perfil. Ejecuta las órdenes con /bin/sh, en tu carpeta personal y con un PATH mínimo (/usr/bin:/bin). Se puede imitar con env -i, que borra el entorno, para probar el script tal como lo verá cron antes de programarlo:

#!/usr/bin/env bash
# Una orden propia en ~/proyecto/bin y la carpeta añadida al PATH, como en un .bashrc
export PATH="$PWD/bin:$PATH"
chmod +x bin/saludar

echo "--- Desde la terminal"
saludar
echo "PATH tiene $(tr ':' '\n' <<< "$PATH" | wc -l) carpetas"

echo "--- Como lo ejecutaría cron"
env -i HOME="$HOME" LOGNAME="$LOGNAME" SHELL=/bin/sh PATH=/usr/bin:/bin /bin/sh -c 'env | sort; saludar'
echo "código de salida: $?"
salida--- Desde la terminal
Hola desde ~/bin
PATH tiene 4 carpetas
--- Como lo ejecutaría cron
HOME=/home/ana
LOGNAME=ana
PATH=/usr/bin:/bin
PWD=/home/ana/proyecto
SHELL=/bin/sh
/bin/sh: 1: saludar: not found
código de salida: 127

Las variables que ve cron son solo esas cuatro, y saludar ya no se encuentra. Tres formas de evitarlo, de mejor a peor: usar rutas absolutas en el script (/home/ana/bin/saludar), definir PATH al principio del script o del crontab (una línea PATH=/usr/local/bin:/usr/bin:/bin antes de las tareas), o cargar el perfil a mano, que arrastra cosas que no hacen falta.

  • Problema en cronPor quéSolución
  • command not foundEl PATH es solo /usr/bin:/binRutas absolutas o PATH= en el crontab
  • No encuentra sus archivosSe ejecuta en tu carpeta personal, no en la del scriptcd "$(dirname "$0")" al principio, o rutas absolutas
  • Falla un [[ ]] o un arrayLa orden se interpreta con /bin/sh (dash)Shebang #!/usr/bin/env bash y permiso de ejecución
  • La orden se corta en un %En el crontab, % significa salto de líneaEscríbelo como \%: date +\%F
  • No pasa nada y no hay rastroLa salida se envía por correo local, si lo hayRedirige: >> ~/tarea.log 2>&1

Guardar un registro

Si no rediriges la salida, cron intenta mandarla por correo al usuario, y en la mayoría de los servidores ese correo no llega a ninguna parte. Redirige siempre la salida y los errores a un archivo, y pon la fecha en cada línea para saber cuándo pasó cada cosa. Este envoltorio lo hace dentro del propio script con exec, que redirige todo lo que venga después:

#!/usr/bin/env bash
set -euo pipefail
log="$HOME/tarea.log"
exec >> "$log" 2>&1          # a partir de aquí, todo va al registro

anotar() { printf '%s %s\n' "$(date -u -d "${AHORA:-now}" '+%F %T')" "$*"; }

anotar "inicio"
anotar "procesados 42 archivos"
ls /carpeta-que-no-existe || anotar "aviso: no se pudo listar la carpeta"
anotar "fin"

Al ejecutarlo no se ve nada en la pantalla: todo ha ido al registro. (La variable AHORA solo sirve para fijar la hora en esta prueba; sin ella, cada línea lleva la hora real.)

terminal$ AHORA='2026-10-30 02:30:00' bash registro.sh
$ cat ~/tarea.log
2026-10-30 02:30:00 inicio
2026-10-30 02:30:00 procesados 42 archivos
ls: cannot access '/carpeta-que-no-existe': No such file or directory
2026-10-30 02:30:00 aviso: no se pudo listar la carpeta
2026-10-30 02:30:00 fin

Que no se solapen dos ejecuciones: flock

Si una tarea programada cada 15 minutos tarda 20 un día, cron lanzará la siguiente con la primera aún en marcha, y las dos pisarán los mismos archivos. flock lo evita: toma un cerrojo sobre un archivo y, con -n, si ya lo tiene otro proceso, se rinde al momento en lugar de esperar:

#!/usr/bin/env bash
tarea() {
    exec 9> "$HOME/tarea.lock"
    if ! flock -n 9; then
        echo "[$1] ya hay otra ejecución en marcha; salgo sin hacer nada"
        return 1
    fi
    echo "[$1] trabajando..."
    sleep 1
    echo "[$1] terminada"
}

tarea primera &
sleep 0.3
tarea segunda
wait
salida[primera] trabajando...
[segunda] ya hay otra ejecución en marcha; salgo sin hacer nada
[primera] terminada

El cerrojo se suelta solo cuando termina el proceso que lo tiene, aunque falle o lo maten, así que no quedan cerrojos «olvidados». En el propio crontab también se puede usar sin tocar el script: */15 * * * * flock -n /tmp/comprobar.lock /home/ana/bin/comprobar-web.sh.

Alternativas a cron

En los sistemas con systemd (Ubuntu, Debian, Fedora…) existen los timers de systemd, que registran cada ejecución en el diario del sistema (journalctl), pueden recuperar las ejecuciones perdidas si el equipo estaba apagado y evitan los solapamientos por sí mismos. Son más verbosos de configurar, con dos archivos por tarea, pero para un servidor merecen la pena. En macOS el equivalente es launchd. Para una tarea personal sencilla, cron sigue siendo lo más rápido.

Todos los scripts de esta página se ejecutaron con Bash 5.3 en Ubuntu 26.04, en un contenedor limpio sin acceso a la red, y pasaron ShellCheck 0.11 sin avisos antes de publicarla; las salidas están copiadas de esa ejecución.