OpenVPN läuft in der Shell, scheitert unter cron: es ist der PATH
Ein Befehl, der aus einer Shell verbindet, kann unter cron oder subprocess scheitern, bevor der Tunnel startet. Korrigieren Sie den Pfad, das Arbeitsverzeichnis und das Terminal.
Derselbe Befehl verbindet aus einer Shell und stirbt unter cron, also liegt die natürliche Schlussfolgerung nahe, dass der Tunnel oder das Zertifikat schuld ist. Schauen Sie sich an, was die beiden Läufe gemeinsam haben, bevor Sie das glauben: Binärdatei, Konfiguration und Zugangsdaten sind identisch, und nur die Prozessumgebung hat sich geändert. Wenn das Programm nicht einmal gefunden wird, hilft kein noch so großes TLS-Debugging. Die Lösung liegt meist in einem Pfad, einem Arbeitsverzeichnis und einer Handvoll Umgebungsvariablen, nicht im VPN.
command -v openvpn in der Shell, die funktioniert, gibt die Antwort aus, und sehr oft ist es /usr/sbin/openvpn, ein Verzeichnis, das cron nicht in seinen Suchpfad aufnimmt.
Der Tunnel startete nie, also ist der Tunnel nicht das Problem
Von cron gestartete Jobs erben eine minimale Umgebung, üblicherweise PATH=/usr/bin:/bin, und kein steuerndes Terminal. Alles, was in /usr/sbin oder /usr/local/sbin installiert ist, fehlt in dieser Liste schlicht, und die Logzeile lautet openvpn: not found statt eines TLS-Fehlers. Pythons subprocess scheitert anders: Es erbt die Umgebung des Interpreterprozesses, die reicher als die von cron sein kann, aber immer noch keine Shell hat, um eine Tilde zu expandieren. Beide Fehler treten auf, bevor ein einziges Paket den Host verlässt.
Ein schneller Weg, sie zu unterscheiden, ist, den ersten Fehler statt des letzten zu lesen. Eine fehlende Binärdatei erzeugt eine Shell- oder Python-Ausnahme, eine schlechte Konfiguration erzeugt eine OpenVPN-Nutzungsmeldung, und ein echter Handshake-Fehler erzeugt Zeitstempel und Peer-Adressen. Nur der dritte ist ein Tunnelproblem.
Absolute Pfade und Argumentlisten schlagen Shell-Strings
Übergeben Sie den aufgelösten Pfad explizit und überspringen Sie die Shell ganz. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) liefert Rückgabecode, stdout und stderr, ohne eine Shell zu bitten, einen Dateinamen zu interpretieren. Bauen Sie den Konfigurationspfad aus os.environ["HOME"] oder pathlib.Path.home(), damit eine Tilde nie expandiert werden muss. Wenn Sie wirklich Shell-Syntax brauchen, verwenden Sie shlex.split, denn das Aufteilen einer Zeichenkette mit .split() bricht bei jedem Pfad mit Leerzeichen. Lesen Sie auch stderr, denn OpenVPN schreibt die meisten seiner frühen Fehler dorthin.
Dieselbe Regel gilt für cron: Die Jobzeile ist keine Login-Shell, also lädt nichts Ihr Profil und nichts definiert Ihre Aliase.
Was ein crontab tatsächlich erbt
Ein crontab-Eintrag bekommt die Umgebung, die der crontab deklariert, plus einige geerbte Vorgaben, also schreiben Sie den Pfad aus oder wickeln Sie den Aufruf in ein Skript, das es tut. Eine Zeile wie PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin am Anfang der Datei deckt die üblichen Installationsorte ab, und die Umleitung mit >> cron.log 2>&1 ist es, die den nächsten Fehler lesbar macht. Ein passphrasengeschütztes Profil hat unter cron keine Möglichkeit, sein Geheimnis abzufragen, da kein Terminal zum Nachfragen existiert. Eine solche Verbindung gehört in eine systemd-Unit, die eine Datei mit Zugangsdaten lesen kann, nicht in einen Job, der sich interaktiv gibt.
Relative Pfade sind die zweite Falle. Ein Testlauf findet oft aus dem Ordner statt, der das Profil enthält, und ein cron-Job startet ganz woanders, also wird --config office.ovpn zu einer Datei, die nicht da ist, und die Meldung liest sich als fehlende Datei statt als fehlender Tunnel.
Beweisen Sie es, indem Sie die Umgebung abziehen
Reproduzieren Sie die minimale Umgebung direkt: env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. Eine Pfad- oder Terminalbeschwerde aus diesem Lauf bedeutet, dass das Problem oberhalb des VPN sitzt. Wenn es verbindet, sind die verbleibenden Verdächtigen das Konto, unter dem es läuft, das Verzeichnis, in dem es startet, und die Zugangsdaten, die es vorlegt. Beide Ergebnisse sind besser als eine weitere Runde Zertifikatsprüfung.
Machen Sie dann den Wrapper selbstberichtend. Beenden Sie mit einem eigenen Status für jede erreichte Stufe, protokollieren Sie beide Ströme mit Zeitstempel, und der nächste Fehler sagt Ihnen, ob er bei der Pfadauflösung, bei der Authentifizierung oder erst nach dem Hochfahren des Tunnels gestorben ist.
Eine Unit braucht außerdem die richtige Reihenfolge: Wants=network-online.target plus After=network-online.target halten den Client davon ab, zu starten, bevor eine Adresse oder Standardroute existiert, was sich sonst als Zertifikats- oder TLS-Fehler zeigt, die bei einem manuellen Neustart verschwinden.