
Recently, a client asked me with a specific request: they needed a fixed outbound IP address for their Azure App Service to ensure consistent communication with external services. While Azure App Service in the Premium V3 tier offers stable IP addresses, I learned that this stability isn’t guaranteed with the newer Premium V4 tier, where outbound IPs are dynamic by design
To future-proof the solution and meet the client’s needs, I opted for a more robust approach using a NAT Gateway with Virtual Network (VNet) integration.
Below, I’ll walk you through the process, the Terraform configuration, and some key lessons learned along the way.
Setting Up a Static Outbound IP with Terraform
To achieve this, I configured a Virtual Network, integrated it with the App Service, and routed outbound traffic through a NAT Gateway with a static public IP. Here’s the Terraform configuration I used, along with explanations of the critical components.
Terraform Implementation
Virtual Network and Subnet Configuration: A VNet is created with a dedicated subnet for the App Service. The subnet is delegated to Microsoft.Web/serverFarms
to allow App Service integration, and service endpoints are enabled for Microsoft.Web
.
resource "azurerm_virtual_network" "vnet" {
name = "vnet"
resource_group_name = azurerm_resource_group.rg.name
location = "West Europe"
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "appsrv_subnet" {
name = appsrv-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
# Allow App Service into this subnet
service_endpoints = ["Microsoft.Web"]
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}
Static Public IP and NAT Gateway: A static public IP is allocated and associated with a NAT Gateway. The NAT Gateway is then linked to the subnet to route outbound traffic through the static IP.
resource "azurerm_public_ip" "pip" {
name = "pip"
resource_group_name = azurerm_resource_group.rg.name
location = "West Europe"
allocation_method = "Static"
sku = "Standard"
}
resource "azurerm_nat_gateway" "ng" {
name = "nat-gateway"
resource_group_name = azurerm_resource_group.rg.name
location = "West Europe"
}
resource "azurerm_nat_gateway_public_ip_association" "ng_pip_association" {
nat_gateway_id = azurerm_nat_gateway.ng.id
public_ip_address_id = azurerm_public_ip.pip.id
}
resource "azurerm_subnet_nat_gateway_association" "subnet_ng_association" {
subnet_id = azurerm_subnet.appsrv_subnet.id
nat_gateway_id = azurerm_nat_gateway.ng.id
}
App Service with VNet Integration: The App Service is configured with vnet_route_all_enabled = true
to ensure all outbound traffic is routed through the VNet. The integration is established using the azurerm_app_service_virtual_network_swift_connection
resource.
resource "azurerm_linux_web_app" "appsrv" {
name = "appsrv"
resource_group_name = azurerm_resource_group.rg.name
location = "West Europe"
service_plan_id = azurerm_service_plan.appplan.id
https_only = true
site_config {
application_stack {
dotnet_version = "8.0"
}
http2_enabled = true
vnet_route_all_enabled = true
}
}
resource "azurerm_app_service_virtual_network_swift_connection" "vnet_swift" {
app_service_id = azurerm_linux_web_app.appsrv.id
subnet_id = azurerm_subnet.appsrv_subnet.id
}
Output the Static IP: Finally, the static outbound IP is outputted for reference, which can be used for whitelisting with external services.
output "static_outbound_ip" {
description = "Static outbound IP for App Service"
value = azurerm_public_ip.pip.ip_address
}
Critical Configuration Notes
- VNet Routing: Setting
vnet_route_all_enabled = true
in the App Service’ssite_config
is essential to ensure all outbound traffic goes through the VNet and, consequently, the NAT Gateway. - Subnet Delegation: The subnet must be delegated to
Microsoft.Web/serverFarms
to support App Service integration. - NAT Gateway Support: This setup works on Basic, Standard, Premium, PremiumV2, and PremiumV3 App Service plans, making it compatible with most tiers.
Challenges with Terraform and Swift Connection
One recurring issue I encountered was with the azurerm_app_service_virtual_network_swift_connection
resource. Occasionally, Terraform would attempt to recreate or update the VNet integration unexpectedly, even when no changes were made to the configuration. This behavior has been noted in community discussions, where the virtual_network_subnet_id
attribute can be reset or cleared during subsequent Terraform runs. To mitigate this, I added a lifecycle
block to ignore changes to certain attributes.
resource "azurerm_linux_web_app" "appsrv" {
# ... existing config ...
lifecycle {
ignore_changes = [
virtual_network_subnet_id,
]
}
Verifying the Setup
After deployment, it’s crucial to confirm that the VNet integration is active and the static IP is applied to outbound traffic. In the Azure Portal, navigate to the App Service’s Networking section under Properties. If the VNet integration is listed and active, and the outbound IP matches the static IP assigned to the NAT Gateway, the setup is working as intended. If the integration status indicates an issue, the static IP won’t apply to outbound requests, so double-check the configuration.
Final Thoughts
With the Premium V4 tier not providing stable outbound IPs, this NAT Gateway solution ensures a consistent outbound IP regardless of the App Service plan tier. Whether you upgrade to V4 or stays on V3, your outbound traffic will remain predictable, avoiding potential disruptions with external services.
While the Terraform setup can be complex and occasionally finicky, especially with the swift connection resource; the result is a robust, future-proof solution. If you’re facing similar challenges or need to whitelist a static IP for third-party integrations, this approach is worth considering.