Blog/Tutorial

Cómo saber si tus cronjobs están fallando en silencio

·9 min de lectura
Cómo saber si tus cronjobs están fallando en silencio

Tu cronjob lleva meses "funcionando". Aparece en crontab -l, el servidor está encendido, no hay nada raro. Pero cuando abres los logs, el último registro tiene cuatro semanas. O peor: cuando necesitas restaurar un backup, no hay nada que restaurar.

Los cronjobs silenciosos son uno de los problemas más peligrosos en infraestructura precisamente porque no hay señal de alarma. No ves un error. No recibes un email. Solo ausencia.

Esta guía te explica cómo detectar si tus cronjobs están fallando, cómo diagnosticar la causa, y cómo evitar que vuelva a ocurrir.


Por qué los cronjobs fallan en silencio

Antes de detectar el problema, conviene entender por qué ocurre.

1. El entorno de cron no es tu entorno de usuario

Este es el culpable más frecuente. Cuando ejecutas un script manualmente, tienes tu PATH, tus variables de entorno y tus aliases. Cron arranca con un entorno mínimo y limpio.

Si tu script llama a rclone, restic, python3 o cualquier otro binario que instalaste en /home/usuario/.local/bin o /usr/local/bin, puede funcionar perfectamente cuando lo ejecutas tú y fallar en silencio cuando lo ejecuta cron, porque ese directorio no está en el PATH de cron.

Solución rápida: usa rutas absolutas en tus scripts.

# Mal — depende del PATH del usuario
rclone sync /data remote:backup/

# Bien — ruta absoluta
/usr/bin/rclone sync /data remote:backup/

2. Rutas relativas en el script

Un script que funciona desde /home/usuario/scripts/ puede fallar cuando cron lo ejecuta porque el directorio de trabajo no es el que esperas.

# Al inicio de tu script — establece el directorio explícitamente
cd /home/usuario/scripts || exit 1

3. El destino no está disponible

Si tu backup va a un NAS, a un bucket S3 o a un servidor remoto, cualquier problema de conectividad puede hacer que el script "termine correctamente" (exit 0) sin haber copiado nada real. Muchas herramientas de backup no devuelven error si el destino no está disponible — simplemente no copian nada.

4. Permisos

Un script que tú ejecutas como tu usuario puede necesitar permisos que el usuario cron (a menudo root o el usuario propietario del crontab) no tiene sobre ciertos archivos o directorios.

5. El propio cron no está corriendo

Poco frecuente pero ocurre: el servicio cron o crond puede haberse detenido tras una actualización o un reinicio del sistema. Si nadie lo monitoriza, nadie lo sabe.


Cómo diagnosticar un cronjob que no funciona

Paso 1: Verifica que cron está corriendo

systemctl status cron      # Debian/Ubuntu
systemctl status crond     # RHEL/CentOS/Fedora

Si no está activo, arrancarlo es fácil:

systemctl start cron && systemctl enable cron

Paso 2: Comprueba los logs de cron

# Ver las últimas ejecuciones
grep CRON /var/log/syslog | tail -50

# En sistemas con journald
journalctl -u cron --since "2 days ago"
journalctl -u crond --since "2 days ago"

Busca líneas con el nombre de tu script. Si no aparecen cuando deberían, cron no lo está ejecutando. Si aparecen pero el script falla, verás el código de salida.

Paso 3: Fuerza la ejecución manualmente con el entorno de cron

La forma más fiable de reproducir el fallo es ejecutar el script con el mismo entorno que usa cron:

# Ejecuta el script con un entorno mínimo, como hace cron
env -i HOME=/root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin bash /ruta/a/tu/script.sh

Si falla aquí y funciona normalmente, el problema es el entorno. Revisa el PATH y las variables que usa tu script.

Paso 4: Captura la salida del script

Cron descarta toda la salida por defecto (o la manda por email local si tienes MAILTO configurado, lo que nadie suele tener). Redirige la salida a un log:

# En tu crontab
0 2 * * * /home/usuario/backup.sh >> /var/log/backup.log 2>&1

El 2>&1 redirige también stderr. Con esto, la próxima vez que el script corra (o falle), tendrás un registro de qué pasó exactamente.

Paso 5: Añade set -euo pipefail a tus scripts

Sin esto, un script bash continúa ejecutándose aunque un comando falle. Con estas opciones:

#!/bin/bash
set -euo pipefail

# El script se detiene en el primer error
rsync -av /datos/ /backup/ || exit 1
rclone sync /datos remote:bucket/

echo "Backup completado: $(date)"
  • set -e: sale si cualquier comando devuelve un código de error
  • set -u: trata variables no definidas como error
  • set -o pipefail: propaga errores a través de pipes

La solución real: no depender de que todo salga bien

Depurar cronjobs es útil, pero no es suficiente. El problema de fondo es que el modelo "si no hay error, todo va bien" no funciona para tareas programadas críticas.

La solución correcta es invertir la lógica: en lugar de esperar que algo salga mal y te enteres, tu script avisa activamente cuando termina bien. Si ese aviso no llega, sabes que algo fue mal.

Esto es exactamente para lo que sirven los heartbeats.


Cómo configurar heartbeats para tus cronjobs

La implementación básica

Añade una llamada HTTP al final de tu script, después de que todo lo importante haya terminado:

#!/bin/bash
set -euo pipefail

# Tu lógica de backup aquí
rclone sync /datos remote:bucket/

# Solo llega aquí si todo funcionó — envía el heartbeat
curl -fsS https://securyblack.com/api/heartbeat/TU_ID > /dev/null

Con set -e, si cualquier paso falla, el script se detiene antes del curl. La señal no llega. Recibes una alerta.

Si el servidor está apagado y no puede correr el cronjob, tampoco llega la señal. Recibes una alerta.

Configurar el periodo de gracia

En SecuryBlack Heartbeats configuras cuánto tiempo puede pasar sin recibir la señal antes de considerarlo un problema. Si tu backup normalmente tarda 20-40 minutos, un periodo de gracia de 90 minutos cubre variaciones normales sin generar falsas alarmas.

Heartbeat con medición de duración

Si quieres saber cuánto tarda cada ejecución, envía también una señal de inicio:

#!/bin/bash
set -euo pipefail

# Señal de inicio (opcional, para medir duración)
curl -fsS "https://securyblack.com/api/heartbeat/TU_ID/start" > /dev/null

# Tu lógica de backup
rclone sync /datos remote:bucket/

# Señal de fin — confirma que todo fue bien
curl -fsS "https://securyblack.com/api/heartbeat/TU_ID" > /dev/null

Ejemplo completo: backup con rclone

#!/bin/bash
set -euo pipefail

LOG="/var/log/backup-$(date +%Y%m%d).log"
HEARTBEAT_ID="TU_ID_AQUI"

echo "[$(date)] Iniciando backup..." | tee -a "$LOG"

# Sync al bucket de S3
/usr/bin/rclone sync /datos remote:mi-bucket/backup/ \
  --log-file="$LOG" \
  --log-level INFO

echo "[$(date)] Backup completado." | tee -a "$LOG"

# Heartbeat solo si llegamos hasta aquí
curl -fsS "https://securyblack.com/api/heartbeat/${HEARTBEAT_ID}" > /dev/null

Otros cronjobs que merecen un heartbeat

Los backups son el ejemplo más obvio, pero cualquier tarea programada cuyo fallo silencioso tenga consecuencias merece un heartbeat:

  • Scripts de sincronización entre bases de datos o servidores
  • Rotación de logs — si no rota, el disco se llena
  • Actualizaciones automáticas de listas negras, certificados, o bases de datos locales
  • Scripts de notificación — si mandas un resumen diario y un día no llega, quieres saber por qué
  • Tareas de mantenimiento de base de datos — VACUUM, ANALYZE, optimizaciones periódicas
  • Generación de reportes — si alguien espera ese informe cada lunes

La regla: si un cronjob puede fallar y nadie se enteraría hasta que sea demasiado tarde, necesita un heartbeat.


Resumen: checklist para cronjobs robustos

  • [ ] set -euo pipefail al inicio de cada script bash
  • [ ] Rutas absolutas a todos los binarios
  • [ ] Directorio de trabajo explícito (cd /ruta || exit 1)
  • [ ] Salida redirigida a un log (>> /var/log/script.log 2>&1)
  • [ ] Heartbeat al final de cada script crítico
  • [ ] Periodo de gracia configurado con margen razonable
  • [ ] Cron y crond habilitados para iniciarse con el sistema

¿Quieres configurar heartbeats para tus scripts ahora mismo? SecuryBlack Heartbeats está disponible gratis durante la beta — un curl al final de tu script es todo lo que necesitas.