Problem
When you provision an Azure Logic App with a storage account that has public network access disabled, you may encounter a 400 Bad Request error with a 403 Forbidden message.
╷│ Error: creating App Service (Subscription: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"│ Resource Group Name: "logicapp-rg"│ Site Name: "logicapptest"): performing CreateOrUpdate: unexpected status 400 (400 Bad Request) with response: {"Code":"BadRequest","Message":"Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible.","Target":null,"Details":[{"Message":"Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible."},{"Code":"BadRequest"},{"ErrorEntity":{"ExtendedCode":"99022","MessageTemplate":"Creation of storage file share failed with: '{0}'. Please check if the storage account is accessible.","Parameters":["The remote server returned an error: (403) Forbidden."],"Code":"BadRequest","Message":"Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible."}}],"Innererror":null}│ │ with azurerm_logic_app_standard.logicapp,│ on main.tf line 127, in resource "azurerm_logic_app_standard" "logicapp":│ 127: resource "azurerm_logic_app_standard" "logicapp" {│ ╵Cause
Due to limitations within the Azure API, the AzureRM provider must use the Data Plane API when provisioning certain resources, such as storage containers. When the target storage account has public network access disabled, calls to the public Data Plane endpoint fail because they cannot reach the privately accessible resources.
Solution
To deploy a Logic App Standard that remains completely private, you must configure all communication to flow through a Virtual Network (VNet).
Procedure
Step 1: Configure the Virtual Network
Create a Virtual Network (VNet) to handle private traffic between the logic app and the storage account. This VNet requires two subnets.
- Private Endpoint Subnet: This subnet is for the storage account's private endpoints and should not be delegated to any service.
-
Logic App Subnet: This subnet must be delegated to
Microsoft.Web/serverFarms. This delegation reserves the subnet for App Service or Logic App workloads, allowing the Logic App Standard to integrate with the network.
This example shows the configuration for the delegated subnet.
resource "azurerm_subnet" "logicapp_subnet" {
name = "logicapp-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.2.0/24"]
delegation {
name = "logicapp-delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
service_endpoints = ["Microsoft.Storage"]
}Step 2: Configure the Private Storage Account
Configure a storage account with public network access disabled (public_network_access_enabled = false). Additionally, ensure the hierarchical namespace (HNS) is disabled, as it can conflict with the Azure Files shares that Logic App Standard requires for storing content.
Step 3: Add Private Endpoints and DNS Zones
Because the storage account is private, the Logic App needs a way to communicate with it internally. Create two private endpoints: one for blob and one for file.
- The Logic App primarily uses the
fileendpoint for its content share, but it also uses theblobendpoint for logs and extension bundles. Without ablobendpoint, the deployment may still fail with a 403 error.
Next, create private DNS zones for privatelink.blob.core.windows.net and privatelink.file.core.windows.net. Each zone needs an A-record that maps the storage account hostname to the private endpoint's IP address. This allows the Logic App to resolve the storage account hostnames to private IPs.
Step 4: Create the App Service Plan
Create an App Service Plan to run the Logic App Standard. Use a Workflow Standard SKU (e.g., WS1, WS2) and connect it to the delegated subnet you created in Step 1.
Step 5: Create the Logic App Standard Resource
Finally, create the azurerm_logic_app_standard resource with the following critical settings to ensure all traffic is forced through the VNet.
-
public_network_access = "Disabled": Disables inbound public traffic. -
virtual_network_subnet_id: Points to the delegated subnet in your VNet. -
site_configandapp_settings: IncludeWEBSITE_VNET_ROUTE_ALL = "1"andWEBSITE_CONTENTOVERVNET = "1"to force all storage traffic through the VNet. -
depends_on: Add a dependency on the storage account to ensure it is fully provisioned before the Logic App attempts to mount its file share.
resource "azurerm_logic_app_standard" "logicapp" {
name = "logicapptest"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
app_service_plan_id = azurerm_service_plan.logicapp_plan.id
storage_account_name = azurerm_storage_account.sa.name
storage_account_access_key = azurerm_storage_account.sa.primary_access_key
public_network_access = "Disabled"
virtual_network_subnet_id = azurerm_subnet.logicapp_subnet.id
enabled = true
https_only = true
version = "~3"
site_config {}
app_settings = {
WEBSITE_VNET_ROUTE_ALL = "1"
WEBSITE_CONTENTOVERVNET = "1"
}
depends_on = [azurerm_storage_account.sa]
}Outcome
After applying this configuration, Terraform should successfully provision the Azure Logic App with a private storage account.