El mismo comando conecta desde una shell y muere bajo cron, así que la conclusión natural es que el túnel o el certificado tienen la culpa. Mire qué tienen en común las dos ejecuciones antes de creerlo: el binario, la configuración y las credenciales son idénticos, y solo cambió el entorno del proceso. Cuando el programa ni siquiera se encuentra, ninguna cantidad de depuración TLS ayuda. La solución suele estar en una ruta, un directorio de trabajo y un puñado de variables de entorno, no en la VPN.

command -v openvpn en la shell que funciona imprime la respuesta, y muy a menudo es /usr/sbin/openvpn, un directorio que cron no pone en su ruta de búsqueda.

El túnel nunca arrancó, así que el túnel no es el problema

Los trabajos iniciados por cron heredan un entorno mínimo, convencionalmente PATH=/usr/bin:/bin, y ninguna terminal de control. Todo lo instalado en /usr/sbin o /usr/local/sbin simplemente falta en esa lista, y la línea del registro dice openvpn: not found en lugar de un error de TLS. El subprocess de Python falla de otra manera: hereda el entorno del proceso del intérprete, que puede ser más rico que el de cron pero sigue sin tener una shell que expanda una tilde. Ambos fallos ocurren antes de que un solo paquete salga del host.

Una forma rápida de distinguirlos es leer el primer error en lugar del último. Un binario que falta produce una excepción de shell o de Python, una configuración mala produce un mensaje de uso de OpenVPN, y un fallo real de apretón de manos produce marcas de tiempo y direcciones de pares. Solo el tercero es un problema de túnel.

Las rutas absolutas y las listas de argumentos superan a las cadenas de shell

Pase la ruta resuelta explícitamente y sáltese la shell por completo. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) le da un código de retorno, stdout y stderr sin pedirle a una shell que interprete un nombre de archivo. Construya la ruta de la configuración a partir de os.environ["HOME"] o pathlib.Path.home(), para que una tilde nunca haya que expandirla. Cuando de verdad necesite sintaxis de shell, use shlex.split, porque dividir una cadena con .split() se rompe en cualquier ruta que contenga un espacio. Lea también stderr, ya que OpenVPN escribe ahí la mayoría de sus fallos tempranos.

La misma regla vale para cron: la línea del trabajo no es una shell de inicio de sesión, así que nada carga su perfil y nada define sus alias.

Qué hereda realmente un crontab

Una entrada de crontab recibe el entorno que declara el crontab más unos cuantos valores por defecto heredados, así que escriba la ruta completa o envuelva la llamada en un script que lo haga. Una línea como PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin al principio del archivo cubre las ubicaciones de instalación habituales, y redirigir con >> cron.log 2>&1 es lo que hace legible el siguiente fallo. Un perfil protegido con contraseña no tiene forma de pedir su secreto bajo cron, ya que no existe una terminal donde preguntar. Esa conexión pertenece a una unidad de systemd que pueda leer un archivo de credenciales, no a un trabajo que finge ser interactivo.

Las rutas relativas son la segunda trampa. Una prueba suele ocurrir desde la carpeta que contiene el perfil, y un trabajo de cron empieza en otro sitio por completo, así que --config office.ovpn se resuelve en un archivo que no está y el mensaje se lee como un archivo que falta más que como un túnel que falta.

Demuéstrelo despojando el entorno

Reproduzca el entorno mínimo directamente: env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. Una queja de ruta o de terminal de esa ejecución significa que el problema está por encima de la VPN. Si conecta, los sospechosos restantes son la cuenta con la que se ejecuta, el directorio en el que arranca y la credencial que presenta. Cualquiera de los dos resultados es mejor que otra ronda de comprobación de certificados.

Luego haga que el envoltorio se autoinforme. Salga con un estado distinto para cada etapa alcanzada, registre ambos flujos con una marca de tiempo, y el siguiente fallo le dirá si murió en la resolución de la ruta, en la autenticación o después de que el túnel ya estuviera levantado.

Una unidad también necesita un orden correcto: Wants=network-online.target más After=network-online.target impiden que el cliente arranque antes de que exista una dirección o una ruta por defecto, lo que de otro modo se manifiesta como errores de certificado o de TLS que desaparecen al reiniciar a mano.

Artículos relacionados