Одна и та же команда подключается из оболочки и умирает под cron, поэтому естественный вывод — виноват туннель или сертификат. Прежде чем в это поверить, посмотрите, что общего у двух запусков: бинарник, конфигурация и учётные данные одинаковы, а изменилось только окружение процесса. Когда программу даже не находят, никакая отладка TLS не помогает. Исправление обычно лежит в пути, рабочем каталоге и горсти переменных окружения, а не в VPN.

command -v openvpn в работающей оболочке печатает ответ, и очень часто это /usr/sbin/openvpn, каталог, который cron не добавляет в свой путь поиска.

Туннель так и не запустился, значит проблема не в туннеле

Задания, запущенные cron, наследуют минимальное окружение, обычно PATH=/usr/bin:/bin, и не получают управляющего терминала. Всё, что установлено в /usr/sbin или /usr/local/sbin, просто отсутствует в этом списке, и строка журнала гласит openvpn: not found вместо ошибки TLS. Python-овский subprocess падает иначе: он наследует окружение процесса интерпретатора, которое может быть богаче, чем у cron, но по-прежнему не имеет оболочки, чтобы развернуть тильду. Оба сбоя случаются до того, как хоть один пакет покинет хост.

Быстрый способ их различить — читать первую ошибку, а не последнюю. Отсутствующий бинарник даёт исключение оболочки или Python, плохая конфигурация даёт сообщение об использовании OpenVPN, а настоящий сбой рукопожатия даёт метки времени и адреса партнёров. Только третий — проблема туннеля.

Абсолютные пути и списки аргументов побеждают строки оболочки

Передайте разрешённый путь явно и полностью пропустите оболочку. subprocess.run(["/usr/sbin/openvpn", "--config", config, "--verb", "4"], capture_output=True, text=True, timeout=90) даёт код возврата, stdout и stderr, не прося оболочку интерпретировать имя файла. Стройте путь к конфигурации из os.environ["HOME"] или pathlib.Path.home(), чтобы тильду никогда не приходилось разворачивать. Когда действительно нужен синтаксис оболочки, используйте shlex.split, потому что разбиение строки через .split() ломается на любом пути с пробелом. Читайте и stderr, поскольку OpenVPN пишет туда большинство своих ранних сбоев.

То же правило касается cron: строка задания — не входная оболочка, поэтому ничто не загружает ваш профиль и ничто не определяет ваши псевдонимы.

Что на самом деле наследует crontab

Запись в crontab получает окружение, которое объявляет crontab, плюс несколько унаследованных значений по умолчанию, поэтому пропишите путь целиком или оберните вызов в скрипт, который это делает. Строка вроде PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin в начале файла покрывает обычные места установки, и перенаправление через >> cron.log 2>&1 — это то, что делает следующий сбой читаемым. Профиль, защищённый парольной фразой, под cron не может спросить свой секрет, поскольку терминала для запроса нет. Такое соединение принадлежит модулю systemd, который умеет читать файл учётных данных, а не заданию, которое притворяется интерактивным.

Относительные пути — вторая ловушка. Тестовый запуск часто происходит из папки с профилем, а задание cron стартует совсем в другом месте, поэтому --config office.ovpn разрешается в файл, которого там нет, и сообщение читается как отсутствующий файл, а не отсутствующий туннель.

Докажите это, сняв окружение

Воспроизведите минимальное окружение напрямую: env -i PATH=/usr/bin:/bin /usr/sbin/openvpn --config office.ovpn --verb 4. Жалоба на путь или терминал из этого запуска означает, что проблема выше VPN. Если он подключается, оставшиеся подозреваемые — это учётная запись, под которой он работает, каталог, в котором он стартует, и учётные данные, которые он предъявляет. Любой из двух результатов лучше очередного круга проверки сертификатов.

Затем сделайте обёртку самоотчитывающейся. Выходите с отдельным статусом для каждого достигнутого этапа, записывайте оба потока с меткой времени, и следующий сбой скажет вам, умер ли он на разрешении пути, на аутентификации или после того, как туннель уже был поднят.

Модулю нужен и правильный порядок: Wants=network-online.target вместе с After=network-online.target не дают клиенту стартовать раньше, чем появится адрес или маршрут по умолчанию, что иначе проявляется как ошибки сертификата или TLS, исчезающие при ручном перезапуске.

Похожие статьи