Skip to content

Setting Up a Private Endpoint for Azure OpenAI

10 min read · updated August 11, 2026

A private endpoint is two mechanisms wearing one name: a network interface with a private IP inside your virtual network, and a DNS override that makes the public hostname resolve to it. Almost every failure is the second one.

The prerequisite nobody mentions

The resource needs a custom subdomain. An Azure AI services resource created without --custom-subdomain-name answers on a shared regional endpoint such as https://westeurope.api.cognitive.microsoft.com, and a hostname shared with every other customer in the region cannot be pointed at your private IP. Microsoft documents custom subdomains as required to enable features including Microsoft Entra ID authentication, and the same uniqueness is what makes private DNS possible.

Microsoft is also explicit that a custom subdomain cannot be changed once created, and that reusing a name means deleting the resource holding it. If you are retrofitting private networking onto an existing resource that never had one, the Azure portal offers a Generate Custom Domain Name action on the resource’s Overview pane — the one place in this page where the console is genuinely the path, and the volatile part of it.

Create the private endpoint

The endpoint is an ARM resource in your virtual network’s subscription, connected to the target resource by ID and by group ID — the sub-resource being exposed. For Cognitive Services accounts that group ID is account.

az network private-endpoint create \
  --name pe-openai-weu \
  --resource-group rg-network \
  --vnet-name vnet-app \
  --subnet snet-privatelink \
  --private-connection-resource-id $(az cognitiveservices account show \
      --name mg-openai-weu --resource-group rg-inference --query id -o tsv) \
  --group-id account \
  --connection-name conn-openai-weu

The subnet needs private endpoint network policies to permit it; on older subnets this is the privateEndpointNetworkPolicies property, and a subnet created before that default changed will reject the endpoint with an error about network policies rather than about anything to do with OpenAI.

The DNS chain, and what to actually check

This is where the mental model has to be right. Your application resolves mg-openai-weu.openai.azure.com. Azure’s public DNS answers that with a CNAME to mg-openai-weu.privatelink.openai.azure.com. What happens next depends entirely on whether the resolver knows about a private DNS zone for privatelink.openai.azure.com:

  • Inside a virtual network linked to that private zone — the zone holds an A record created when the private endpoint was created, and the name resolves to the private IP.
  • Anywhere else — the CNAME resolves onward to the public front-end address, and traffic goes over the internet.

Microsoft’s guidance is explicit that the *.privatelink.openai.azure.com name is an intermediary in that CNAME chain and should not be called directly. Doing so is the classic false positive: the certificate is issued for the public name, so a direct call to the privatelink name fails TLS validation and looks like a broken private endpoint when the setup is fine.

The check that means something is a resolution of the public hostname from a machine inside the virtual network, confirming it lands on an RFC 1918 address:

nslookup mg-openai-weu.openai.azure.com

# Expected, from inside the linked VNet:
#   mg-openai-weu.openai.azure.com
#     canonical name = mg-openai-weu.privatelink.openai.azure.com
#   Address: 10.20.4.7

A public IP in that answer means the private DNS zone is not linked to the virtual network the query came from, which is a separate object from the endpoint itself and the most common thing left undone.

Disable public network access

Creating a private endpoint does not close the public door; it opens a second one. Until public network access is disabled, the resource is still reachable with a key from anywhere on the internet, and every client that has not been moved onto the virtual network keeps working — which is precisely why nobody notices.

az resource update \
  --ids $(az cognitiveservices account show \
      --name mg-openai-weu --resource-group rg-inference --query id -o tsv) \
  --set properties.publicNetworkAccess=Disabled

Once this is set, the service rejects connections at the front door regardless of how the name resolved. That is the useful failure mode: a client with stale DNS gets a connection refusal rather than a silently-public path. Flip this in a maintenance window, because anything still calling from outside — a CI runner, a developer laptop, a monitoring probe — stops immediately.

Disabling public network access also cuts off the Foundry portal’s playground and any “test in browser” experience running from outside the network. Plan for a jump host or a VPN before you make the change, not after somebody needs to debug a prompt.

What it does not cover

A private endpoint secures one direction of one resource, and a system built around Azure OpenAI has more paths than that. Four are routinely missed, and each is a separate piece of work rather than a setting on the endpoint you just created.

  • It is not authentication. Anyone who reaches the private IP and holds a key gets a completion. Network isolation and credential isolation are orthogonal, and a private endpoint with a shared key checked into a repository is one control, not two. This is the argument for pairing it with a managed identity rather than treating either as sufficient.
  • Outbound calls are a different problem. When the service retrieves from an Azure AI Search index or reads a fine-tuning file from Blob Storage, that traffic originates at the service and goes to your other resources. Locking those down means their own private endpoints plus an identity or trusted-service path for the OpenAI resource to reach them. A storage account with public access disabled will break an ingestion job that worked yesterday.
  • Every resource needs its own. Private endpoints are per-resource, not per-subscription. A second Azure OpenAI account in a second region for failover is a second endpoint, a second DNS A record, and a second thing to forget when someone adds region three.
  • The management plane stays public. Creating deployments, reading quota and changing configuration all go through management.azure.com, which is unaffected by any of this. Restricting that is Azure RBAC and Azure Policy, not networking.

Resolving from outside the virtual network

On-premises networks and custom DNS servers do not see Azure private DNS zones. Microsoft documents three ways to bridge that, and the choice is architectural rather than technical:

  1. Conditional-forward the privatelink.openai.azure.com zone from your DNS servers to an Azure DNS resolver inside the virtual network. This is the option that keeps working when endpoints are added.
  2. Host the privatelink.openai.azure.com zone on your own DNS servers and add A records manually. Correct, and stale the moment somebody recreates a private endpoint and it takes a new IP.
  3. Delegate the privatelink subdomain to the virtual network’s private zone.

Whichever you pick, the same verification applies: resolve the public hostname and confirm a private address. And do it from every network that calls the endpoint, because a private endpoint that works from the application subnet and not from the batch subnet is a routine outcome of linking the zone to one virtual network and forgetting the peer. Once the network path is closed, the natural next step is closing the credential path too — swapping the API key for a managed identity.