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 errorset -u: trata variables no definidas como errorset -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 pipefailal 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.