Skip to content

content_sources: - type: mslearn-adapted url: https://learn.microsoft.com/azure/azure-functions/functions-networking-options - type: mslearn-adapted url: https://learn.microsoft.com/azure/azure-functions/functions-create-vnet - type: mslearn-adapted url: https://learn.microsoft.com/azure/app-service/overview-vnet-integration diagrams: - id: private-egress-architecture type: flowchart source: self-generated justification: "VNet integration pattern from MSLearn networking documentation" based_on: - https://learn.microsoft.com/azure/azure-functions/functions-create-vnet content_validation: status: verified last_reviewed: 2026-04-12 reviewer: agent core_claims: - claim: "Consumption plan does not support VNet integration for Azure Functions" source: https://learn.microsoft.com/azure/azure-functions/functions-networking-options verified: true - claim: "Flex Consumption requires Microsoft.App/environments subnet delegation for VNet integration" source: https://learn.microsoft.com/azure/azure-functions/functions-create-vnet verified: true - claim: "Premium and Dedicated plans use Microsoft.Web/serverFarms subnet delegation for VNet integration" source: https://learn.microsoft.com/azure/app-service/overview-vnet-integration verified: true - claim: "Private endpoints let a function app reach storage over private network paths instead of public endpoints" source: https://learn.microsoft.com/azure/azure-functions/functions-create-vnet verified: true


Scenario 2: Private Egress (VNet + Storage PE)

Public inbound access with private outbound connectivity to backend services through VNet integration and private endpoints.

When to Use

  • Access databases, storage, or other services secured behind private endpoints
  • Compliance requirements for private data plane traffic
  • Hybrid connectivity to on-premises through VPN/ExpressRoute
  • Multi-tier architectures with private backend services

Architecture

flowchart TD
    INET[Internet] -->|HTTPS| FA[Function App]

    subgraph VNET["VNet"]
        subgraph INT_SUB["Integration Subnet"]
            FA
        end
        subgraph PE_SUB["Private Endpoint Subnet"]
            PE_BLOB[PE: blob]
            PE_QUEUE[PE: queue]
            PE_TABLE[PE: table]
            PE_FILE[PE: file]
        end
    end

    PE_BLOB --> ST[Storage Account]
    PE_QUEUE --> ST
    PE_TABLE --> ST
    PE_FILE --> ST

    FA -.->|Managed Identity| MI[RBAC]
    MI --> ST

    style FA fill:#0078d4,color:#fff
    style VNET fill:#E8F5E9,stroke:#4CAF50
    style ST fill:#FFF3E0

Supported Plans

Plan Supported Subnet Delegation
Consumption (Y1) N/A
Flex Consumption (FC1) Microsoft.App/environments
Premium (EP) Microsoft.Web/serverFarms
Dedicated (B1) [^1] N/A
Dedicated (S1+) Microsoft.Web/serverFarms

[^1]: Basic (B1) supports VNet integration per Azure documentation, but is not tested or recommended for private networking scenarios in this guide. Use Standard (S1+) for production.

Prerequisites

Before starting, complete the base deployment from your language tutorial's 02-first-deploy.md, then return here to add VNet integration.

Required resources: - [ ] Function App deployed and running - [ ] VNet with address space (e.g., 10.0.0.0/16) - [ ] Integration subnet (e.g., 10.0.1.0/24) — empty, delegated - [ ] Private endpoint subnet (e.g., 10.0.2.0/24) - [ ] Managed identity enabled on the function app

Step-by-Step Configuration

Step 1: Set Variables

export RG="rg-func-private-demo"
export APP_NAME="func-private-demo"
export STORAGE_NAME="stprivatedemo"
export VNET_NAME="vnet-func-demo"
export LOCATION="koreacentral"
Command/Parameter Purpose
export RG=... Resource group containing all resources
export VNET_NAME=... Virtual network name for integration

Step 2: Create VNet and Subnets

az network vnet create \
  --name "$VNET_NAME" \
  --resource-group "$RG" \
  --location "$LOCATION" \
  --address-prefixes "10.0.0.0/16" \
  --subnet-name "snet-integration" \
  --subnet-prefixes "10.0.1.0/24"

az network vnet subnet create \
  --name "snet-private-endpoints" \
  --resource-group "$RG" \
  --vnet-name "$VNET_NAME" \
  --address-prefixes "10.0.2.0/24"
Command/Parameter Purpose
--address-prefixes "10.0.0.0/16" Total VNet address space
--subnet-prefixes "10.0.1.0/24" Integration subnet CIDR

Step 3: Delegate Subnet (Plan-Specific)

az network vnet subnet update \
  --name "snet-integration" \
  --resource-group "$RG" \
  --vnet-name "$VNET_NAME" \
  --delegations "Microsoft.App/environments"
az network vnet subnet update \
  --name "snet-integration" \
  --resource-group "$RG" \
  --vnet-name "$VNET_NAME" \
  --delegations "Microsoft.Web/serverFarms"
Command/Parameter Purpose
--delegations "Microsoft.App/environments" FC1 subnet delegation
--delegations "Microsoft.Web/serverFarms" EP/ASP subnet delegation

Step 4: Enable VNet Integration

az functionapp vnet-integration add \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --vnet "$VNET_NAME" \
  --subnet "snet-integration"
Command/Parameter Purpose
--vnet "$VNET_NAME" Target virtual network
--subnet "snet-integration" Delegated integration subnet

Step 5: Create Storage Private Endpoints

export STORAGE_ID=$(az storage account show \
  --name "$STORAGE_NAME" \
  --resource-group "$RG" \
  --query "id" \
  --output tsv)

for SVC in blob queue table file; do
  az network private-endpoint create \
    --name "pe-st-$SVC" \
    --resource-group "$RG" \
    --location "$LOCATION" \
    --vnet-name "$VNET_NAME" \
    --subnet "snet-private-endpoints" \
    --private-connection-resource-id "$STORAGE_ID" \
    --group-ids "$SVC" \
    --connection-name "conn-st-$SVC"
done
Command/Parameter Purpose
--group-ids "$SVC" Storage sub-resource (blob, queue, table, file)
--private-connection-resource-id "$STORAGE_ID" Links endpoint to the storage account

Step 6: Create Private DNS Zones

for SVC in blob queue table file; do
  az network private-dns zone create \
    --resource-group "$RG" \
    --name "privatelink.$SVC.core.windows.net"

  az network private-dns link vnet create \
    --resource-group "$RG" \
    --zone-name "privatelink.$SVC.core.windows.net" \
    --name "link-$SVC" \
    --virtual-network "$VNET_NAME" \
    --registration-enabled false

  az network private-endpoint dns-zone-group create \
    --resource-group "$RG" \
    --endpoint-name "pe-st-$SVC" \
    --name "$SVC-dns-zone-group" \
    --private-dns-zone "privatelink.$SVC.core.windows.net" \
    --zone-name "$SVC"
done
Command/Parameter Purpose
--registration-enabled false Disables auto-registration of VMs
az network private-endpoint dns-zone-group create Links PE to DNS zone for automatic IP registration

Step 7: Lock Down Storage (Optional)

After private endpoints are configured, disable public access:

az storage account update \
  --name "$STORAGE_NAME" \
  --resource-group "$RG" \
  --default-action Deny \
  --allow-blob-public-access false
Command/Parameter Purpose
--default-action Deny Blocks all public network access

Order Matters

Disable public access after private endpoints and DNS zones are configured. Otherwise, your function app will lose storage connectivity.

Step 8: Configure Storage Authentication and Content Routing

RBAC Required

Identity-based host storage requires RBAC role assignments. Assign Storage Blob Data Owner and Storage Queue Data Contributor to the managed identity before configuring app settings.

Durable Functions

If using Durable Functions, also assign Storage Table Data Contributor for orchestration state storage.

FC1 supports both system-assigned and user-assigned managed identity for storage. System-assigned is simpler; user-assigned is required if you need to pre-configure RBAC before app creation.

Option A: System-Assigned (simpler)

# Enable system-assigned identity (if not already enabled)
az functionapp identity assign \
  --name "$APP_NAME" \
  --resource-group "$RG"

# Get principal ID
export PRINCIPAL_ID=$(az functionapp identity show \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --query "principalId" \
  --output tsv)

# Assign storage RBAC roles
az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Storage Blob Data Owner" \
  --scope "$STORAGE_ID"

az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Storage Queue Data Contributor" \
  --scope "$STORAGE_ID"

# Configure identity-based storage
az functionapp config appsettings set \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --settings \
    "AzureWebJobsStorage__accountName=$STORAGE_NAME" \
    "AzureWebJobsStorage__credential=managedidentity"

Option B: User-Assigned (pre-configured RBAC)

# Create user-assigned managed identity
export MI_NAME="mi-$APP_NAME"
az identity create \
  --name "$MI_NAME" \
  --resource-group "$RG" \
  --location "$LOCATION"

# Get identity resource ID and client ID
export MI_ID=$(az identity show \
  --name "$MI_NAME" \
  --resource-group "$RG" \
  --query "id" \
  --output tsv)

export MI_PRINCIPAL_ID=$(az identity show \
  --name "$MI_NAME" \
  --resource-group "$RG" \
  --query "principalId" \
  --output tsv)

export MI_CLIENT_ID=$(az identity show \
  --name "$MI_NAME" \
  --resource-group "$RG" \
  --query "clientId" \
  --output tsv)

# Assign storage RBAC roles to UAMI
az role assignment create \
  --assignee "$MI_PRINCIPAL_ID" \
  --role "Storage Blob Data Owner" \
  --scope "$STORAGE_ID"

az role assignment create \
  --assignee "$MI_PRINCIPAL_ID" \
  --role "Storage Queue Data Contributor" \
  --scope "$STORAGE_ID"

# Attach UAMI to function app
az functionapp identity assign \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --identities "$MI_ID"

# Configure identity-based storage with UAMI
az functionapp config appsettings set \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --settings \
    "AzureWebJobsStorage__accountName=$STORAGE_NAME" \
    "AzureWebJobsStorage__credential=managedidentity" \
    "AzureWebJobsStorage__clientId=$MI_CLIENT_ID"

Premium plans can use identity-based host storage, but private storage scenarios still need Azure Files content share settings for deployment and scale operations.

# Enable system-assigned identity (if not already enabled)
az functionapp identity assign \
  --name "$APP_NAME" \
  --resource-group "$RG"

# Get principal ID
export PRINCIPAL_ID=$(az functionapp identity show \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --query "principalId" \
  --output tsv)

export STORAGE_CONNECTION_STRING=$(az storage account show-connection-string \
  --name "$STORAGE_NAME" \
  --resource-group "$RG" \
  --query "connectionString" \
  --output tsv)

# Assign storage RBAC roles for host storage
az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Storage Blob Data Owner" \
  --scope "$STORAGE_ID"

az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Storage Queue Data Contributor" \
  --scope "$STORAGE_ID"

# Configure host storage and content share routing
az functionapp config appsettings set \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --settings \
    "AzureWebJobsStorage__accountName=$STORAGE_NAME" \
    "AzureWebJobsStorage__credential=managedidentity" \
    "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING=$STORAGE_CONNECTION_STRING" \
    "WEBSITE_CONTENTSHARE=$APP_NAME" \
    "WEBSITE_CONTENTOVERVNET=1"

Dedicated plans can avoid Azure Files content share dependency by deploying with WEBSITE_RUN_FROM_PACKAGE=1.

# Enable system-assigned identity (if not already enabled)
az functionapp identity assign \
  --name "$APP_NAME" \
  --resource-group "$RG"

# Get principal ID
export PRINCIPAL_ID=$(az functionapp identity show \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --query "principalId" \
  --output tsv)

# Assign storage RBAC roles
az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Storage Blob Data Owner" \
  --scope "$STORAGE_ID"

az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Storage Queue Data Contributor" \
  --scope "$STORAGE_ID"

# Configure identity-based storage
az functionapp config appsettings set \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --settings \
    "AzureWebJobsStorage__accountName=$STORAGE_NAME" \
    "AzureWebJobsStorage__credential=managedidentity" \
    "WEBSITE_RUN_FROM_PACKAGE=1"
Command/Parameter Purpose
az functionapp identity assign Enables managed identity on the function app
az role assignment create Grants storage access to the managed identity
az storage account show-connection-string Retrieves the Azure Files connection string required by Premium content share deployment
AzureWebJobsStorage__accountName Storage account name (not connection string)
AzureWebJobsStorage__credential=managedidentity Use managed identity for authentication
AzureWebJobsStorage__clientId (UAMI only) Specifies which identity to use
WEBSITE_CONTENTAZUREFILECONNECTIONSTRING Keeps Premium content share access working when storage is private
WEBSITE_CONTENTSHARE Sets the Premium content share name
WEBSITE_CONTENTOVERVNET=1 Routes Premium content share traffic through the integrated VNet
WEBSITE_RUN_FROM_PACKAGE=1 Uses package-based deployment for Dedicated plans instead of Azure Files content share

Verification

Check VNet Integration

az functionapp vnet-integration list \
  --name "$APP_NAME" \
  --resource-group "$RG" \
  --output table
Command/Parameter Purpose
az functionapp vnet-integration list Confirms that the function app is attached to the expected VNet and subnet

Test DNS Resolution (from within VNet)

nslookup $STORAGE_NAME.blob.core.windows.net
Command/Parameter Purpose
nslookup $STORAGE_NAME.blob.core.windows.net Verifies that the storage account resolves to the private endpoint IP from inside the VNet

Expected: Returns private IP (e.g., 10.0.2.x), not public IP.

Test Function Endpoint

curl --request GET "https://$APP_NAME.azurewebsites.net/api/health"
Command/Parameter Purpose
curl --request GET Confirms that the public endpoint still responds while outbound storage traffic stays private

Troubleshooting

Symptom Likely Cause Solution
Storage access denied DNS not resolving to private IP Verify DNS zone linked to VNet
Function timeout VNet integration not active Check az functionapp vnet-integration list
403 on storage RBAC not assigned Assign Storage Blob Data Owner to managed identity
Deployment fails Public access disabled too early Re-enable public access, complete deployment, then disable

Next Steps

See Also

Sources