La même commande se connecte depuis un shell et meurt sous cron, donc la conclusion naturelle est que le tunnel ou le certificat est en cause. Regardez ce que les deux exécutions ont en commun avant de le croire : le binaire, la configuration et les identifiants sont identiques, et seul l’environnement du processus a changé. Quand le programme n’est même pas trouvé, aucun débogage TLS n’aide. Le correctif se trouve généralement dans un chemin, un répertoire de travail et une poignée de variables d’environnement, pas dans le VPN.

command -v openvpn dans le shell qui fonctionne affiche la réponse, et c’est très souvent /usr/sbin/openvpn, un répertoire que cron ne met pas dans son chemin de recherche.

Le tunnel n’a jamais démarré, donc le tunnel n’est pas le problème

Les tâches lancées par cron héritent d’un environnement minimal, conventionnellement PATH=/usr/bin:/bin, et d’aucun terminal de contrôle. Tout ce qui est installé dans /usr/sbin ou /usr/local/sbin est simplement absent de cette liste, et la ligne de journal indique openvpn: not found au lieu d’une erreur TLS. Le subprocess de Python échoue autrement : il hérite de l’environnement du processus interpréteur, qui peut être plus riche que celui de cron mais n’a toujours pas de shell pour développer un tilde. Les deux échecs se produisent avant qu’un seul paquet ne quitte l’hôte.

Un moyen rapide de les distinguer est de lire la première erreur plutôt que la dernière. Un binaire manquant produit une exception de shell ou de Python, une mauvaise configuration produit un message d’usage d’OpenVPN, et une vraie panne de poignée de main produit des horodatages et des adresses de pairs. Seule la troisième est un problème de tunnel.

Les chemins absolus et les listes d’arguments battent les chaînes de shell

Passez le chemin résolu explicitement et sautez complètement le shell. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) vous donne un code de retour, stdout et stderr sans demander à un shell d’interpréter un nom de fichier. Construisez le chemin de configuration à partir de os.environ["HOME"] ou de pathlib.Path.home(), pour qu’un tilde n’ait jamais à être développé. Quand vous avez vraiment besoin de syntaxe shell, utilisez shlex.split, car découper une chaîne avec .split() casse sur tout chemin contenant une espace. Lisez aussi stderr, puisque OpenVPN y écrit la plupart de ses échecs précoces.

La même règle vaut pour cron : la ligne de tâche n’est pas un shell de connexion, donc rien ne charge votre profil et rien ne définit vos alias.

Ce qu’un crontab hérite réellement

Une entrée de crontab reçoit l’environnement que le crontab déclare plus quelques valeurs par défaut héritées, alors écrivez le chemin en entier ou enveloppez l’appel dans un script qui le fait. Une ligne comme PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin en haut du fichier couvre les emplacements d’installation habituels, et la redirection avec >> cron.log 2>&1 est ce qui rend l’échec suivant lisible. Un profil protégé par phrase de passe n’a aucun moyen de demander son secret sous cron, puisqu’aucun terminal n’existe pour poser la question. Une telle connexion appartient à une unité systemd capable de lire un fichier d’identifiants, pas à une tâche qui prétend être interactive.

Les chemins relatifs sont le second piège. Un essai se fait souvent depuis le dossier qui contient le profil, et une tâche cron démarre ailleurs, donc --config office.ovpn se résout en un fichier absent et le message se lit comme un fichier manquant plutôt qu’un tunnel manquant.

Prouvez-le en dépouillant l’environnement

Reproduisez l’environnement minimal directement : env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. Une plainte de chemin ou de terminal issue de cette exécution signifie que le problème siège au-dessus du VPN. Si elle se connecte, les suspects restants sont le compte sous lequel elle tourne, le répertoire dans lequel elle démarre et l’identifiant qu’elle présente. L’un ou l’autre résultat vaut mieux qu’un nouveau tour de vérification des certificats.

Rendez ensuite le wrappeur auto-rapporteur. Sortez avec un statut distinct pour chaque étape atteinte, journalisez les deux flux avec un horodatage, et l’échec suivant vous dira s’il est mort à la résolution du chemin, à l’authentification ou après que le tunnel était déjà monté.

Une unité a aussi besoin d’un ordre correct : Wants=network-online.target plus After=network-online.target empêchent le client de démarrer avant qu’une adresse ou une route par défaut existe, ce qui se manifeste sinon par des erreurs de certificat ou TLS qui disparaissent au redémarrage manuel.

Articles similaires