Ubuntu 24.04: Cron se ejecuta manualmente pero no según lo programado — PATH/Entorno y soluciones

¿Te fue útil?

Ejecutas el script a mano. Funciona. Lo programas en cron. Desaparece en la noche como un compañero cuando suena el teléfono de guardia.

En Ubuntu 24.04 esto normalmente no significa que “cron esté roto”. Significa que cron es brutalmente honesto: ejecuta tu comando con un entorno mínimo, un shell no interactivo, suposiciones distintas sobre el directorio de trabajo y sin paciencia para un vago “funciona en mi terminal”. Hagamos que cron vuelva a ser aburrido.

Qué cambia cuando cron ejecuta tu comando

Cuando ejecutas un comando manualmente traes mucho equipaje invisible: tu shell interactivo, tus dotfiles, tus ajustes de PATH, tu directorio actual, tu agente SSH cargado, quizá tu ticket de Kerberos y esa variable exportada de la semana pasada que olvidaste que existía.

Cron no trae nada de eso. La visión de cron es más bien: “Aquí hay una hora. Aquí hay una cadena de comando. La ejecutaré con un conjunto pequeño de variables de entorno y no adivinaré lo que quisiste decir.” Eso no es crueldad. Es fiabilidad.

En Ubuntu, cron normalmente usa /bin/sh (dash), no bash, a menos que especifiques lo contrario. Se ejecuta de forma no interactiva. No lee tu ~/.bashrc. El PATH por defecto es pequeño (a menudo /usr/bin:/bin más algunas rutas sbin según el contexto). Y si dependías de rutas relativas, cron se ejecutará desde tu directorio home o desde / dependiendo de cómo se invoque el trabajo, y tus rutas relativas apuntarán al vacío.

Regla operativa: trata a cron como un pequeño contenedor sin personalidad. Si tu trabajo necesita algo, decláralo explícitamente.

Guía rápida de diagnóstico

Cuando estás de guardia no tienes tiempo para un debate filosófico con cron. Necesitas el camino más corto a la verdad. Aquí está el orden que suele encontrar el cuello de botella más rápido.

1) Prueba que cron intentó ejecutarlo

  • Comprueba la salud del servicio cron (estado systemd).
  • Comprueba los registros para el minuto exacto en que el trabajo debería haberse ejecutado.
  • Confirma que editaste el crontab correcto (usuario vs root vs /etc/cron.*).

2) Captura el entorno y stderr/stdout

  • Redirige la salida a un archivo de registro con marcas de tiempo.
  • Vuelca env a un archivo cuando el trabajo se ejecute.
  • Usa rutas absolutas para todo: intérprete, script, binarios, archivos.

3) Elimina diferencias de PATH/shell

  • Establece un PATH conocido en el crontab (o al inicio del script).
  • Forza el shell a bash si escribiste bash-isms.
  • Usa /usr/bin/env con cuidado (el PATH de cron puede no incluir lo que esperas).

4) Comprueba permisos e identidad

  • Confirma que el trabajo se ejecuta como el usuario que crees.
  • Comprueba la pertenencia a grupos y permisos de archivos.
  • Vigila fallos silenciosos de sudo (cron no tiene TTY).

5) Verifica hora y detalles del horario

  • Desajuste de zona horaria (sistema vs expectativa de usuario).
  • Rarezas de DST (los trabajos alrededor de las 02:00 están malditos dos veces al año).
  • Error de sintaxis en cron: lógica día del mes vs día de la semana.

Hechos interesantes y contexto histórico

  1. El “entorno mínimo” de cron es una característica, no un bug. El automatismo en Unix temprano asumía que los scripts debían declarar dependencias explícitamente, porque los shells de inicio eran inconsistentes entre usuarios.
  2. /bin/sh en Ubuntu es dash, no bash, por rendimiento. Esa decisión lleva años y sigue sorprendiendo a scripts que “funcionaban bien” de forma interactiva.
  3. Cron tradicional precede a systemd por décadas. El modelo mental de cron es “ejecutar un comando a una hora”, mientras que los timers de systemd añaden “ejecutar con orden de dependencias y registro en journald”.
  4. Vixie Cron moldeó el ecosistema moderno de cron. Muchas distribuciones heredaron su comportamiento, incluyendo el patrón de entrega de salida por MAILTO.
  5. El formato de horario de cron es intencionalmente compacto. Está optimizado para teclear, no para depurar; por eso errores como campos intercambiados son tan comunes.
  6. Históricamente, cron registraba en syslog. En Ubuntu moderno, syslog puede seguir existiendo, pero journald suele ser el primer lugar donde realmente encontrarás el registro.
  7. Hay múltiples puntos de entrada para cron. Crontabs de usuario, /etc/crontab y los directorios /etc/cron.* se comportan de forma distinta (notablemente: campo de usuario en crontab del sistema).
  8. Anacron existe porque los portátiles duermen. “Ejecutar tareas diarias incluso si la máquina estaba apagada” resolvió un problema real de escritorios en los 90 y sigue importando para servidores y VMs intermitentes.
  9. Algunas implementaciones de cron soportan segundos; el cron clásico no. Si necesitas programación sub-minuto, ya estás fuera de la zona cómoda de cron.

Tareas prácticas (comandos, salidas, decisiones)

Abajo hay verificaciones probadas en campo. Cada una incluye una salida de ejemplo realista y la decisión que tomas a partir de ella. Ejecútalas en orden cuando puedas; elige selectivamente cuando no puedas.

Task 1: Confirmar que el servicio cron está en ejecución

cr0x@server:~$ systemctl status cron
● cron.service - Regular background program processing daemon
     Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-12-29 08:11:02 UTC; 3h 14min ago
       Docs: man:cron(8)
   Main PID: 742 (cron)
      Tasks: 1 (limit: 18639)
     Memory: 2.2M
        CPU: 1.023s
     CGroup: /system.slice/cron.service
             └─742 /usr/sbin/cron -f

Qué significa: Si no está active (running), tu trabajo nunca tuvo oportunidad. Si está en ejecución, continúa.

Decisión: Si está inactivo, inícialo/actívalo. Si está activo, la falla está dentro de la programación, entorno, permisos o tu script.

Task 2: Verifica que editaste el crontab correcto

cr0x@server:~$ crontab -l
# m h  dom mon dow   command
*/5 * * * * /home/cr0x/bin/report.sh

Qué significa: Este es el crontab del usuario actual. El crontab de root es separado.

Decisión: Si pretendías root, ejecuta sudo crontab -l. Si pretendías algo a nivel sistema, revisa /etc/crontab y /etc/cron.d.

Task 3: Revisa el minuto del trabajo en los registros (journald)

cr0x@server:~$ sudo journalctl -u cron --since "2025-12-29 10:00" --until "2025-12-29 10:10"
Dec 29 10:05:01 server CRON[18342]: (cr0x) CMD (/home/cr0x/bin/report.sh)
Dec 29 10:05:01 server CRON[18341]: (CRON) info (No MTA installed, discarding output)

Qué significa: Cron sí ejecutó el comando a las 10:05. También descartó la salida porque no hay un agente de transporte de correo (MTA) instalado.

Decisión: Si no hay una línea “CMD” a la hora esperada, tienes un problema de programación/instalación. Si se ejecutó, ahora necesitas capturar stdout/stderr por tu cuenta.

Task 4: Revisa entradas estilo syslog de cron (si rsyslog se usa)

cr0x@server:~$ grep -i cron /var/log/syslog | tail -n 5
Dec 29 10:05:01 server CRON[18342]: (cr0x) CMD (/home/cr0x/bin/report.sh)
Dec 29 10:00:01 server CRON[18112]: (cr0x) CMD (/home/cr0x/bin/report.sh)

Qué significa: La misma verdad que journald, solo desde syslog. Algunos entornos mantienen ambos; algunos no.

Decisión: Si /var/log/syslog no tiene nada relacionado con cron, no entres en pánico; confía en la salida de journald.

Task 5: Añade registro explícito a la entrada de crontab

cr0x@server:~$ crontab -l | tail -n 3
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * /home/cr0x/bin/report.sh >>/var/log/report.log 2>&1

Qué significa: Fuerzas el shell y el PATH, y conservas la salida.

Decisión: Si el trabajo “no hace nada”, esto crea un artefacto. Si el log permanece vacío, no se está ejecutando (o no puede abrir la ruta del log).

Task 6: Confirma que el archivo de log es escribible por ese usuario

cr0x@server:~$ ls -l /var/log/report.log
-rw-r--r-- 1 root root 0 Dec 29 10:04 /var/log/report.log

Qué significa: Tu usuario no puede añadir a un archivo propiedad de root en /var/log por defecto.

Decisión: Registra en una ruta propiedad del usuario (como ~/cron-logs) o configura permisos/rotación de logs. No “arriesgues” esto con chmod 777 a menos que te guste explicar incidentes.

Task 7: Captura el entorno en tiempo de ejecución de cron

cr0x@server:~$ mkdir -p /home/cr0x/cron-debug
cr0x@server:~$ crontab -l | tail -n 2
*/5 * * * * env | sort > /home/cr0x/cron-debug/env.txt
*/5 * * * * /usr/bin/date -Is >> /home/cr0x/cron-debug/ticks.log 2>&1
cr0x@server:~$ cat /home/cr0x/cron-debug/env.txt
HOME=/home/cr0x
LANG=C.UTF-8
LOGNAME=cr0x
PATH=/usr/bin:/bin
PWD=/home/cr0x
SHELL=/bin/sh
USER=cr0x

Qué significa: El PATH de cron es mínimo y el shell es /bin/sh.

Decisión: Si tu script depende de /usr/local/bin, ~/.local/bin, pyenv, rbenv, nvm, conda, etc., ahora sabes por qué falla según lo programado.

Task 8: Prueba que “command not found” es la falla

cr0x@server:~$ tail -n 5 /home/cr0x/cron-logs/report.log
/home/cr0x/bin/report.sh: line 7: jq: command not found

Qué significa: Tu shell interactivo puede encontrar jq pero cron no.

Decisión: Usa la ruta absoluta (/usr/bin/jq) o establece PATH en crontab/script. También verifica que el paquete exista donde crees.

Task 9: Localiza binarios como lo haría cron

cr0x@server:~$ command -v jq
/usr/bin/jq
cr0x@server:~$ /usr/bin/env -i PATH=/usr/bin:/bin command -v jq
/usr/bin/jq
cr0x@server:~$ /usr/bin/env -i PATH=/usr/bin:/bin command -v aws
/usr/bin/env: ‘command’: No such file or directory

Qué significa: Con un entorno mínimo, incluso command puede no existir como binario independiente; es un builtin del shell. Además, herramientas instaladas en rutas de usuario no serán encontradas.

Decisión: Prueba tu script bajo un entorno depurado. No supongas nada. Si necesitas aws desde ~/.local/bin, establece PATH o llámalo explícitamente.

Task 10: Ejecuta el script como lo haría cron (no interactivo, entorno mínimo)

cr0x@server:~$ /usr/bin/env -i HOME=/home/cr0x USER=cr0x LOGNAME=cr0x PATH=/usr/bin:/bin SHELL=/bin/sh /bin/sh -c '/home/cr0x/bin/report.sh' ; echo $?
127

Qué significa: El código de salida 127 es el clásico “command not found”. Esta es la reproducción limpia que quieres.

Decisión: Arregla dependencias (instala paquetes faltantes, usa rutas absolutas o establece PATH). No depures esto dentro de tu shell interactivo; te está mintiendo.

Task 11: Confirma que el script tiene shebang válido y es ejecutable

cr0x@server:~$ head -n 1 /home/cr0x/bin/report.sh
#!/usr/bin/env bash
cr0x@server:~$ ls -l /home/cr0x/bin/report.sh
-rwxr-xr-x 1 cr0x cr0x 1842 Dec 29 09:55 /home/cr0x/bin/report.sh

Qué significa: Es ejecutable y tiene shebang. Pero nota: /usr/bin/env buscará bash en PATH. En cron, el PATH podría no incluir donde está bash (normalmente sí), pero sé explícito si estás endureciendo.

Decisión: Para cron de producción, prefiere #!/bin/bash si dependes de características de bash. Es aburrido y estable.

Task 12: Verifica que el horario es el que crees

cr0x@server:~$ grep -n . /var/spool/cron/crontabs/cr0x | sed -n '1,5p'
1 # DO NOT EDIT THIS FILE - edit the master and reinstall.
2 # m h  dom mon dow   command
3 5 * * * * /home/cr0x/bin/report.sh

Qué significa: Esto se ejecuta en el minuto 5 de cada hora, no cada 5 minutos. La gente lo lee mal constantemente.

Decisión: Corrige el horario. Si querías cada 5 minutos, usa */5 * * * *.

Task 13: Comprueba supuestos de zona horaria

cr0x@server:~$ timedatectl
               Local time: Mon 2025-12-29 11:25:41 UTC
           Universal time: Mon 2025-12-29 11:25:41 UTC
                 RTC time: Mon 2025-12-29 11:25:41
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active

Qué significa: Tu servidor está en UTC. Si los humanos esperan hora local, el trabajo “fallará” por horas y todos culparán a cron.

Decisión: Decide si quieres tiempo del sistema en UTC (por lo general sí) y ajusta expectativas, o mueve el horario para que coincida con UTC.

Task 14: Detecta fallos de “sudo necesita un tty”

cr0x@server:~$ tail -n 10 /home/cr0x/cron-logs/report.log
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required

Qué significa: Cron no tiene TTY interactivo, así que las solicitudes de contraseña fallan.

Decisión: Deja de usar sudo interactivo en cron. Usa un crontab propiedad de root, una regla sudoers restringida para un comando específico, o rediseña el trabajo para que no necesite escalado de privilegios.

Task 15: Confirma permisos y pertenencia a grupos en tiempo de ejecución

cr0x@server:~$ id cr0x
uid=1000(cr0x) gid=1000(cr0x) groups=1000(cr0x),27(sudo),113(lxd)
cr0x@server:~$ ls -l /data/reports
drwxr-x--- 2 root analytics 4096 Dec 29 08:00 /data/reports

Qué significa: cr0x no está en el grupo analytics, por lo que escribir en /data/reports fallará.

Decisión: Corrige la pertenencia a grupos y permisos adecuadamente, o escribe la salida en un lugar accesible para el usuario del trabajo.

Task 16: Verifica que las reglas allow/deny de cron no bloqueen al usuario

cr0x@server:~$ sudo ls -l /etc/cron.allow /etc/cron.deny
ls: cannot access '/etc/cron.allow': No such file or directory
-rw-r--r-- 1 root root 0 Apr  5  2024 /etc/cron.deny

Qué significa: Si /etc/cron.deny existe y contiene el nombre de usuario, cron rechazará el crontab de ese usuario.

Decisión: Asegura que el usuario esté permitido. En muchos entornos esto no se usa, pero cuando se usa es una trampa silenciosa.

Problemas con PATH y entorno (y soluciones)

Si tu trabajo se ejecuta manualmente pero no en su horario, empieza asumiendo PATH/entorno. Es la causa más frecuente y suele ser fácil de demostrar.

Cron no lee tus dotfiles

Tu shell interactivo puede establecer PATH en ~/.profile, ~/.bashrc o en un “init” corporativo que carga runtimes de lenguajes. Cron no fuentea esos archivos. No es grosero; es determinista.

Solución: Coloca las variables de entorno necesarias ya sea:

  • al inicio del crontab (mejor cuando necesitas ajustes distintos por trabajo), o
  • dentro del script (mejor cuando quieres que el script sea portátil fuera de cron).

Establece PATH explícitamente (y mantenlo aburrido)

La mayoría de trabajos cron de producción debería establecer un PATH conocido y luego llamar a binarios con rutas absolutas para lo crítico. Sí, ambos. No es redundante; es defensa en profundidad contra futuros movimientos de paquetes, cambios de administración y scripts “útiles” de perfil.

PATH recomendado:

  • /usr/local/sbin:/usr/local/bin para herramientas administrativas instaladas localmente
  • /usr/sbin:/usr/bin:/sbin:/bin para paquetes del sistema

Si dependes de ~/.local/bin, añádelo explícitamente. Pero pregúntate por qué un trabajo cron de producción depende de instalaciones pip por usuario. No es ilegal; es frágil.

Sorpresas con locale y codificación

En sesiones interactivas puedes tener un locale rico; en cron puedes tener LANG=C o C.UTF-8. Si tu script analiza fechas, ordena cadenas o procesa texto no ASCII, esto importa.

Solución: Establece LANG y LC_ALL explícitamente en el script. Si procesas UTF-8, decide eso desde el principio.

Variables de entorno presentes manualmente pero ausentes en cron

Ejemplos clásicos:

  • SSH_AUTH_SOCK (tu agente SSH): disponible en tu terminal; ausente en cron.
  • AWS_PROFILE / AWS_REGION: en tu shell; faltan en cron.
  • PYTHONPATH / activación de virtualenv: presente manualmente; no en cron.
  • HTTP_PROXY / NO_PROXY: presentes en escritorios; ausentes en servidores.

Solución: Inyecta las variables necesarias en la línea del crontab o dentro del script. Mejor: no dependas del estado del agente o de autenticación interactiva para trabajos programados; usa credenciales dedicadas con el principio de menor privilegio.

Broma #1: Cron no “olvida” tu PATH. Nunca lo aprendió en primer lugar.

Directorio de trabajo, rutas relativas y umask

Las rutas relativas son una comodidad hasta que se convierten en una estrategia de producción. Cron las hace doler porque no promete un directorio de trabajo que coincida con tus expectativas del terminal.

Usa siempre rutas absolutas en trabajos cron

Si tu script hace ./bin/tool, ../data o escribe en logs/output.log, es efectivamente una lotería: a veces funciona (cuando pruebas desde el directorio del script), a veces falla (cuando cron lo ejecuta desde otro lugar).

Opciones de solución:

  • Usa rutas absolutas en todas partes.
  • O cd a un directorio conocido al inicio del script (cd /opt/app || exit 1).

Las diferencias de umask cambian permisos de archivos

Tu shell interactivo puede establecer una umask que produzca archivos con escritura para el grupo; cron puede usar otra. Los síntomas son sutiles: se crean archivos pero otros procesos no pueden leerlos, o un trabajo posterior falla.

Solución: Establece umask explícitamente en el script. Decide qué permisos necesitas en lugar de heredar sensaciones.

HOME y expansión de tilde

Cron normalmente establece HOME, pero no apuestes por ello. Además, usar ~ dentro de entradas de crontab puede ser inconsistente según el shell y el quoting. Tu script no debería depender de la expansión de tilde para encontrar archivos críticos.

Solución: Usa /home/username (o mejor: un directorio de aplicación dedicado) en cron. Lo aburrido gana.

Permisos, usuarios, grupos y sudo en cron

La forma más rápida de lograr que un trabajo cron “funcione” es ejecutarlo como root. La forma más rápida de tener un incidente después es mantenerlo así.

Sabe qué usuario ejecuta el trabajo

Hay varios lugares para definir trabajos cron:

  • Crontab de usuario vía crontab -e: se ejecuta como ese usuario.
  • Crontab de root vía sudo crontab -e: se ejecuta como root.
  • /etc/crontab y /etc/cron.d/*: incluyen un campo de usuario explícito.
  • /etc/cron.hourly, daily, weekly, monthly: los scripts se ejecutan como root, pero el entorno de ejecución difiere (a menudo vía run-parts).

No uses sudo dentro de cron a menos que lo diseñes

sudo en cron falla por razones previsibles: sin TTY, prompts de contraseña, reseteo de entorno y restricciones de política. Si realmente necesitas privilegio, prefiere:

  • una entrada cron propiedad de root que llame a un script propiedad de root con permisos estrictos, o
  • una regla sudoers mínima que permita un comando específico sin contraseña, con rutas explícitas y sin comodines.

El “truco del comodín en sudoers” eventualmente será usado como una escalera. Puede que no te guste quién la use.

Pertenencia a grupos y grupos suplementarios

Cuando cron se ejecuta como un usuario, usa la pertenencia a grupos de ese usuario. Si añadiste al usuario a un grupo recientemente, quizá necesites una nueva sesión de login para que tus pruebas manuales coincidan con la realidad; cron puede ya estar usando la lista de grupos actualizada, o tu shell puede no hacerlo. Esta discrepancia crea comportamientos confusos de “funciona para mí”.

Solución: Valida el acceso con sudo -u user y verifica permisos explícitamente. No infieras.

Diferencias de shell: /bin/sh vs bash, y por qué importa

Muchos casos de “cron falla misteriosamente” son en realidad “el script usa sintaxis de bash, pero cron lo ejecuta con sh/dash.” Las fallas pueden ser silenciosas si nunca capturas stderr.

Uso de bash que rompe bajo sh

  • [[ ... ]] en lugar de [ ... ]
  • Arrays
  • source en lugar de .
  • Sustitución de procesos: <( ... )
  • Diferencias en set -o pipefail

Solución: O escribe scripts POSIX y ejecútalos con /bin/sh, o declara bash y escribe en bash. Mezclar ambos es la forma de recibir páginas a las 3 a.m.

Forzar el shell en crontab (cuando proceda)

Añade al inicio del crontab:

  • SHELL=/bin/bash

Pero no te quedes ahí. Usa también un shebang correcto en el script. El script debe poder ejecutarse fuera de cron y el intérprete debe ser explícito.

Registro: dónde va la salida de cron en Ubuntu 24.04

Cron tiene dos formas de decirte qué pasó: registros sobre la ejecución y la salida de tu trabajo. La gente las confunde y luego acusa a cron de “estar en silencio”. Cron no está en silencio; simplemente no conectaste el micrófono.

Registros de ejecución: journald y/o syslog

En Ubuntu 24.04, journalctl -u cron suele ser la fuente de verdad más rápida: muestra que cron inició un comando y qué usuario lo ejecutó. No mostrará la salida de tu script a menos que la redirijas.

Salida del trabajo: mail (si tienes un MTA) o tus redirecciones

Tradicionalmente, cron manda por correo stdout/stderr al usuario (o a MAILTO). Pero muchos servidores no tienen un MTA instalado. En ese caso verás el mensaje “No MTA installed, discarding output”. Esa es tu señal: redirige la salida tú mismo.

Mi opinión fuerte: los trabajos cron de producción siempre deberían redirigir la salida a un archivo de registro o a syslog/journald explícitamente. El correo está bien como canal secundario, no como principal.

Prefiere logs estructurados cuando puedas

Si tu trabajo es significativo (pipelines de datos, backups, exportes de facturación), haz mejor que volcar texto. Emite marcas de tiempo, códigos de salida y un resumen en una sola línea. Si puedes, envía logs a tu sistema central. Incluso sin eso, un log local con rotación es una mejora enorme sobre “creo que se ejecutó”.

Cron vs systemd timers: cuándo cambiar

Cron sigue siendo una herramienta fiable. Pero en Ubuntu 24.04, los timers de systemd encajan mejor operativamente cuando te importan orden de dependencias, sandboxing y registro unificado.

Mantén cron cuando

  • El trabajo es simple y local.
  • Necesitas compatibilidad con patrones antiguos y convenciones existentes en la flota.
  • Ya tienes buen registro y detección de fallos.

Prefiere systemd timers cuando

  • Quieres logs en journald automáticamente.
  • Necesitas reintentos, dependencias (network-online) o controles de recursos.
  • Quieres una unidad clara que puedas inspeccionar con systemctl status y monitorizar.

Un modelo mental útil: cron es “disparar y olvidar”. Los timers de systemd son “declara intención y observa estado”. En producción, la observabilidad suele ganar.

Un patrón mínimo de timer (conceptual)

Aunque te quedes en cron hoy, aprende el enfoque de timers. Es una vía de escape cuando los problemas de entorno de cron se convierten en trabajo recurrente. Y sí, puedes ejecutar el mismo script; solo obtienes mejores herramientas alrededor.

Tres microhistorias corporativas desde producción

Microhistoria 1: El incidente causado por una suposición errónea

Un equipo cercano a finanzas tenía un job de exportación nocturno: recoger números, formatear un CSV y dejarlo en una carpeta SFTP. El trabajo funcionaba perfectamente cuando el desarrollador lo ejecutaba a mano. También funcionaba en staging. En producción, “fallaba aleatoriamente” dos veces por semana.

La suposición errónea era pequeña: el script usaba ~/exports y esperaba que el directorio de trabajo fuera la raíz del repo. Cuando alguien probaba manualmente, siempre lo ejecutaban desde el directorio del repo y en un shell interactivo que configuraba PATH y un par de variables.

En cron, el trabajo se ejecutó con PWD=/home/user y SHELL=/bin/sh. La expansión de la tilde se comportó distinto según el quoting, y una ruta relativa apuntó a un directorio inexistente. El script falló temprano, escribió un error en stderr y cron intentó mandarlo por correo. No hubo MTA. Así que el error se descartó. La única señal fueron archivos faltantes, descubiertos por humanos a la mañana siguiente.

La solución fue aburrida y decisiva: rutas absolutas, PATH y locale explícitos, y redirección de salida a un archivo de log. Luego añadieron un simple archivo marcador de “última ejecución exitosa” y una alerta si no se actualizaba antes de las 02:00. Después de eso, el trabajo o tenía éxito o fallaba ruidosamente, que es todo el punto.

Microhistoria 2: La optimización que salió mal

Un grupo de infraestructura intentó acelerar un job de mantenimiento que eliminaba artefactos antiguos. Reemplazaron un script cuidadoso por un one-liner más rápido usando find piped a xargs -P para borrados en paralelo. Parecía genial en una prueba rápida. También redujo el tiempo de minutos a segundos en un directorio pequeño.

En producción apareció el modo de fallo evitable: espacios y saltos de línea en nombres de archivo. La tubería de borrado en paralelo ocasionalmente apuntó a la ruta equivocada. La mayoría de las veces fue inofensivo. Una vez, borró un conjunto de artefactos “actuales” porque la lógica de selección se mangueó.

El otro efecto secundario fue la contención de recursos. Los borrados paralelos generaron ráfagas de operaciones de metadata que dispararon la latencia de I/O para workloads no relacionados. No se cayó nada, pero disparó timeouts y reintentos en un servicio dependiente justo al minuto que corría el job. Clásico “optimizamos una cosa y pagamos en otra”.

Hicieron rollback a un enfoque más lento pero seguro: find -print0 con delimitadores nulos, sin parseos peligrosos, y un límite deliberado de ritmo. No fue glamoroso. Dejó de romper producción. El postmortem incluyó la lección real: si es un job cron que borra cosas, trata “rápido” como un requisito que te ganas, no como un predeterminado.

Microhistoria 3: La práctica aburrida pero correcta que salvó el día

Un equipo de plataforma de datos ejecutaba un import ETL diario vía cron. Tenía un patrón poco sexy: cada ejecución escribía un log con sello temporal y un resumen final de una sola línea que incluía tiempo de ejecución, recuento de registros y código de salida. También escribía un marcador “.ok” solo después de que todos los pasos tuvieran éxito. Y corría con set -euo pipefail en bash, con excepciones manejadas cuidadosamente.

Una mañana, los dashboards downstream carecían de datos frescos. La gente inmediatamente sospechó del cluster del data warehouse. Pero los logs del job de cron contaron la historia en menos de un minuto: el job inició, resolvió DNS y luego falló en una llamada API específica con un error claro sobre validación TLS. El código de salida fue distinto de cero y el archivo marcador no se actualizó. Sin misterio.

La causa resultó ser un cambio de certificado en un endpoint upstream y un CA bundle desactualizado en una imagen de contenedor usada por el script. El equipo actualizó el bundle, volvió a ejecutar el job y el marcador cambió a verde. El incidente se contuvo porque el job era observable y determinista.

No hubo momento heroico. Solo un equipo que trató a cron como producción, no como un calendario mágico con vibras.

Errores comunes: síntoma → causa raíz → solución

Esta es la sección que querrás tener abierta durante una sesión de resolución. Los patrones se repiten entre empresas, equipos y niveles de seniority.

1) Síntoma: “Se ejecuta en el terminal, pero cron no crea archivos de salida.”

Causa raíz: Rutas relativas o directorio de trabajo inesperado. El script escribe en ./output pero cron corre desde HOME o /.

Solución: Usa rutas absolutas o cd al inicio del script. Añade logging mostrando pwd y ls de los directorios esperados.

2) Síntoma: “El log de cron muestra que CMD se ejecutó, pero no pasa nada.”

Causa raíz: La salida se descartó porque no hay MTA, o nunca redirigiste la salida.

Solución: Redirige stdout/stderr a un archivo de log. Si quieres correo, instala/configura un MTA, pero no dependas de él como único canal.

3) Síntoma: “/bin/sh: 1: source: not found” o “[[: not found”

Causa raíz: El script usa características de bash pero se ejecuta bajo /bin/sh (dash).

Solución: Añade shebang de bash y/o establece SHELL=/bin/bash en el crontab. O reescribe el script para POSIX sh.

4) Síntoma: “command not found” para herramientas que sabes que están instaladas

Causa raíz: El PATH en cron es mínimo; tu PATH interactivo es más amplio.

Solución: Establece PATH explícitamente. Prefiere rutas absolutas para comandos críticos.

5) Síntoma: el trabajo se ejecuta pero se comporta distinto (ordenado, parseo, regex)

Causa raíz: Diferencias de locale (LANG/LC_ALL) entre la sesión interactiva y cron.

Solución: Establece locale explícitamente en el script. No analices salidas en formato humano; usa formatos legibles por máquina cuando sea posible.

6) Síntoma: “Permiso denegado” solo bajo cron

Causa raíz: Usuario diferente, falta de pertenencia a grupo, diferencias de umask o escritura en directorios propiedad de root como /var/log.

Solución: Asegura la propiedad/permisos correctos y la pertenencia a grupos. Escribe logs en rutas propiedad del usuario o crea un directorio de logs con permisos controlados.

7) Síntoma: sudo falla en cron

Causa raíz: sudo solicita contraseña; cron no tiene TTY. O la política sudo lo prohíbe.

Solución: No uses sudo interactivo. Ejecuta como el usuario correcto (root si es necesario) o crea entradas sudoers mínimas para comandos específicos.

8) Síntoma: el trabajo “no se ejecuta” alrededor de cambios DST

Causa raíz: DST puede omitir o duplicar horas locales; los horarios cercanos a las 02:00 pueden perderse o repetirse.

Solución: Mantén servidores en UTC. Si debes usar hora local, evita horas frágiles y agrega idempotencia al trabajo.

Broma #2: El horario de verano: porque a veces tu trabajo cron merece fallar dos veces por el mismo error.

Listas de verificación / plan paso a paso

Checklist A: Hacer un trabajo cron apto para producción en 15 minutos

  1. Usa rutas absolutas para el script y cualquier archivo que lea/escriba.
  2. Establece el intérprete explícitamente en el script: #!/bin/bash (o #!/usr/bin/python3, etc.).
  3. Establece PATH ya sea en el crontab o al inicio del script a un valor conocido.
  4. Establece locale si parseas/ordenas texto: LC_ALL=C.UTF-8 o una alternativa deliberada.
  5. Registra stdout/stderr pensando en rotación. Empieza con ~/cron-logs si no tienes estrategia de logging aún.
  6. Sal con código distinto de cero en fallo y no ocultes errores con || true salvo que los manejes intencionalmente.
  7. Añade un “marcador de éxito” (archivo o métrica) para alertar sobre ejecuciones obsoletas.

Checklist B: Depura un trabajo fallido sin adivinar

  1. Confirma que cron disparó: journalctl -u cron.
  2. Redirige salida y reproduce el fallo en un archivo de log.
  3. Vuelca el entorno desde cron (env | sort) y compáralo con tu entorno interactivo (env | sort en una shell).
  4. Ejecuta el trabajo con un entorno depurado usando /usr/bin/env -i.
  5. Arregla PATH, shell, permisos y directorio de trabajo en ese orden.
  6. Sólo entonces depura la lógica de la aplicación.

Checklist C: Decide si migrar a un systemd timer

  1. Si necesitas orden de dependencias (red, montajes), prefiere systemd.
  2. Si necesitas logging consistente y comprobaciones de estado, prefiere systemd.
  3. Si necesitas “ejecutar trabajos perdidos tras una caída”, considera anacron o timers de systemd persistentes.
  4. Si es simple y estable, cron está bien—solo endurece el entorno.

Preguntas frecuentes

1) ¿Por qué mi trabajo cron funciona bien cuando lo pego en un terminal?

Porque tu sesión de terminal tiene un entorno más rico: PATH, locale, agentes de autenticación y configuración de dotfiles. Cron se ejecuta con un entorno mínimo y un shell no interactivo. Haz explícitas las dependencias.

2) ¿Dónde veo los logs de cron en Ubuntu 24.04?

Empieza con journalctl -u cron. Dependiendo de la configuración de logging, cron también puede escribir en /var/log/syslog. La salida de tu script no aparecerá ahí a menos que la redirijas.

3) ¿Por qué veo “No MTA installed, discarding output”?

Cron intentó enviar por correo stdout/stderr del job pero no hay un agente de transporte de correo instalado/configurado. Redirige la salida a un archivo (o instala un MTA si la entrega por correo es parte de tu modelo operativo).

4) ¿Debo establecer PATH en el crontab o en el script?

Si varios jobs cron comparten el mismo entorno, establecer PATH al inicio del crontab es conveniente. Si el script puede ejecutarse fuera de cron (CI, systemd, manual), establece PATH dentro del script también. En producción, a menudo hago ambas cosas y aún uso rutas absolutas para binarios críticos.

5) Mi script usa arrays de bash. ¿Cómo hago que cron use bash?

Usa un shebang de bash (#!/bin/bash) y/o establece SHELL=/bin/bash en el crontab. Luego captura stderr para que realmente veas errores de sintaxis si algo falla.

6) ¿Por qué cron falla al acceder recursos de red que funcionan manualmente?

Causas comunes: variables proxy faltantes, credenciales ausentes, DNS no listo al arranque o el job corre antes de que un montaje de red esté disponible. Cron no hace orden de dependencias; los timers de systemd sí.

7) ¿Cómo pruebo exactamente lo que cron hará sin esperar?

Usa una reproducción con entorno depurado: /usr/bin/env -i con un conjunto mínimo de variables y ejecuta el mismo intérprete que usaría cron. Esto detecta problemas de PATH y locale rápidamente.

8) ¿Es seguro escribir logs en /var/log desde un cron de usuario?

No por defecto. /var/log suele ser propiedad de root. O bien registra en un directorio propiedad del usuario, o crea un directorio de logs dedicado con permisos controlados y reglas de logrotate. Evita logs world-writable.

9) Mi trabajo a veces se ejecuta dos veces. ¿Cron lo duplica?

Cron no duplica intencionadamente una entrada única, pero puede que hayas definido el trabajo en dos sitios (crontab de usuario más /etc/cron.d, por ejemplo). DST también puede duplicar horas locales. Busca en tus ubicaciones de configuración de cron y mantén servidores en UTC.

10) ¿Cuándo dejo de pelear con cron y uso un timer de systemd?

Cuando necesitas orden de dependencias, logging consistente, inspección de estado fácil, controles de recursos o semánticas de “ejecutar tras downtime”. Cron está bien para tareas periódicas simples; systemd es mejor para programación de producción con observabilidad.

Conclusión: próximos pasos prácticos

Si tu trabajo cron en Ubuntu 24.04 se ejecuta manualmente pero no según lo programado, no empieces reescribiendo el script. Comienza probando qué hizo cron, capturando el entorno y haciendo la ejecución determinista: rutas absolutas, PATH explícito, shell explícito y logging explícito.

Luego hazlo observable: guarda un log, registra códigos de salida y añade un marcador de éxito o una alerta. La fiabilidad se trata mayormente de quitar misterio, no de añadir ingenio.

Una idea guía para dejar en una nota: “La esperanza no es una estrategia.” — Gene Kranz

← Anterior
Tooltips sin bibliotecas: posicionamiento, flecha y patrones compatibles con ARIA
Siguiente →
10 mitos sobre las GPU que se niegan a morir

Deja un comentario