La lección anterior terminó con un diagnóstico y una tarea pendiente: la copia de seguridad nocturna de Tramontana se ejecuta a la hora equivocada, con la prioridad equivocada y sobre más datos de los necesarios. Alguien la programó en algún momento y nadie ha vuelto a mirarla. Es el retrato exacto de la automatización mal hecha: funciona hasta que un día tira el servicio.

cron es el temporizador de Unix. Lleva funcionando desde 1975 y sigue siendo la forma más extendida de decirle a un servidor «haz esto todos los días a las cuatro». Su sintaxis es breve y sus fallos son siempre los mismos tres, repetidos en todas las empresas del mundo. Esta lección te da la sintaxis completa y, sobre todo, esos tres fallos con su solución, porque son el 90 % de las incidencias reales de cron.

Una precisión de alcance: aquí las tareas serán one-liners o llamadas a comandos existentes. Escribir el script de copia con sus comprobaciones y su manejo de errores es el Módulo 4.

Contenido

  1. Qué se automatiza bien y qué se automatiza mal
  2. Crontab de usuario y crontab del sistema
  3. Los cinco campos
  4. El conflicto entre día del mes y día de la semana
  5. Fallo clásico 1: el PATH de cron
  6. Fallo clásico 2: la salida que nadie lee
  7. Fallo clásico 3: la zona horaria
  8. Depurar una tarea de cron
  9. Solapamiento y flock
  10. anacron y los timers de systemd
  11. Caso Tramontana: la copia nocturna y la purga

  1. Qué se automatiza bien y qué se automatiza mal

Buen candidato Mal candidato
Idempotente: repetirlo no rompe nada Acumula efectos en cada ejecución
Duración acotada y predecible Puede durar horas indefinidamente
Falla de forma visible y registrada Falla en silencio
No necesita decisiones humanas Requiere criterio caso por caso
Sus recursos están controlados Compite libremente con el servicio

La copia nocturna de Tramontana suspendía en las dos últimas filas: no limitaba su prioridad de E/S y nadie leía su resultado. Una tarea automática que falla en silencio es peor que no tenerla, porque genera la confianza de que el trabajo está hecho. La primera vez que descubres que las copias llevan tres meses fallando suele ser el día que necesitas restaurar una.

  1. Crontab de usuario y crontab del sistema

El demonio cron (paquete cron en Ubuntu) lee varios sitios distintos:

Ubicación Campo de usuario Se edita con Para qué
crontab -e del usuario no crontab -e tareas de una cuenta
/etc/crontab sí editor + sudo tareas del sistema
/etc/cron.d/<fichero> sí editor + sudo tareas de paquetes o servicios
/etc/cron.{hourly,daily,weekly,monthly}/ — scripts ejecutables tareas periódicas simples
operador@srv-tramontana:~$ crontab -l
30 3 * * * tar -czf /srv/tramontana/backups/envios/nocturna.tar.gz /opt/tramontana

Ahí está la culpable de la incidencia de anoche.

Opción Efecto
crontab -l listar
crontab -e editar (usa $EDITOR, que fijaste en 03-01)
crontab -r borrar el crontab entero, sin preguntar
crontab fichero reemplazar el crontab por el contenido de un fichero
crontab -u usuario -l ver el de otro usuario (requiere root)

Cuidado con crontab -r. No pide confirmación y no hay papelera: borra todas tus tareas de golpe. Y está justo al lado de -e en el teclado. La costumbre profesional es sencilla: mantén tu crontab en un fichero versionado y cárgalo con crontab fichero. Así una pulsación desafortunada cuesta un crontab ~/cron/operador.cron, no una reconstrucción de memoria.

La diferencia clave entre los dos formatos es que las líneas de /etc/crontab y /etc/cron.d/ llevan un campo extra con el usuario que ejecuta la tarea, justo antes del comando:

# /etc/cron.d/tramontana
30 3 * * *  operador  /usr/bin/tar -czf /srv/... /opt/...

Olvidar ese campo es un error frecuente: cron interpreta el nombre de usuario como el primer argumento del comando y la tarea falla de un modo desconcertante.

Los directorios cron.daily y compañía los ejecuta run-parts, que tiene una peculiaridad: ignora los ficheros cuyo nombre contenga un punto. Un script llamado copia.sh en /etc/cron.daily/ no se ejecutará nunca. Debe llamarse copia, sin extensión, y ser ejecutable.

  1. Los cinco campos

┌───── minuto        (0-59)
│ ┌─── hora          (0-23)
│ │ ┌─ día del mes   (1-31)
│ │ │ ┌───── mes     (1-12 o jan-dec)
│ │ │ │ ┌─── día sem (0-7, donde 0 y 7 son domingo, o sun-sat)
│ │ │ │ │
* * * * *  comando
Sintaxis Significa
* cualquier valor
5 ese valor exacto
1,15,30 lista
9-17 rango
*/5 cada 5 unidades desde 0
0-30/10 cada 10, dentro del rango
Expresión Cuándo se ejecuta
* * * * * cada minuto
*/15 * * * * a los minutos 0, 15, 30 y 45
30 4 * * * todos los días a las 04:30
0 9-18 * * 1-5 en punto, de 9 a 18, de lunes a viernes
0 4 1 * * el día 1 de cada mes a las 04:00
15 2 * * 6 los sábados a las 02:15
0 0 1 1,7 * el 1 de enero y el 1 de julio
*/10 2-4 * * * cada 10 minutos, entre las 2 y las 4
5 0 * * * a las 00:05 (no a las 00:00: evita el minuto de máxima concurrencia)

Esa última fila es un consejo real. A las 00:00 en punto arranca todo lo que alguien programó sin pensar; desplazar cinco minutos evita competir con ello.

Atajos que sustituyen a los cinco campos:

Atajo Equivale a
@reboot una vez, al arrancar el sistema
@hourly 0 * * * *
@daily / @midnight 0 0 * * *
@weekly 0 0 * * 0
@monthly 0 0 1 * *
@yearly 0 0 1 1 *

  1. El conflicto entre día del mes y día de la semana

Aquí hay una regla que contradice la intuición y que hace que muchas tareas se ejecuten más veces de las previstas.

Si los campos «día del mes» y «día de la semana» son ambos distintos de *, se combinan con OR, no con AND. La tarea se ejecuta si se cumple cualquiera de los dos.

0 4 13 * 5     # a las 04:00 los días 13 Y TAMBIÉN todos los viernes

La intención habitual —«solo los viernes 13»— exige comprobarlo dentro del comando, porque cron no sabe expresarla:

0 4 13 * *  [ "$(date +\%u)" = 5 ] && /ruta/al/comando

(Fíjate en el \%: en un crontab, el símbolo % tiene un significado especial que veremos en el apartado 6.)

Cuando uno de los dos es *, no hay ambigüedad y manda el otro. La tabla resume los cuatro casos:

Día mes Día semana Resultado
* * todos los días
15 * solo el día 15
* 1 solo los lunes
15 1 los días 15 y además todos los lunes

  1. Fallo clásico 1: el PATH de cron

Es, con diferencia, el número uno. El síntoma siempre es el mismo: el comando funciona perfectamente cuando lo escribes tú y no hace nada desde cron.

La causa la conoces desde 03-01: cron no lanza un shell de login ni interactivo, así que no lee /etc/profile, ni ~/.profile, ni ~/.bashrc. Nada de tu entorno existe ahí. El PATH que cron proporciona por defecto es:

PATH=/usr/bin:/bin

Dos directorios. No están /usr/local/bin, ni /usr/sbin, ni por supuesto /home/operador/scripts que añadiste en 03-01. Tampoco existen tus alias, ni tus variables, ni la locale que tienes configurada.

Si rsync estuviera instalado en /usr/local/bin, una tarea que lo invoque por su nombre fallaría cada vez, en silencio y sin dejar rastro visible.

Las tres soluciones, por orden de preferencia:

  1. Rutas absolutas siempre. Averígualas con command -v y escríbelas tal cual.

    command -v rsync tar find te las da todas de una vez: /usr/bin/rsync, /usr/bin/tar, /usr/bin/find.

  2. Declarar el PATH en la cabecera del crontab. Las asignaciones de variables se ponen antes de las tareas y se aplican a todas:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
MAILTO=operador
  1. Hacer que el script se cargue el entorno con . ~/.profile como primera línea. Funciona, pero acopla la tarea a la configuración personal de una cuenta, y eso es frágil.

La regla que aplicamos en Tramontana es la 1 reforzada con la 2: rutas absolutas en los comandos y un PATH explícito en la cabecera, para lo que el comando pueda invocar internamente.

  1. Fallo clásico 2: la salida que nadie lee

Cron captura stdout y stderr de cada tarea y los envía por correo local al usuario propietario. En un servidor sin MTA configurado —como srv-tramontana— ese correo no llega a ninguna parte: se descarta o se queda en /var/mail/operador sin que nadie lo abra jamás.

Resultado: si no rediriges la salida, no te enteras de nada. Ni de los éxitos ni, sobre todo, de los fallos.

Redirección Qué consigues
(nada) correo local que nadie lee
> /dev/null silencia la salida; los errores sí generan correo
> /dev/null 2>&1 silencio absoluto. Evítalo
>> /var/log/... 2>&1 lo correcto: todo queda registrado
30 4 * * * /usr/bin/rsync -a /home/operador/datos/ /srv/tramontana/backups/envios/ >> /var/log/tramontana/cron-copia.log 2>&1

El 2>&1 va al final, por lo que aprendiste en 03-04: primero se redirige stdout al fichero y después se copia ese destino sobre stderr. Al revés, los errores —que son justo lo que te importa— acabarían en el correo fantasma.

Un registro con fecha se consigue anteponiendo un date:

30 4 * * * { /usr/bin/date '+--- %F %T inicio'; /usr/bin/rsync -a /home/operador/datos/ /srv/tramontana/backups/envios/; } >> /var/log/tramontana/cron-copia.log 2>&1

MAILTO controla el destino del correo: [email protected] lo envía a una dirección real (si hay MTA), y MAILTO="" desactiva el envío por completo. La combinación profesional es MAILTO="" más redirección a un fichero de registro, y que sea un sistema de monitorización quien vigile ese fichero.

Y la peculiaridad del %: en un crontab, % se interpreta como un salto de línea que alimenta la entrada estándar del comando. Por eso date +%F dentro de un crontab hay que escribirlo date +\%F. Es una fuente de errores desproporcionada para lo raro que es el detalle.

  1. Fallo clásico 3: la zona horaria

cron usa la zona horaria del sistema, no la del usuario. Compruébala siempre:

operador@srv-tramontana:~$ timedatectl | grep -i 'time zone'
               Time zone: Europe/Madrid (CEST, +0200)

En zonas con horario de verano hay dos días al año problemáticos. Cuando el reloj adelanta en marzo, las 02:30 no existen: una tarea programada a esa hora no se ejecuta ese día. Cuando atrasa en octubre, las 02:30 ocurren dos veces, y según la implementación la tarea puede ejecutarse dos veces.

Tres formas de protegerse:

  • Programar fuera de la franja 01:00–03:00. Es la solución más simple y la que resuelve el problema de verdad.
  • Usar UTC en el servidor (sudo timedatectl set-timezone UTC), práctica habitual en servidores, a costa de tener que traducir mentalmente los horarios.
  • Declarar CRON_TZ=UTC en la cabecera del crontab, que fija la zona solo para esas tareas.

Aquí hay una coincidencia que conviene subrayar: la copia de Tramontana estaba a las 03:30, dentro de la franja de riesgo, y además chocaba con el pico de carga. Moverla resuelve dos problemas de una vez.

  1. Depurar una tarea de cron

flowchart TD
    A["La tarea no hace lo esperado"] --> B{"¿Se ejecutó?<br/>grep CRON /var/log/syslog"}
    B -->|No| C["Revisa la sintaxis de los 5 campos<br/>y que el crontab esté cargado"]
    B -->|Sí| D{"¿Hay registro<br/>en tu fichero de log?"}
    D -->|No| E["Falta la redirección:<br/>añade >> log 2>&1"]
    D -->|Sí| F{"¿Qué dice el error?"}
    F -->|"orden no encontrada"| G["PATH: usa rutas absolutas"]
    F -->|"permiso denegado"| H["Usuario equivocado<br/>o permisos del destino"]
    F -->|"otro"| I["Reproduce con env -i"]

Dónde se registra. El demonio anota cada ejecución en el syslog:

operador@srv-tramontana:~$ grep CRON /var/log/syslog | tail -3
ago 18 03:30:01 srv-tramontana CRON[2210]: (operador) CMD (tar -czf /srv/tramontana/backups/envios/nocturna.tar.gz /opt/tramontana)
ago 18 04:30:01 srv-tramontana CRON[2455]: (operador) CMD (/usr/bin/rsync -a /home/operador/datos/ /srv/tramontana/backups/envios/)
ago 18 04:30:02 srv-tramontana CRON[2455]: (CRON) info (No MTA installed, discarding output)

Tres cosas: la copia arrancó a las 03:30:01, coincidiendo con la incidencia; el rsync también corrió; y el mensaje «No MTA installed, discarding output» es cron diciéndote literalmente que ha tirado la salida a la basura. Ahí está el fallo 2, confesado en el propio log.

Ese registro te dice si la tarea se ejecutó, pero no si funcionó: cron no comprueba el código de salida. Para eso hace falta tu propio fichero de log.

Volcar el entorno de cron. El truco definitivo para el fallo 1: programa una tarea temporal que guarde su entorno y compáralo con el tuyo.

* * * * * /usr/bin/env > /tmp/entorno-cron.txt 2>&1
operador@srv-tramontana:~$ cat /tmp/entorno-cron.txt
SHELL=/bin/sh
PWD=/home/operador
LOGNAME=operador
PATH=/usr/bin:/bin
HOME=/home/operador

Cinco variables. Ni LANG, ni tu PATH ampliado. Nótese además que SHELL es /bin/sh, no bash: las construcciones específicas de Bash pueden fallar. Si las necesitas, declara SHELL=/bin/bash en la cabecera.

Probar el comando con el entorno de cron, sin esperar a que se dispare:

operador@srv-tramontana:~$ env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=/home/operador \
    /bin/sh -c 'rsync -a /home/operador/datos/ /srv/tramontana/backups/envios/'
/bin/sh: 1: rsync: not found

Reproducido en dos segundos. env -i arranca el comando sin ninguna variable heredada y le añade solo las que le indiques: es una simulación fiel de cron. Cualquier tarea nueva debería probarse así antes de programarla.

  1. Solapamiento y flock

Si una ejecución dura más que el intervalo, cron lanza la siguiente igualmente. Dos copias escribiendo en el mismo fichero producen un archivo corrupto; dos purgas simultáneas pueden borrarse los pies mutuamente.

flock resuelve el problema con un fichero de bloqueo:

30 4 * * * /usr/bin/flock -n /var/lock/tramontana-copia.lock /usr/bin/tar -czf /srv/tramontana/backups/envios/nocturna.tar.gz /opt/tramontana/app/ >> /var/log/tramontana/cron-copia.log 2>&1
Opción Comportamiento si el bloqueo está tomado
-n sale de inmediato con código 1
-w 60 espera hasta 60 segundos y luego se rinde
(nada) espera indefinidamente

-n es casi siempre la opción correcta para una tarea periódica: si la anterior sigue en marcha, esta sobra. Sin -n, las ejecuciones se van encolando y acabas con veinte procesos esperando.

El bloqueo se libera solo cuando el proceso termina, incluso si muere de golpe, porque lo gestiona el kernel a través del descriptor de archivo. No hay ficheros de bloqueo huérfanos que limpiar a mano, que es exactamente el problema de implementarlo con un touch y un rm.

  1. anacron y los timers de systemd

cron supone que la máquina está encendida a la hora prevista. Si el servidor estaba apagado, la tarea no se recupera. anacron cubre ese hueco: trabaja con granularidad de días y, al arrancar, ejecuta lo que quedó pendiente. En Ubuntu, cron.daily, cron.weekly y cron.monthly los gestiona anacron precisamente por eso.

cron anacron systemd timer
Granularidad minutos días segundos
Recupera lo perdido no sí sí (Persistent=true)
Registro syslog syslog journalctl -u <unidad>
Dependencias entre tareas no no sí
Aleatorizar el arranque no sí RandomizedDelaySec
Complejidad mínima baja alta (dos ficheros por tarea)

Cuándo elegir cada uno. cron para lo simple y periódico en un servidor siempre encendido: es universal, cabe en una línea y cualquier administrador lo entiende. anacron para portátiles y máquinas que se apagan. Los timers de systemd cuando necesitas dependencias entre unidades, control de recursos, reintentos o el registro integrado del sistema; se estudian en 05-05. Para la copia de Tramontana, cron es suficiente y más legible.

  1. Caso Tramontana: la copia nocturna y la purga

Rehacemos la tarea culpable aplicando todo lo anterior. El crontab se mantiene en un fichero versionado y se carga desde ahí.

operador@srv-tramontana:~$ crontab -l > ~/cron-operador.bak-$(date +%F)
operador@srv-tramontana:~$ nano ~/cron/operador.cron
# Crontab de operador — Tramontana Reservas
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
CRON_TZ=Europe/Madrid

# Copia diaria de datos y del release activo. 04:20 (fuera del pico y del cambio de hora).
20 4 * * * /usr/bin/flock -n /var/lock/tramontana-copia.lock /usr/bin/ionice -c 3 /usr/bin/tar -czf /srv/tramontana/backups/envios/datos-$(/usr/bin/date +\%F).tar.gz /home/operador/datos /opt/tramontana/app/ >> /var/log/tramontana/cron-copia.log 2>&1

# Purga de copias de más de 30 días. Domingos a las 05:10.
10 5 * * 0 /usr/bin/find /srv/tramontana/backups/envios -type f -name 'datos-*.tar.gz' -mtime +30 -delete >> /var/log/tramontana/cron-purga.log 2>&1

Se carga con crontab ~/cron/operador.cron y se comprueba con crontab -l. Repasa cada decisión, porque cada una corrige un problema concreto:

  • 04:20 en lugar de 03:30: fuera del pico de errores que identificaste en 03-05 y fuera de la franja del cambio de hora.
  • flock -n: si una copia anterior sigue en marcha, esta no arranca.
  • ionice -c 3: la copia solo usa el disco cuando el servicio no lo necesita. Es la corrección directa de la incidencia de 03-06.
  • /opt/tramontana/app/ con barra final en vez de /opt/tramontana entero: app es el enlace simbólico al release activo, y la barra hace que tar siga el enlace. Se copia un release en vez de cuatro, y siempre el que está sirviendo.
  • Nombre con fecha: datos-2026-08-18.tar.gz, con \%F escapado. Cada día su fichero, en vez de sobrescribir la única copia que tenías.
  • >> ... 2>&1 en ambas tareas: todo queda registrado, con el 2>&1 al final.
  • Rutas absolutas en todos los comandos, incluido el date de dentro de la sustitución.
  • La purga en tarea aparte y con find -mtime +30: sabes de 03-03 que eso significa 31 días o más, margen sobrado para una retención mensual.

Verificación antes de irse a casa: se prueba el comando con el entorno de cron y se comprueba el resultado.

operador@srv-tramontana:~$ env -i SHELL=/bin/bash PATH=/usr/bin:/bin HOME=/home/operador /bin/bash -c \
    '/usr/bin/flock -n /var/lock/tramontana-copia.lock /usr/bin/ionice -c 3 /usr/bin/tar -czf /tmp/prueba.tar.gz /home/operador/datos /opt/tramontana/app/'
operador@srv-tramontana:~$ echo $?; ls -lh /tmp/prueba.tar.gz
0
-rw-r----- 1 operador operador 47M ago 18 13:40 /tmp/prueba.tar.gz
operador@srv-tramontana:~$ tar -tzvf /tmp/prueba.tar.gz | head -2
drwxr-x--- operador/tramontana 0 2026-08-18 09:14 home/operador/datos/
-rw-r----- operador/tramontana 1284 2026-08-14 11:02 home/operador/datos/reservas.csv

Código 0, 47 MB —un release, no cuatro— y tar -tzvf confirma que el contenido es el esperado antes de dar la tarea por buena. Es la convención del curso aplicada aquí: mirar dentro del archivo antes de confiar en él.

Informe para Marta. La copia nocturna se ha reprogramado a las 04:20, con la prioridad de disco más baja y limitada al release en servicio en lugar de a los cuatro. Esto protege frente a que la copia vuelva a dejar sin disco a la aplicación, frente a que dos copias se solapen y frente a la pérdida de la copia anterior al generarse la nueva. No protege frente a un fallo de la copia: nadie vigila todavía /var/log/tramontana/cron-copia.log, de modo que un error seguiría pasando desapercibido, y tampoco frente a la pérdida del servidor entero, porque las copias siguen en el mismo disco. Ambas cosas requieren decisiones que exceden el ajuste técnico y se abordan en el módulo de administración.

Errores Comunes y Consejos

  • crontab -r en lugar de -e. Borra todo sin preguntar. Mantén el crontab en un fichero y cárgalo con crontab fichero.
  • Olvidar el campo de usuario en /etc/cron.d/. Cron lo toma como argumento del comando.
  • Poner una extensión a un script de /etc/cron.daily/. run-parts ignora los nombres con punto.
  • Suponer que existe tu PATH. Cron da /usr/bin:/bin y nada más. Rutas absolutas.
  • % sin escapar. Dentro de un crontab, % es un salto de línea. Escribe \%.
  • No redirigir la salida. Sin >> log 2>&1 los fallos desaparecen.
  • Poner 2>&1 antes de la redirección de salida. Va al final, siempre.
  • Programar entre las 02:00 y las 03:00. Es la franja del cambio de hora.
  • * * * * * de prueba y olvidarse de quitarlo. Deja una tarea corriendo cada minuto para siempre.
  • Consejo: documenta cada tarea con un comentario que diga qué hace y por qué a esa hora. Dentro de un año lo agradecerás tú, no otro.
  • Consejo: haz que la tarea escriba una marca de éxito con fecha. Comprobar «¿la última línea del log es de hoy?» es un chequeo trivial y detecta el 90 % de los problemas.

Ejercicios

Ejercicio 1. Escribe las expresiones de cron para: cada 20 minutos entre las 8 y las 20 los días laborables; el último día de cada mes a las 23:50; y a las 06:15 los días 1 y 15. Justifica el segundo caso, que tiene truco.

Ejercicio 2. Programa una tarea que registre cada hora el número de errores 500 de errores.log en /var/log/tramontana/errores-500.log junto con la fecha. Aplica las tres protecciones contra los fallos clásicos y demuestra que funcionaría en el entorno de cron antes de instalarla.

Ejercicio 3. Una tarea programada */5 * * * * /home/operador/scripts/sincronizar >> /tmp/sync.log 2>&1 no produce ninguna línea en su log, aunque el comando funciona cuando lo ejecutas tú. Describe el procedimiento completo de diagnóstico, en orden, indicando qué comprobarías en cada paso y qué concluirías de cada resultado.

Soluciones

Solución 1.

*/20 8-20 * * 1-5   # cada 20 min, de 8 a 20, de lunes a viernes
15 6 1,15 * *       # a las 06:15 los días 1 y 15

El segundo, «el último día de cada mes», no se puede expresar con los cinco campos: cron no sabe cuántos días tiene el mes, y 31 se saltaría febrero, abril, junio, septiembre y noviembre. La solución idiomática es programarlo todos los días y dejar que el comando decida:

50 23 * * * [ "$(/usr/bin/date -d tomorrow +\%d)" = 01 ] && /ruta/comando

Se pregunta qué día será mañana: si es día 1, hoy es el último del mes, sea cual sea su longitud y aunque el año sea bisiesto. Es un buen ejemplo de la frontera de cron: cuando la condición no cabe en los cinco campos, se ejecuta a diario y se comprueba dentro. El \% va escapado, como siempre.

Solución 2. Primero se prueba el comando en el entorno de cron:

operador@srv-tramontana:~$ env -i PATH=/usr/bin:/bin HOME=/home/operador /bin/sh -c \
    'echo "$(/usr/bin/date +%F\ %T) $(/usr/bin/grep -c "ERROR 500" /var/log/tramontana/errores.log)"'
2026-08-18 13:52 41

Funciona con solo /usr/bin:/bin en el PATH, luego no dependemos de nada que cron no tenga. Ahora la línea de crontab:

0 * * * * /usr/bin/flock -n /var/lock/errores-500.lock /bin/sh -c 'echo "$(/usr/bin/date +\%F\ \%T) $(/usr/bin/grep -c \"ERROR 500\" /var/log/tramontana/errores.log)"' >> /var/log/tramontana/errores-500.log 2>&1

Las tres protecciones: rutas absolutas para date y grep, de modo que el PATH mínimo no importe; >> ... 2>&1 para que tanto el resultado como cualquier error queden en el fichero y no en un correo que nadie lee; y CRON_TZ declarado en la cabecera del crontab —junto con la elección del minuto 0 de cada hora, lejos de la franja del cambio horario— para que las marcas de tiempo sean coherentes. Se añade flock porque es una costumbre barata, aunque aquí el riesgo de solapamiento sea mínimo. Y los % van escapados: sin las barras, cron cortaría el comando en el primer % y le pasaría el resto como entrada estándar, que es un fallo especialmente difícil de reconocer.

Una línea que empieza a acumular escapes como esta es la señal de que el contenido pide un script propio. Eso es lo que resolverá el Módulo 4.

Solución 3. El procedimiento, en orden, de lo más externo a lo más interno:

  1. ¿Existe la tarea? crontab -l | grep sincronizar. Si no aparece, se editó otro crontab —el de root, por ejemplo— o un crontab -r se la llevó.
  2. ¿Se está ejecutando? grep CRON /var/log/syslog | grep sincronizar | tail -3. Si no hay entradas, cron no la está lanzando: revisa la sintaxis de los campos y que el demonio esté activo. Si sí las hay, cron la lanza y el problema está más adentro.
  3. ¿Existe el fichero de log y con qué permisos? ls -l /tmp/sync.log. Un log vacío pero existente confirma que la redirección funciona y que el comando no escribe nada; que el fichero no exista apunta a que el comando ni siquiera llegó a arrancar.
  4. ¿Es ejecutable y con la ruta correcta? ls -l /home/operador/scripts/sincronizar. Sin el bit x, cron obtiene «permiso denegado». Y aquí está el sospechoso principal: la tarea usa una ruta absoluta al script, pero el script por dentro probablemente invoca comandos por su nombre, y esos sí dependen del PATH.
  5. Reproducir con el entorno de cron: env -i PATH=/usr/bin:/bin HOME=/home/operador /bin/sh /home/operador/scripts/sincronizar. Este paso casi siempre reproduce el fallo al instante.
  6. Verificar la escritura sobre el destino con sudo -u operador touch en el directorio de destino, por si la tarea corre con un usuario distinto del que crees.

La conclusión más probable es la del paso 5. Y hay una pista que apunta a ello desde el principio: si el log está vacío en lugar de contener un mensaje de error, el comando no llegó a producir salida, lo que encaja con un shell que ni siquiera pudo arrancar el binario. Un log vacío es información, no ausencia de información.

Conclusión

Has convertido una tarea automática peligrosa en una tarea automática defendible.

  • Sabes qué se automatiza bien: lo idempotente, acotado, con recursos controlados y con fallo visible. Y distingues el crontab de usuario del crontab del sistema, con su campo extra de usuario, y conoces run-parts y su manía con los puntos en los nombres.
  • Dominas los cinco campos con listas, rangos, pasos y atajos, y sabes que día del mes y día de la semana se combinan con OR, no con AND.
  • Tienes resueltos los tres fallos clásicos: el PATH mínimo de cron —rutas absolutas siempre—, la salida que nadie lee —>> log 2>&1 con el 2>&1 al final— y la zona horaria con su franja de riesgo entre las 02:00 y las 03:00.
  • Depuras una tarea con método: grep CRON /var/log/syslog para saber si se ejecutó, tu propio log para saber si funcionó, el volcado del entorno con env y la reproducción exacta con env -i.
  • Evitas el solapamiento con flock -n, sabiendo que el bloqueo lo libera el kernel y no deja restos.
  • Conoces anacron para máquinas que se apagan y sabes cuándo un timer de systemd compensa su complejidad.
  • Y has reprogramado la copia de Tramontana con hora nueva, bloqueo, prioridad de E/S, alcance reducido al release activo, nombre con fecha y registro, con la purga en tarea aparte y un informe honesto sobre lo que la medida no cubre.

Queda un frente sin tocar. Todo lo que has hecho hasta ahora ocurre dentro de srv-tramontana, y un servidor solo existe para quien pueda llegar hasta él. La última lección del módulo, Comandos de Redes, te da las herramientas para mirar hacia fuera: ip para leer la configuración real de la máquina, ping, traceroute y mtr para el camino, dig y /etc/hosts para la resolución de nombres, ss para saber qué puertos escuchan y quién los ocupa, y curl para comprobar si el servicio responde de verdad. Todo ello ordenado en una metodología de diagnóstico por capas que aplicarás al caso que te espera: Marta avisa de que la web de reservas «no carga», y hay que averiguar exactamente dónde está el corte.

Curso de Linux: De Principiante a Administrador de Sistemas

Módulo 1: Introducción a Linux

Módulo 2: Comandos Básicos de Linux

Módulo 3: Habilidades Avanzadas en la Línea de Comandos

Módulo 4: Scripting en Shell

Módulo 5: Administración del Sistema

Módulo 6: Redes y Seguridad

Módulo 7: Temas Avanzados

Módulo 8: Proyectos Prácticos

© Copyright 2026. Todos los derechos reservados