Privatize App Service with VNET integration and private endpoint - Glennverdale/cote-d-Azure GitHub Wiki
Privatize: to make private
Especially, to change from public to private control or ownership
This page will outline the services and features Azure offers for making an app service privately accessible only. The outline focusses on a templated approach using Azure resource manager (ARM) Templates for deploying a new (private) App Service or converting an existing (public) app service.
🔗 Template: App service with VNET integration and private endpoint
View this section
Privatization is done to increase overall security of the solution the App Service is hosting. It decreases or removes network traffic bound to the app service from traversing the public Internet. This is especially desired when the app service is hosting an application that only needs to be accessed from a private network only.
While there are ways of securing the public endpoint of an app service by means of access restriction rules; if there is no need for public (inbound) access, you are better off to close down the public endpoint entirely.
Azure has a several services and features to privatize app service network traffic. We will focus on VNET integration and private endpoints services (using private link services).
A brief look into VNET integration, how its works and what requirements it has.
View this section
The App Service VNET integration feature enables your apps to access resources in or through a virtual network. Virtual network integration doesn't enable your app to be accessed privately. ...Regional virtual network integration supports connecting to a virtual network in the same region and doesn't require a gateway.[1]
As above quote states, implementing the VNET integration feature on your app service only enables your app to communicate with resources in or through a virtual network. "Through a virtual network" basically means that the app service traffic coming into the virtual network can be forwarded to any other network as long as access control lists (ACL) and routing permits it. The Microsoft documentstion lists a great del of possible scenarios.
When enabling VNET integration you can control network traffic coming from your app through Azure route tables and network security groups (NSG).
- NSG is used to control network access via outbound security rules
- Azure route tables are used to reroute traffic to any other network connected to the virtual network the app service is integrating with, connection could be done by means of virtual network peering, virtual network gateways or Network Virtual Appliances (NVA).
- App service will use same DNS set on VNET level by default
NVAs (e.g. firewall appliances) can do both access control and routing, they are commonly placed in a hub virtual network. Azure route tables are then used to route traffic from spokes towards the NVA.
In short; VNET integration allows traffic from your app service to reach resources in your virtual network, one way only. session can only be initiated by the app service.
Below diagram indicates the traffic flow direction VNET integration supports (blue line marked by 1).

There are some requirements when implementing regional VNET integration;
- App service plan of tier Standard or higher
- App service and virtual network need to reside in the same Azure region.
- A dedicated integration subnet is needed within the virtual network with minimum CIDR of /27
- Use larger ranges when doing multiple integrations on the same subnet.
- The integration subnet is delegated to Microsoft.Web/serverFarms
- No resources other than app service plans can make use of the subnet.
- Subnet has route tables and access control (NSG/NVA) set so that it can succesfully reach resources within the virtual network.
- Consult a network engineer.
VNET integration comes with a property called VNET route all that can be enabled or disabled.
- With VNET route all disabled, the app service will only route RFC1918 addresses towards the virtual network and automatically route public adresses to the Internet. In most cases, this is the wanted scenario.
- With VNET route all enabled the app service will simply route all traffic towards the virtual network. This behaviour is wanted if you also want to control public traffic via route tables and NSGs. You can have the traffic rerouted via an NVA to inspect it.
❕ You'll also want to enable VNET route all if the private network is not using RFC1918 addresses. In this case you'll need to have "public" traffic routed towards your virtual network instead of having it automatically route to the Internet.
A brief look into private endpoints, how they work and what requirements they have.
View this section
You can use Private Endpoint for your Azure App service to allow clients located in your private network to securely access the app over Private Link. The Private Endpoint uses an IP address from your Azure VNET address space. Network traffic between a client on your private network and the Web App traverses over the VNET and a Private Link on the Microsoft backbone network, eliminating exposure from the public Internet.[2]
In short; a network interface (NIC) is created in a subnet you define with a dynamic IP address (first available IP is taken!). After succesfull creation, the NIC is linked to your app service using the Private Link service. Private Link provides private connectivity from your virtual network to Azure PaaS services. You can reach the private endpoint from any network as long as access control and routing allows it. Private endpoints allow traffic from resources in and behind the virtual network to reach the app service privately, one way. Session can only be initiated from resources inside or behind the virtual network. This is the exact opposite as VNET integration provides.
Below diagram indicates the traffic flow direction private endpoints supports (green line marked by 2).

There are some requirements when implementing private endpoints;
- App service plan of tier Premium or higher
- An App service environment is private by default, this doesn't need private endpoints, App service plans do.
- Ready the DNS infrastructure to resolve the correct privatelink forward lookup zone.
- For app services this is privatelink.azurewebsites.net and scm.privatelink.azurewebsites.net
- Private endpoint NIC needs a dynamic IP address
- The private endpoint is deployed to a subnet, the first available IP in teh subnet range will be taken
- When deploying via ARM, if you designed the IPs to be used in a certain order, deploy the resources in the same order (you might even want to use the DependsOn property in ARM.
- Subnet where the private endpoint resides has the network poliy for private endpoints disabled[3]
- Network has routes and access control that allows the correct clients or resources to reach the private endpoint IP.
As said earlier, private endpoints heavily rely on (Azure) DNS. The moment you enable a private endpoint on a supporting Azure service, Azure DNS will do some changes in the background. Lets take the example of private endpoints for App Services;
- A default app service is available on myAwsomeApp.azurewebsites.net
- Azure hosts the authoritive DNS servers for the public zone azurewebsites.net, it holds an A-record for your app service;
| Record name | Type | address |
|---|---|---|
| myAwsomeApp | A | 80.5.9.8 |
- When a private endpoint is created Azure DNS changes the A-record to a CNAME-record in the azurewebsites.net* zone that will point to a new zone privatelink.azurewebsites.net;
| Record name | Type | address |
|---|---|---|
| myAwsomeApp.azurewebsites.net | CNAME | myAwsomeApp.privatelink.azurewebsites.net |
| myAwsomeApp.privatelink.azurewebsites.net | A | 80.5.9.8 |
- The CNAME record will trigger a second lookup to myAwsomeApp.privatelink.azurewebsites.net, you can create this zone in your own private DNS (e.g. via Azure private DNS zones or Active Directory DNS) and set the same A-record for myAwsomeApp.privatelink.azurewebsites.net to the private endpoint IP in your network.
| Record name | Type | address |
|---|---|---|
| myAwsomeApp.privatelink.azurewebsites.net | A | 192.168.10.5 |
- The second lookup to myAwsomeApp.privatelink.azurewebsites.net will now be resolved by your own DNS with your own IP adresses.
- If you don't have the zone in your private DNS the second lookup to AwsomeApp.privatelink.azurewebsites.net will be resolved by Azure DNS and return you the public endpoint IP of the app service;
| Record name | Type | address |
|---|---|---|
| myAwsomeApp.azurewebsites.net | CNAME | myAwsomeApp.privatelink.azurewebsites.net |
| myAwsomeApp.privatelink.azurewebsites.net | A | 80.5.9.8 |
❕ The DNS lookup will return a public IP address and you will not be routed to your private endpoint. You will not be routed privately but publicly (this might be unintentionally or not knowingly)!
❕ When you are routed publicly and your app service public endpoint is disabled, you will get a 404 error. This can be an indicator of the DNS issue. It also brings forward the best practice of dosabling the app service public endpoint when configuring it with a private endpoint.
Below forward lookup zones need to be created (to catch teh CNAME lookup) when deploying a private endpoint on an app service;
| zone | purpose |
|---|---|
| privatelink.azurewebsites.net | Access your app service privately |
| scm.privatelink.azurewebsites.net | Access the Kudu console privately as it will be closed-off |
This section will outline how to deploy an app service via ARM.
First, you'll want to deploy an app service plan (ASP)
View this section
Deploying an app service plan is pretty straight forward, it serves as the hardware on which you'll host app services. Depending on the App service plan tier, you will be able to use VNET integration (starting from standard) and private endpoints (starting from premium).
Next, you'll want to deploy your app service with VNET integration and private endpoint
View this section
View code
"properties": {
"serverFarmId": "[concat('/subscriptions/', subscription().subscriptionId,'/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
}
View code
"properties": {
"virtualNetworkSubnetId": "[resourceId(parameters('vnetResourceGroupName'),'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('vnetIntegrationSubnetName'))]"
}
View code
{
"type": "Microsoft.Network/privateEndpoints",
"apiVersion": "2019-04-01",
"name": "[parameters('peName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('appName'))]"
],
"properties": {
"subnet": {
"id": "[resourceId(parameters('vnetResourceGroupName'),'Microsoft.Network/virtualNetworks/subnets', parameters('vnetname'), parameters('peSubnetName'))]"
},
"privateLinkServiceConnections": [
{
"name": "[parameters('privLinkServiceName')]",
"properties": {
"privateLinkServiceId": "[resourceId('Microsoft.Web/sites', parameters('appName'))]",
"groupIds": [
"sites"
]
}
}
]
}
}
❕ Re-deploying an app service with no app settings defined in your ARM will remove all app settings from the app service (e.g. if your app settings were set via the portal or PowerShell).
Even with an incremental deploy, Azure only knows how to increment on a resource level, not so much on a configuration level. As a work-around, you'll simply include all app settings in json format as a parameter array.
View this section
Below code snippit is defined in your appservice.json
View code
"properties": {
"siteConfig": {
"appSettings": "[parameters('appProperties')]",
}
Below code snippit is defined in your appservice.json
View code
"appProperties": {
"type": "Array",
"metadata": {
"description": "Application settings."
}
}
Below code snippit is defined in your appservice.parameters.json
View code
"appProperties": {
"value": [
{
"name": "AppSettings:Logging:LogLevel",
"value": "Information",
"slotSetting": false
},
{
"name": "WEBSITE_RUN_FROM_PACKAGE",
"value": "0",
"slotSetting": false
}
]
}