O serviço de VPN falha em EXEC: compare unit, binário e sudoers
Falhar no passo EXEC significa que o systemd não conseguiu iniciar o binário. Compare o caminho na unit, o caminho instalado e a permissão no sudoers antes de alterar qualquer coisa.
Ontem o túnel funcionava, e hoje de manhã a unit está morta com Failed at step EXEC spawning /usr/local/bin/vpnclient no journal. Credenciais, rotas e a conta nunca entram nessa frase. O systemd resolveu o ExecStart para um caminho, pediu ao kernel que o executasse, e o kernel recusou, quase sempre porque não existe nada nesse caminho ou porque o arquivo não é executável. Trate a falha como um problema de arquivo e argv até que o comando analisado prove o contrário.
Por que o EXEC falha antes de a autenticação começar
O systemd não executa o ExecStart através de um shell. Ele faz fork, monta um vetor de argumentos a partir da linha e chama execve no primeiro elemento. Se essa chamada falhar, o serviço nunca chega ao código próprio do cliente de VPN, então nenhuma edição de perfil nem verificação de credenciais pode ajudar.
O código de estado separa esta família das quedas comuns. Uma unit que termina com status=203/EXEC nunca iniciou um processo; uma unit que arranca e depois termina com status=1 pode ter falhado na autenticação, no DNS ou no encaminhamento. Leia o código antes do log do cliente, porque no primeiro caso o log do cliente estará vazio.
O caminho no journal é o que o systemd usou, não necessariamente o que você editou. Arquivos drop-in e a saída do gerador podem substituir o arquivo do fornecedor, e um daemon-reload desatualizado mantém a linha antiga em memória. Por isso o primeiro diagnóstico é perguntar ao systemd o que ele considera o comando, em vez de abrir o arquivo que você lembra ter alterado.
Três lugares onde o caminho do binário está escrito
Um cliente de VPN instalado a partir de um pacote de distribuição, de um script do fornecedor e de um tarball de versão pode parar em três prefixos diferentes. A unit, a regra de privilégio e o instalador registraram cada um um desses prefixos, e a falha aparece quando dois dos três não coincidem.
| Record | Common value | How to read it |
|---|---|---|
| Unit or drop-in | /usr/local/bin/vpnclient | systemctl cat vpnclient.service |
| Installed binary | /usr/bin/vpnclient | command -v vpnclient, then ls -l the result |
| Sudoers grant | /opt/vpnclient/bin/vpnclient | sudo grep -r vpnclient /etc/sudoers.d/ |
Uma divergência concreta aparece assim: o script do fornecedor instala em /usr/local/bin, o tarball foi extraído sob /opt, e a unit ainda nomeia o caminho antigo /usr/bin de um pacote anterior. Só um desses arquivos existe, e raramente é o que está no ExecStart.
Decida qual caminho é canônico antes de alterar qualquer um dos arquivos. Se o pacote de distribuição é dono do binário, o caminho do pacote é o canônico e a unit deve segui-lo. Se for o script do fornecedor, a mesma regra se aplica ao prefixo do fornecedor. Copiar um binário entre prefixos cria duas cópias que divergem na próxima atualização.
Quando um comando entre aspas se torna um nome de arquivo
Um segundo modo de falha se esconde atrás da mesma linha do journal. Alguém escreve isto na unit e espera que o cliente se conecte durante o arranque:
ExecStart=/usr/bin/"vpnclient connect office"O systemd divide o valor em palavras, então a primeira palavra se torna /usr/bin/vpnclient connect office, com as aspas removidas como sintaxe de agrupamento. Depois o kernel procura um arquivo cujo nome contenha espaços, e ele não existe. O erro é idêntico ao de um binário ausente, embora o binário esteja instalado.
A forma suportada é um executável real seguido de argumentos separados, por exemplo ExecStart=/usr/bin/vpnclient connect office. Se o fluxo de trabalho realmente precisa de um pipe ou de uma cadeia, coloque-o num script e chame o script, porque o ExecStart não tem &&, não tem pipes e não tem expansão de glob.
Corrija a unit, não o sistema de arquivos
A correção tentadora é um link simbólico do caminho esperado para o binário real. Ele funciona até o pacote ser atualizado e o link apontar para um arquivo removido, ou até um rótulo do SELinux deixar de corresponder ao alvo. Prefira editar a unit por meio de um drop-in, para que o arquivo do fornecedor permaneça intacto e as atualizações continuem previsíveis.
A regra de privilégio precisa corresponder ao mesmo caminho e aos mesmos subcomandos. Uma entrada no sudoers para /usr/local/bin/vpnclient não autoriza /usr/bin/vpnclient, e uma entrada que permite qualquer argumento deixa qualquer processo local reconfigurar ou parar o túnel. Conceda os dois verbos de que a unit precisa e mantenha a desmontagem fora dela se parar o serviço já fecha a interface.
É aqui que a comparação compensa: a unit, o banco de dados de pacotes e o sudoers devem nomear um só caminho. Quando isso acontece, o erro EXEC desaparece sem reiniciar e sem uma cópia do binário num terceiro lugar.
Prove o comando analisado antes de reiniciar
sudo systemctl daemon-reload
sudo systemctl cat vpnclient.service
systemctl show -p ExecStart vpnclient.service
systemd-analyze verify /etc/systemd/system/vpnclient.service
sudo systemctl restart vpnclient.service
systemctl status vpnclient.service --no-pagersystemctl show é a saída decisiva porque imprime o argv que o systemd realmente analisou, incluindo qualquer drop-in. systemd-analyze verify detecta um caminho ausente ou não executável antes de você reiniciar, e o comando status após o reinício diz se o processo agora existe.
Se a unit arranca e depois falha, você deixou a família EXEC e entrou no tratamento de erros do próprio cliente. Procure no journal a mensagem do cliente, não outra falha de execve. Guarde o diff da unit e o diff do sudoers juntos no registro de mudanças, para que a próxima atualização não reintroduza em silêncio o caminho dividido.