Azure Functions — VNet Integration & Private Endpoints
Outbound via VNet, Inbound via Private Endpoint
Introduction
Securing Azure Functions is critical for enterprise applications. Two primary mechanisms provide network isolation:
- VNet Integration — Enables your function app to access resources in your virtual network (outbound traffic)
- Private Endpoints — Allows other resources to access your function app through a private IP address (inbound traffic)
Together, these provide a comprehensive security model where your functions can securely communicate with on-premises resources, other Azure services, and be accessed only from within your network.
Understanding VNet Integration
How VNet Integration Works
When you enable VNet integration:
- Your function app gets a subnet delegated to it
- Outbound traffic from your function can be routed through the VNet
- You can access on-premises resources via VPN or ExpressRoute
- You can access other Azure services using private IPs
┌─────────────────────────────────────────────────────────┐
│ Your Virtual Network │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ On-Premises │ │ Azure VM │ │ SQL DB │ │
│ │ Database │◀───│ (Private) │◀───│ (Private) │ │
│ └──────────────┘ └──────────────┘ └───────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ ┌───────┴──────────────────┴──────────────────┴──────┐ │
│ │ Function App with VNet │ │
│ │ Integration Enabled │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Configuring VNet Integration
Premium Plan (Elastic Premium)
// host.json
{
"version": "2.0",
"extensions": {
"http": {
"routePrefix": "api"
}
}
}
Azure Portal Configuration
- Go to your Function App
- Select "Networking" under Settings
- Under "VNet Integration", click "Add VNet Integration"
- Select your subscription, VNet, and subnet
- The subnet must be /27 or larger and dedicated to Functions
Using ARM/Bicep
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(variables('functionAppName'), '/web')]",
"apiVersion": "2021-02-01",
"properties": {
"vnetName": "[parameters('vnetName')]",
"vnetResourceGroupName": "[parameters('vnetResourceGroupName')]",
"subnetResourceId": "[resourceId(parameters('vnetResourceGroupName'), 'Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnetName'))]"
}
}
Using Azure CLI
# Create a dedicated subnet for Functions
az network vnet subnet create \
--name function-subnet \
--resource-group my-rg \
--vnet-name my-vnet \
--address-prefixes 10.0.1.0/24
# Enable VNet integration
az functionapp vnet-integration add \
--name my-function-app \
--resource-group my-rg \
--vnet-name my-vnet \
--subnet-name function-subnet
Routing Traffic Through VNet
Configure Route All
By default, only traffic to private addresses goes through the VNet. To route ALL traffic through the VNet:
{
"name": "WEBSITE_VNET_ROUTE_ALL",
"value": "1",
"slotSetting": false
}
This is useful when:
- You need to inspect all outbound traffic
- Using network virtual appliances (NVAs)
- Accessing on-premises resources
Configuring Specific Routes
{
"name": "WEBSITE_ADD_SITENAME_BINDINGS_IN_APPHOST",
"value": "1"
}
Sticky Connections
{
"name": "WEBSITE_VNET_STICKY_DIAGNOSTICS",
"value": "true"
}
Private Endpoints for Inbound Access
Private endpoints allow your function to be accessed from within your VNet without exposing it to the public internet.
Architecture
┌────────────────────────────────────────────────────────┐
│ Your Virtual Network │
│ │
│ ┌─────────────┐ │
│ │ Client │ │
│ │ VM │──────┐ │
│ └─────────────┘ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Private Endpoint │ │
│ │ (10.0.1.20 - my-function-app.azurewebsites.net) │
│ └─────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
Creating a Private Endpoint
# Get the resource ID of your function app
FUNCTION_APP_ID=$(az functionapp show \
--name my-function-app \
--resource-group my-rg \
--query id -o tsv)
# Create private endpoint
az network private-endpoint create \
--name function-private-endpoint \
--resource-group my-rg \
--subnet-name private-subnet \
--connection-name function-connection \
--target-resource $FUNCTION_APP_ID \
--group-id sites \
--private-link-service-connection-type manual
DNS Configuration
Option 1: Private DNS Zone
# Create private DNS zone
az network private-dns zone create \
--name "privatelink.azurewebsites.net" \
--resource-group my-rg
# Link to VNet
az network private-dns vnet-link create \
--name my-dns-link \
--resource-group my-rg \
--zone-name "privatelink.azurewebsites.net" \
--virtual-network my-vnet
# Create DNS record
az network private-dns record-set a create \
--name my-function-app \
--zone-name "privatelink.azurewebsites.net" \
--resource-group my-rg
az network private-dns record-set a add-record \
--record-set-name my-function-app \
--zone-name "privatelink.azurewebsites.net" \
--resource-group my-rg \
--ipv4-address 10.0.1.20
Option 2: Custom DNS Forwarder
If you use your own DNS server:
# Add A record to your DNS
my-function-app.azurewebsites.net -> 10.0.1.20
Hybrid Connections for On-Premises Access
Azure Functions Hybrid Connections are a simpler alternative for accessing on-premises resources without full VNet integration.
When to Use Hybrid Connections
- Access a single on-premises resource
- Don't need full VNet integration
- Quick setup for development/testing
Setting Up Hybrid Connections
# Create Hybrid Connection
az functionapp hybrid-connection add \
--name my-function-app \
--resource-group my-rg \
--namespace-name my-hybrid-namespace \
--hybrid-connection-name onprem-db-connection
Configuration in host.json
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
Using Hybrid Connection in Code
[FunctionName("OnPremiseData")]
public static async Task<IActionResult> OnPremiseData(
[HttpTrigger] HttpTriggerOptions options,
ILogger log)
{
// Connect to on-premises SQL Server via Hybrid Connection
// Connection string uses the hybrid connection endpoint
var connectionString = "Server=127.0.0.1,1433;Database=OnPremDB;...";
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
// Query data
// ...
return new OkObjectResult(data);
}
Secure Access to Azure Services
Accessing Key Vault with Private Endpoint
[FunctionName("GetSecret")]
public static async Task<IActionResult> GetSecret(
[HttpTrigger] HttpTriggerOptions options,
ILogger log)
{
var keyVaultUrl = "https://my-keyvault.vault.azure.net/";
// Using Managed Identity with Key Vault private endpoint
var credential = new DefaultAzureCredential();
var secretClient = new SecretClient(new Uri(keyVaultUrl), credential);
var secret = await secretClient.GetSecretAsync("my-secret");
return new OkObjectResult(secret.Value.Value);
}
Configuring Key Vault Access
{
"name": "AZURE_CLIENT_ID",
"value": "<managed-identity-client-id>"
}
Accessing SQL Database Privately
[FunctionName("QueryDatabase")]
public static async Task<IActionResult> QueryDatabase(
[HttpTrigger] HttpTriggerOptions options,
ILogger log)
{
// Database with private endpoint
var connectionString = "Server=my-sql-server.database.windows.net;Database=MyDB;...";
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
var result = await connection.QueryAsync("SELECT * FROM Customers");
return new OkObjectResult(result);
}
Configuring Access Restrictions
Set IP Restrictions
# Allow only VNet traffic
az functionapp config access-restriction set \
--name my-function-app \
--resource-group my-rg \
--action Allow \
--rule-name "Allow VNet" \
--ip-address "VirtualNetwork"
Using Azure Portal
- Navigate to Function App > Networking
- Under "Access Restrictions", add rules
- Configure:
- Priority
- Action (Allow/Deny)
- Type (IP/Service Tag/VNet)
- Value (IP range/Service Tag/Subnet)
Configure in ARM
{
"type": "Microsoft.Web/sites/config",
"name": "[concat(variables('functionAppName'), '/web')]",
"apiVersion": "2021-02-01",
"properties": {
"ipSecurityRestrictions": [
{
"ipAddress": "VirtualNetwork",
"action": "Allow",
"priority": 100,
"name": "Allow VNet"
}
]
}
}
Network Security Groups (NSGs)
Create NSG with Rules
# Create NSG
az network nsg create \
--name function-nsg \
--resource-group my-rg
# Allow outbound to VNet
az network nsg rule create \
--name AllowVNetOutbound \
--nsg-name function-nsg \
--resource-group my-rg \
--direction Outbound \
--access Allow \
--source-address-prefix "VirtualNetwork" \
--destination-address-prefix "VirtualNetwork" \
--destination-port-range "*"
# Deny public internet outbound (if needed)
az network nsg rule create \
--name DenyInternetOutbound \
--nsg-name function-nsg \
--resource-group my-rg \
--direction Outbound \
--access Deny \
--source-address-prefix "VirtualNetwork" \
--destination-address-prefix "Internet"
Associate NSG with Subnet
az network vnet subnet update \
--name function-subnet \
--resource-group my-rg \
--vnet-name my-vnet \
--network-security-group function-nsg
Complete Security Architecture
┌────────────────────────────────────────────────────────────────┐
│ Virtual Network │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Private Endpoint │ │
│ │ (my-function-app.privatelink.azurewebsites.net) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Function App │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ VNet │ │ Managed │ │ IP │ │ │
│ │ │ Integration │ │ Identity │ │ Restrict │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Key Vault │ │ SQL DB │ │ Service │ │
│ │ (Private) │ │ (Private) │ │ Bus │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ │
│ │ On-Prem │ │ Storage │ │
│ │ (VPN) │ │ (Private) │ │
│ └────────────┘ └────────────┘ │
└────────────────────────────────────────────────────────────────┘
Troubleshooting
Common Issues
| Issue | Solution |
|---|---|
| VNet integration not working | Verify subnet is delegated to Functions |
| Can't resolve DNS | Check private DNS zone configuration |
| Private endpoint not accessible | Verify NSG rules allow traffic |
| Functions can't reach on-premises | Check VPN/ExpressRoute connectivity |
Diagnostic Commands
# Check VNet integration status
az functionapp vnet-integration show \
--name my-function-app \
--resource-group my-rg
# Test connectivity
az functionapp show \
--name my-function-app \
--resource-group my-rg \
--query "possibleOutboundIpAddresses"
Best Practices
| Practice | Description |
|---|---|
| Use dedicated subnet | Don't share with other Azure resources |
| Plan IP address space | Reserve /24 or larger subnet |
| Use private endpoints | For all Azure service access |
| Enable Managed Identity | Avoid storing secrets in code |
| Configure NSGs | Add security layer at network level |
| Use Private DNS zones | Proper DNS resolution for private endpoints |
Azure Integration Hub - Advanced Level