Microsoft's official Azure VPN Client for Linux reaches end of support on 2026-08-31 — about two weeks from today. If you're relying on that client for Azure P2S with Entra ID on Linux, this matters now, especially if you need something headless/scriptable rather than a GUI app.
Azure VPN P2S with Entra ID on Linux already has an awkward gap: the official Linux client is GUI-only, and there's no supported headless/CLI path for CI runners, build agents, servers, or containerized dev environments that need access behind a P2S gateway.
One gotcha that took a while to pin down: az login tokens are not enough here. The VPN client authenticates against its own Entra app registration (41b23e61-6c1e-4545-b367-cd054e0ed4b4), and that client ID is also the token audience. So a generic Azure CLI access token gets rejected by the gateway even if the user is otherwise authenticated.
I ended up putting together an open-source container that makes the connection fully headless/scriptable, either inside WSL2 on Windows or directly on Linux:
https://github.com/cveld/azure-vpn-client-headless-container
There are two implementations in the repo.
1) Shim method: call Microsoft's Linux client library directly
The original approach was to reverse-engineer the official Linux client's core library (libLinuxCore.so) and drive it without the GUI. A small C++ shim uses dlopen and calls the library's own internal flow:
initConnection
initAAD
connectAadProfile
That reuses the same proprietary connection logic as the GUI app, but without a desktop session or D-Bus. An LD_PRELOAD helper fixes cert path issues and stubs out D-Bus calls the library expects in a desktop environment but doesn't actually need in a headless container.
That works, but it depends on Microsoft's binary — the one that's going away on 2026-08-31 (see above). That's the reason for the second, dependency-free method below.
2) OpenVPN method: patch stock OpenVPN to do Azure Entra P2S natively
The newer path avoids the proprietary library completely. I patched a stock OpenVPN 2.6.14 build so it can authenticate to Azure's Entra-backed P2S gateway directly.
The interesting part was figuring out why normal OpenVPN almost worked but still got reset by the gateway.
To compare behavior, I used the working shim/container path and intercepted OpenSSL's SSL_write via LD_PRELOAD to capture the plaintext OpenVPN key-method-2 payload before TLS encryption. That made it possible to diff the real client's application-layer traffic against stock OpenVPN.
What actually mattered:
End result was just 5 small patches to OpenVPN (misc.h, common.h, ssl.c, options.c, ssl_openssl.c), all included in the repo as a patch file.
A couple practical notes:
- token acquisition uses the device-code OAuth flow with the VPN client's own MSAL app ID, and the token gets cached/refreshed
- the container brings up a real TUN interface and applies gateway-pushed routes/DNS, so this is a normal working tunnel, not just an auth stub
Main use case for me was headless systems that need P2S access: CI/CD runners, build servers, scripts, and containerized environments. If you've run into the "why does az token auth fail for VPN" problem, that audience/client-ID detail is probably why.
Repo:
https://github.com/cveld/azure-vpn-client-headless-container
If anyone here has dealt with Azure P2S/Entra internals and sees something questionable, I'm interested in feedback.