Skip to content

Azure VPN Troubles: A Hilariously Frustrating Journey into Certificate‑Based P2S on macOS

If you’ve ever tried to glue together Azure’s certificate‑based point‑to‑site VPN on macOS, you know it’s a bit like performing a summoning ritual in an unknown language while balancing on a unicycle. My client’s team discovered this the hard way when they decided to secure their Azure Functions behind a Private Endpoint. What followed was a hilariously frustrating journey filled with bizarre errors, mismatched documentation, and the ultimate conclusion: sometimes you have to laugh (and maybe open your firewall instead). Join me as I recount this chaotic adventure into the depths of Azure VPN troubleshooting!

Subtitle: How I got defeated by lunch by Azure VPN Private Gateway colluding with my mac OS

If you’ve ever tried to glue together Azure’s certificate‑based point‑to‑site VPN on macOS, you know it’s a bit like performing a summoning ritual in an unknown language while balancing on a unicycle. My client’s team discovered this the hard way when they decided that their Azure Functions should live securely behind a Private Endpoint instead of publicly on the internet. On paper, it sounded simple: configure a VPN gateway, generate some certificates, edit an .ovpn file and connect. Reality disagreed.

What follows is a 2000‑plus‑word (yes, you asked for it) blow‑by‑blow chronicle of the steps, the CLI incantations, the bizarre errors, the mismatched documentation and the ultimate conclusion: sometimes you have to laugh (and maybe open your firewall instead). Prepare yourself for wits, sarcasm and the kind of raw technical details only a network engineer could love.


The Plan: Hide an Azure Function Behind Private Link

The goal was straightforward: publish an Azure Function via Private Endpoint so that only clients on our VNet can reach it. Because we wanted to call this Function from a developer’s laptop, we needed a Point‑to‑Site (P2S) VPN. Azure P2S supports various protocols (IKEv2/IPsec, SSTP, OpenVPN) and various authentications (Azure AD, RADIUS, certificates). Our security requirements prohibited Azure AD sign‑ins from this environment and we didn’t want to set up a RADIUS server, so certificate‑based P2S via OpenVPN seemed appropriate.

In theory, the workflow looked like this:

  1. Create a resource group (RG) and virtual network (VNet) with address space 10.224.0.0/12.
  2. Pick an unused /27 subnet for the GatewaySubnet (e.g., 10.239.255.224/27).
  3. Create the VPN gateway and enable OpenVPN.
  4. Generate a self‑signed root CA and a client certificate, upload the root to the gateway.
  5. Generate an OpenVPN client package from Azure and import the profile into an OpenVPN client.
  6. Connect, and magically the FQDN yourfunction.some‑region.azurewebsites.net resolves to a 10.x.x.x Private Endpoint IP.

That was the fantasy. Now let’s look at each step and the potholes we fell into.


Step 1: Subnet Maths – Which Addresses Are Legal?

Azure VNets operate in the private IPv4 space. You choose an address space in CIDR notation (e.g., 10.224.0.0/12), then carve subnets inside. The catch: the GatewaySubnet must reside inside the VNet’s address space and cannot overlap with other subnets or the P2S address pool. We initially thought a /27 anywhere in the 10.223 range would work, only to have Azure slap us with:

(NetcfgSubnetRangeOutsideVnet) Subnet ‘GatewaySubnet’ is not valid because its IP address range is outside the IP address range of virtual network ‘aks-vnet-40883769’.

After verifying the VNet with az network vnet show and az network vnet subnet list, we learned that 10.223.254.0/27 lies completely outside 10.224.0.0/12. The fix: pick a high‑numbered block within the VNet, like 10.239.255.224/27.

Code snippet:

# Inspect the VNet address space
az network vnet show -g MC_archy-rg_aks-archy-prod-swe-001_francecentral -n aks-vnet-40883769 --query "addressSpace.addressPrefixes"
# Create GatewaySubnet in a valid /27
az network vnet subnet create -g MC_archy-rg_aks-archy-prod-swe-001_francecentral \
  --vnet-name aks-vnet-40883769 \
  -n GatewaySubnet \
  --address-prefixes 10.239.255.224/27

Sarcastic translation: Azure, you could’ve just said “Your subnet is outside the VNet address range” instead of that cryptic message.


Step 2: Deploying the VPN Gateway – Where Exactly Is the VNet?

While our core resources were in archy-rg, the VNet actually existed in Azure Kubernetes Service’s managed resource group: MC_archy-rg_aks-archy-prod-swe-001_francecentral. If you deploy an AKS cluster with a VNet, Azure sticks the VNet in this MC group. Creating resources in the wrong RG yields ResourceNotFound errors. So we set environment variables accordingly:

RG="MC_archy-rg_aks-archy-prod-swe-001_francecentral"
VNET="aks-vnet-40883769"
LOC=$(az network vnet show -g "$RG" -n "$VNET" --query location -o tsv)

GW_PIP="gwpip-$VNET"
GW="gw-$VNET"
# Create Public IP
az network public-ip create -g "$RG" -n "$GW_PIP" -l "$LOC" --sku Standard
# Create VPN gateway
az network vnet-gateway create -g "$RG" -n "$GW" \
  --vnet "$VNET" \
  --public-ip-address "$GW_PIP" \
  --gateway-type Vpn --vpn-type RouteBased --sku VpnGw1

Tip: your gateway and VNet must reside in the same region, and the public IP must be Standard SKU.


Step 3: Enabling OpenVPN and Creating Address Pools

To enable certificate‑based OpenVPN, we updated the gateway:

# P2S address pool that does not overlap with VNet
P2S_POOL="172.16.0.0/24"

# Enable OpenVPN on the gateway
az network vnet-gateway update -g "$RG" -n "$GW" \
  --address-prefixes "$P2S_POOL" \
  --client-protocol OpenVPN

We also set up environment variables for later certificate creation:

ROOT_CERT_NAME="dev-root"

You’d think that adding a –protocol OpenVPN flag when generating the client profile would be intuitive. Azure disagreed. The CLI doesn’t accept an –protocol parameter (contrary to some documentation). The command always bundles the OpenVPN profile if OpenVPN is enabled.


Step 4: Generating and Uploading Certificates – A Comedy of Commands

Root CA and Client Cert

We needed a root CA to sign our client certs. Our first attempt used OpenSSL commands like this:

# Root CA (self-signed)
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
  -subj "/CN=$ROOT_CERT_NAME" \
  -keyout rootCA.key -out rootCA.cer

# Client key and CSR
openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=client"
# Client cert signed by root
openssl x509 -req -in client.csr -CA rootCA.cer -CAkey rootCA.key -CAcreateserial \
  -out client.cer -days 825 -sha256

However, that certificate lacked an Extended Key Usage (EKU) for Client Authentication. According to Microsoft’s docs, Azure will reject client certs without extendedKeyUsage = clientAuth[1]. So we regenerated with an extra config:

cat > client-ext.cnf <<'CONF'
[req]
distinguished_name=req
[v3_client]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
keyUsage = digitalSignature
extendedKeyUsage = clientAuth
CONF

# Regenerate client cert
openssl x509 -req -in client.csr -CA rootCA.cer -CAkey rootCA.key \
  -CAcreateserial -out client.cer -days 825 -sha256 \
  -extfile client-ext.cnf -extensions v3_client

Lesson: when Azure’s logs keep resetting connections without explanation, check your client certificate’s EKU.

Exporting to PFX and PKCS #8

We needed a PFX for macOS Keychain/IKEv2 and a PKCS #8 version of the private key for OpenVPN. Commands looked like:

# Create password-protected PFX
openssl pkcs12 -export \
  -inkey client.key -in client.cer -certfile rootCA.cer \
  -out client.pfx -passout pass:'StrongPass123!'

# Convert private key to PKCS8 (unencrypted) for .ovpn
openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt \
  -in client.key -out client_pkcs8.key

Uploading Root Cert to Azure – CLI Conspiracy

Azure requires you to upload the public root certificate to the gateway before generating client packages. This seemingly simple command devolved into an odyssey:

Our first attempt used the CLI syntax from outdated docs:

az network vnet-gateway root-cert add -g "$RG" -n "$GW" \
  --name "$ROOT_CERT_NAME" --public-cert-data "$(base64 -w0 rootCA.cer)"

This returned add is misspelled or not recognized. Apparently, the parameter names changed. We tried vpn-client root-cert add, add-root-cert, create, and update –add with nested JSON. Each variant threw errors like “Too many options,” “Missing required fields,” “publicCertData not found,” or InvalidArgumentValue.

At one point we considered brute forcing the CLI, but after yet another cryptic failure we updated the Azure CLI (brew upgrade azure-cli) to the latest version. Then we discovered the correct syntax for our build:

# The CLI interprets the PEM file path when passed directly
az network vnet-gateway root-cert create \
  --resource-group "$RG" \
  --gateway-name "$GW" \
  --name "$ROOT_CERT_NAME" \
  --public-cert-data rootCA.cer

This finally succeeded. The command prints the resource details, confirming our root certificate named dev-root is uploaded.[2]

Sarcasm: The CLI documentation still shows the deprecated vpn-client root-cert add, so you’ll think you’re just misreading – not losing your mind. Always update the CLI and be prepared for parameters to change names on Tuesdays and Fridays.


Step 5: Generating the OpenVPN Client Package – Where’s My Protocol?

Generating the VPN client configuration requires another CLI call:

az network vnet-gateway vpn-client generate \
  --resource-group "$RG" --name "$GW" \
  --processor-architecture Amd64

The –processor-architecture flag supports only Amd64 or X86, even on Apple Silicon. Azure’s docs still reference an Arm64 option, but the CLI rejects it. Also, there is no longer a –protocol argument; OpenVPN and IKEv2 profiles are bundled automatically.

The command returns a URL to a ZIP (valid for one hour) that contains folders: Generic, OpenVPN, and AzureVPN. Inside OpenVPN is a file like vpnconfig.ovpn. Microsoft docs instruct you to insert your client certificate and private key into <cert> and <key> sections and not modify other fields[3]. We discovered two gotchas:

  1. The <tls-auth> static key block and key-direction 1 line are essential. Removing them results in TLS connection resets after certificate verification[1].
  2. OpenVPN 2.6 defaults to Data Channel Offload (DCO), which Azure gateways don’t support. The sample config includes #disable-dco. Uncomment it or the connection will reset just as often.

After editing, our vpnconfig.ovpn looked like this (private key elided):

client
remote azuregateway-….vpn.azure.com 443
verify-x509-name d65a0046-….vpn.azure.com name
remote-cert-tls server

disable-dco # for OpenVPN 2.6, required for Azure

proto tcp-client
resolv-retry infinite
nobind
persist-key
persist-tun

auth SHA256
cipher AES-256-GCM

<ca>
-----BEGIN CERTIFICATE-----
# contents of server-ca.crt (converted from VpnServerRoot.cer_0)
-----END CERTIFICATE-----
</ca>

<tls-auth>
-----BEGIN OpenVPN Static key V1-----
# static key from profile
-----END OpenVPN Static key V1-----
</tls-auth>
key-direction 1

<cert>
-----BEGIN CERTIFICATE-----
# contents of client.cer
-----END CERTIFICATE-----
</cert>

<key>
-----BEGIN PRIVATE KEY-----
# contents of client_pkcs8.key
-----END PRIVATE KEY-----
</key>

Note: The <ca> block must contain the gateway’s server root certificate (DigiCert Global Root G2), not your own CA. If you paste your dev root here, the server might accept itself and still drop the connection.


Step 6: The Comedy of OpenVPN Clients

Azure VPN Client for macOS – Not a Chance

After generating our vpnconfig.ovpn, we tried Azure’s own VPN client for macOS. The UI looked promising but quickly dashed our hopes. When we imported the OpenVPN profile, the client showed text boxes for “Client Certificate Public Key Data” and “Private Key.” There was no file selector; the UI expected us to copy base64 strings manually. Worse, when we attempted to do so, the client refused to connect and eventually displayed an error about certificate authentication being unsupported. A Microsoft Q&A confirmed our suspicion: Azure VPN Client on macOS does not support certificate‑based authentication[2]. It only works with Azure AD or RADIUS. So we had to try other clients.

OpenVPN Connect – Too Strict for Azure

Next we tried OpenVPN Connect. This client is picky. It refused to accept the log directive and even the pkcs12 directive. Removing those lines and using inline certificates got us further, but as soon as the handshake began, we saw cryptic errors such as:

UNKNOWN/UNSUPPORTED OPTIONS: log
Unsupported option (ignored): resolv-retry,persist-key,persist-tun

We removed the log line and separated options into their own lines (no commas!). Then we encountered:

X509::parse_pem: error in cert: error:0480006C:PEM routines:no start line

This indicated that OpenVPN Connect couldn’t parse the certificate because we accidentally left out the —–BEGIN CERTIFICATE—– header. After ensuring proper headers and footers, OpenVPN Connect still refused to handshake. We realized that OpenVPN Connect requires an inline <tls-auth> or <tls-crypt> key if key-direction is specified. When we removed the static key, the handshake proceeded but the connection reset repeatedly.

Tunnelblick – The Only Option (Sort of)

Tunnelblick is an open‑source OpenVPN client for macOS. After hours of fiddling with OpenVPN Connect, we switched to Tunnelblick out of desperation, and…it worked! Well, sort of.

We imported the .ovpn file into Tunnelblick and clicked connect. The logs showed a successful TLS handshake:

VERIFY OK: depth=0, CN=d65a0046-61e4-4cde-9ec5-e4becb37acca.vpn.azure.com
...
Control Channel: TLSv1.3, cipher TLS_AES_256_GCM_SHA384
Peer Connection Initiated
...
SENT CONTROL [d65a0046-...]: 'PUSH_REQUEST'
PUSH: Received control message: 'PUSH_REPLY,route 10.224.0.0 255.240.0.0,route-gateway 172.5.0.1,topology subnet,ifconfig 172.5.0.2 255.255.0.0,cipher AES-256-GCM'
Initialization Sequence Completed

We were connected, assigned IP 172.5.0.2 and given a route to 10.224.0.0/12 via 172.5.0.1. However, the logs also contained warnings:

*Tunnelblick: Warning: DNS server address 168.63.129.16 is not a public DNS server known to Tunnelblick and is not being routed through the VPN.

Translation: OpenVPN didn’t push a DNS server. Tunnelblick defaulted to our home router for DNS, which resolves privatelink.azurewebsites.net records to a public IP. We added dhcp-option DNS 168.63.129.16 in the config to direct DNS queries to Azure, then discovered that Azure DNS isn’t reachable by default—there is no route for 168.63.129.16. The fix: add a static route:

# Force route to Azure’s DNS via the VPN gateway
route 168.63.129.16 255.255.255.255 vpn_gateway

After reconnecting, nslookup yourfunction.privatelink.azurewebsites.net returned the private IP (e.g., 10.224.1.169). Success!

The Final Straw

Although we eventually got Tunnelblick to connect, we spent a full day generating certs, regenerating certs, editing .ovpn files, converting DER to PEM, disabling DCO, adding static DNS routes… it was, to quote Monty Python, “a complete waste of time.” In the end, our team decided that keeping a public Function endpoint with proper authentication (e.g., function keys or AAD tokens) was far simpler. The Private Endpoint required too much manual glue and produced little gain.


Lessons Learned (with Sarcasm)

  1. Azure CLI documentation is part history lesson. The docs show parameters that no longer exist or behave differently depending on CLI versions. Always run az –version and update via brew upgrade (on macOS) if commands fail.
  2. Check your VNet’s actual address space before carving out subnets. A simple –query “addressSpace.addressPrefixes” would have prevented the initial confusion.
  3. Don’t trust Azure’s own VPN client on macOS for certificate auth. It simply doesn’t support it[2].
  4. OpenVPN Connect is picky about config syntax and unsupported directives. If you must use it, embed the cert/key inline, remove any unknown directives and ensure <tls-auth> is included.
  5. Tunnelblick is the most tolerant but still requires disable-dco, a DNS route for 168.63.129.16, and proper <tls-auth>.
  6. The gateway’s server CA matters. The <ca> block must contain the Azure‑issued server root (DigiCert Global Root G2) or the handshake will fail silently.
  7. macOS Keychain with IKEv2 is an alternative. Import your PFX into Keychain and configure a built‑in IKEv2 connection to the gateway. It works better and doesn’t require editing .ovpn files. But our environment forbade IKEv2.
  8. Sometimes it’s not worth it. After spending hours debugging, our team concluded that a public endpoint with proper authentication was less painful and offered similar security with simpler maintenance.

It’s Not You, It’s Azure (Maybe)

Implementing Azure certificate‑based P2S VPN on macOS is not for the faint of heart. You will battle shifting CLI syntax, cryptic error messages, certificate EKUs, unsupported macOS clients, OpenVPN config quirks and DNS route madness. You will question your sanity when –protocol OpenVPN doesn’t exist, and you’ll learn far more about TLS static keys than you ever wanted.

In the end, we achieved our objective for about 30 seconds: a working tunnel, proper DNS resolution and connectivity to our Private Endpoint. Then we realized that maintenance overhead and complexity outweighed the benefits. We opened the function to the public internet with authentication tokens and regained our sanity.

If you still insist on following this path, remember: don’t edit fields outside the <cert> and <key> blocks; ensure your client cert has clientAuth EKU; disable DCO; include <tls-auth>; and route DNS properly. And keep a bottle of your favorite beverage handy.

That’s our tale of Azure VPN heartbreak. May your next networking project bring you more joy and fewer CLI errors!


[1] Configure P2S VPN clients: certificate authentication: OpenVPN Client 3.x series – Windows – Azure Virtual WAN | Microsoft Learn

https://learn.microsoft.com/en-us/azure/virtual-wan/point-to-site-vpn-client-certificate-windows-openvpn-client-version-3

Azure VPN Troubles: A Hilariously Frustrating Journey into Certificate‑Based P2S on macOS

If you’ve ever tried to glue together Azure’s certificate‑based point‑to‑site VPN on macOS, you know it’s a bit like performing a summoning ritual in an unknown language while balancing on a unicycle. My client’s team discovered this the hard way when they decided to secure their Azure Functions behind a Private Endpoint. What followed was a hilariously frustrating journey filled with bizarre errors, mismatched documentation, and the ultimate conclusion: sometimes you have to laugh (and maybe open your firewall instead). Join me as I recount this chaotic adventure into the depths of Azure VPN troubleshooting!

Read More »

Managing Complexity: How AI Tools Give Enterprise Architects a Clearer Map

In today’s hyper-connected business world, enterprise systems often resemble an impenetrable spaghetti diagram. This article explores how AI tools are revolutionizing the way enterprise architects, IT portfolio managers, and CTOs understand, map, and optimize their complex IT environments, moving from manual, error-prone processes to automated, insightful clarity. Discover the tangible benefits, from enhanced visibility and cost savings to improved agility, while also acknowledging the practical challenges and considerations for successful AI adoption in enterprise architecture.

Read More »
Architecture
Nenad

Azure VPN Troubles: A Hilariously Frustrating Journey into Certificate‑Based P2S on macOS

If you’ve ever tried to glue together Azure’s certificate‑based point‑to‑site VPN on macOS, you know it’s a bit like performing a summoning ritual in an unknown language while balancing on a unicycle. My client’s team discovered this the hard way when they decided to secure their Azure Functions behind a Private Endpoint. What followed was a hilariously frustrating journey filled with bizarre errors, mismatched documentation, and the ultimate conclusion: sometimes you have to laugh (and maybe open your firewall instead). Join me as I recount this chaotic adventure into the depths of Azure VPN troubleshooting!

Read More »
AI
Nenad Crnčec

Managing Complexity: How AI Tools Give Enterprise Architects a Clearer Map

In today’s hyper-connected business world, enterprise systems often resemble an impenetrable spaghetti diagram. This article explores how AI tools are revolutionizing the way enterprise architects, IT portfolio managers, and CTOs understand, map, and optimize their complex IT environments, moving from manual, error-prone processes to automated, insightful clarity. Discover the tangible benefits, from enhanced visibility and cost savings to improved agility, while also acknowledging the practical challenges and considerations for successful AI adoption in enterprise architecture.

Read More »