Azure Functions
-
Rule: A Function App that hosts customer workload code must be connected privately to the customer spoke virtual network (VNet). HTTP triggers using anonymous authorization must have internet ingress handled through centralized hub services. HTTP triggers using function-level authorization may be directly exposed to the internet.
-
Action: Enable VNet Integration for outbound traffic through the hub firewall. For anonymous HTTP triggers, disable public network access and use a Private Endpoint for inbound access, routing public traffic through hub AFD. For function-key-authenticated HTTP triggers, public network access may be enabled with access restrictions enforcing the function key requirement.
-
Function App outbound connectivity must use VNet Integration to a dedicated delegated subnet (
Microsoft.Web/serverFarms) with a User Defined Route (UDR) directing outbound traffic to the hub firewall. -
HTTP triggers with
anonymousauthorization must not be directly accessible from the internet. Public network access must be disabled, and internet ingress must go through hub-managed Azure Front Door. -
HTTP triggers with
function-level authorization may be directly internet-accessible, as the function key provides authentication. Public network access may be enabled for this case. -
HTTP triggers with
admin-level authorization must not be directly exposed to the internet under any circumstances. -
NSGs should follow least privilege and allow only required inbound traffic from approved sources (for example, campus network ranges, VPN, or the hub firewall).
-
Administrative access must not be internet-exposed. See Access Methods for details.
-
Function App Plan must be on a Flex Consumption, Premium, or Dedicated (App Service Plan) SKU to support both Private Endpoints and VNet Integration. Consumption plan does not support VNet Integration.
With the exception of anonymous HTTP triggers, your Function App triggers, bindings, deployment workflow, and runtime settings are configured as usual. The key TAMU-network differences are private inbound access and VNet-integrated outbound routing.
Implementation Pattern
Azure Front Door
If you intend to publish your Function App to the internet, the recommended approach is to do so through the hub-managed Azure Front Door (AFD). Using this method, a private endpoint for inbound access is created and managed by AFD, and do you do not have to create the private endpoint yourself.
However, when using AFD-managed private endpoints, only AFD can access your application, and all traffic must go through AFD. Internal/Private traffic from other resources or networks will require a private endpoint in your spoke VNet.
For more information, see Access Methods.
Private Endpoint for Internal/Private Access
You may follow the Microsoft documentation for using Private Endpoints with Azure Functions as a reference. The key points for the TAMU managed network are:
- Create two subnets in your spoke VNet:
- A private endpoint subnet for the Function App Private Endpoint.
- A VNet Integration subnet delegated to
Microsoft.Web/serverFarmsfor outbound traffic. This subnet must have the UDR to the hub firewall associated.
- Deploy the Function App on an Elastic Premium or Dedicated App Service Plan in the same region as your spoke VNet.
- Create a Private Endpoint targeting the Function App, placing it in the private endpoint subnet. Select
Nowhen prompted toIntegrate with private DNS zone. DNS records will be created automatically in private DNS zone in the hub VNet. - Enable VNet Integration on the Function App, selecting the delegated integration subnet.
- Set
Public network accesstoDisabledon the Function App (or configure access restrictions with a default Deny rule if needed). - Verify the Private DNS zone
privatelink.azurewebsites.netis updated with the appropriate DNS records for the Private Endpoint by performing a DNS lookup for your Function App’s private FQDN from a machine inside the TAMU firewall and confirming it resolves to the private endpoint IP. Contact Cloud Services if it does not update.
Exposing HTTP Triggers Publicly
The correct approach depends on the authLevel of the HTTP trigger.
function-level auth — direct public exposure permitted
HTTP triggers that require a function key (authLevel: function) may be directly exposed to the internet, since the key provides authentication. In this case:
-
Public network access on the Function App may remain enabled.
-
No hub AFD or firewall configuration is required for inbound traffic.
-
Callers must supply the function key as an
x-functions-keyheader orcodequery parameter:curl -X POST "https://<func-name>.azurewebsites.net/api/<function-name>?code=<function-key>" \ -H "Content-Type: application/json" \ -d '{"key": "value"}'
Tip
Store function keys in Azure Key Vault and reference them from your Function App’s application settings. This allows keys to be rotated without redeployment and audited via Key Vault access logs.
Warning
admin-level authorization uses the host master key and must never be directly exposed to the internet.
anonymous-level auth — hub ingress required
HTTP triggers with no authentication (authLevel: anonymous) must not be directly accessible from the internet. The Function App itself remains fully private with public network access disabled. All public traffic must be routed through the hub Azure Front Door, which provides WAF protection and traffic inspection. The traffic path is:
Internet → Azure Front Door (hub) → Private Link → Function App Private Endpoint
What to request from Cloud Services
Submit a Cloud Services request and include the following information:
- The private FQDN of your Function App (e.g.
func-workload.azurewebsites.net) - The HTTP trigger path(s) to be routed (e.g.
/api/my-function, or/api/*for all functions) - The desired public hostname or custom domain (e.g.
api.example.tamu.edu) - Any required WAF exclusions or routing rules (e.g. allowed HTTP methods, rate limits)
Testing the AFD-routed path
Once Cloud Services has configured the AFD origin and endpoint, test the public path from any network:
curl -X POST "https://<afd-endpoint>/api/<function-name>" \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
Testing and Running Functions Securely
With public network access disabled, functions must be invoked and monitored through private, approved channels. The following methods are all supported:
-
Local development with Azure Functions Core Tools: Run and test functions entirely on your local machine using the Azure Functions Core Tools CLI (
func start). This requires no network access to Azure and is the recommended approach during active development. -
VS Code or IDE with Azurite: Use the Azure Functions extension for VS Code along with the Azurite storage emulator to run and debug functions locally, including trigger simulation for queue, blob, and timer triggers.
-
Campus network or VPN: Once deployed, invoke functions directly via their private FQDN from the campus network or via the Campus VPN. The private endpoint makes the Function App reachable at its private IP from any host on the managed network.
-
Azure CLI (from campus/VPN): Use
az functionappcommands to invoke functions, stream logs, and inspect configuration from any host with campus or VPN connectivity.az rest --method post \ --url "https://<func-name>.azurewebsites.net/api/<function-name>" \ --headers "x-functions-key=<key>" -
Azure Portal “Test/Run” tab: The Portal’s built-in test tab works when your browser is on the campus network or VPN, since the request is proxied through the Portal to the private endpoint.
-
Deployment from CI/CD: Deployments via GitHub Actions or Azure DevOps pipelines do not require public access to the Function App; use the Azure Functions GitHub Action or
az functionapp deploymentCLI commands. If the pipeline needs to reach resources inside the VNet (e.g. a private storage account), configure GitHub Actions private networking per the CI/CD Access guidance.
Steps in Azure Portal
The steps below are generalized for new or existing Function Apps.
- Azure Portal > Function App >
Networking. - Under
Outbound traffic, configure VNet Integration to the designated delegated subnet. - Under
Inbound traffic, configure a Private Endpoint targeting the private endpoint subnet. - In Function App
Settings>Configuration(orEnvironment variables), setPublic network accesstoDisabled. - Open the target subnet(s) >
Route tableand verify the hub firewall UDR is associated. See Route Tables for details. /cloud/aws/network.html#using-subnets - Review NSGs on the private endpoint subnet and verify only required ports and approved source ranges are allowed.
Migrating
To migrate an existing public Function App to the managed network:
- Create the VNet integration and private endpoint subnets if they do not already exist.
- Enable VNet Integration on the Function App using the delegated integration subnet.
- Review all HTTP triggers and identify their
authLevel:function-level: The trigger may remain publicly accessible. No further network change is required for inbound traffic, though adding a Private Endpoint for campus/VPN access is still recommended.anonymous: The trigger must be moved behind hub AFD. Continue with steps 4–7 below.
- Add a Private Endpoint to the Function App targeting the private endpoint subnet.
- Validate connectivity from within the VNet (e.g. from a VM or via campus VPN) before disabling public access.
- Submit a Cloud Services request to configure an AFD origin and endpoint for any anonymous HTTP triggers that need to remain publicly accessible.
- Once AFD is configured and validated, set
Public network accesstoDisabled. - Update any DNS records or application configurations that previously pointed to the public Function App hostname to use the new AFD endpoint.
Note
Disabling public network access will immediately block all direct inbound traffic to the Function App. Ensure the Private Endpoint and any hub-side AFD configuration are validated before making this change in production.
Example Terraform Snippets
Function App with VNet Integration and Private Endpoint
Flex Consumption Plan (recommended)
resource "azurerm_service_plan" "workload" {
name = "asp-func-workload"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
os_type = "Linux"
sku_name = "FC1"
}
resource "azurerm_function_app_flex_consumption" "workload" {
name = "func-workload"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
service_plan_id = azurerm_service_plan.workload.id
storage_account_name = azurerm_storage_account.sa.name
storage_account_access_key = azurerm_storage_account.sa.primary_access_key
public_network_access_enabled = false
virtual_network_subnet_id = azurerm_subnet.func_integration.id
runtime_name = "node"
runtime_version = "20"
maximum_instance_count = 50
instance_memory_in_mb = 2048
site_config {}
}
Elastic Premium SKU
resource "azurerm_service_plan" "workload" {
name = "asp-func-workload"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
os_type = "Linux"
sku_name = "EP1"
}
resource "azurerm_linux_function_app" "workload" {
name = "func-workload"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
service_plan_id = azurerm_service_plan.workload.id
storage_account_name = azurerm_storage_account.sa.name
storage_account_access_key = azurerm_storage_account.sa.primary_access_key
public_network_access_enabled = false
virtual_network_subnet_id = azurerm_subnet.func_integration.id
site_config {}
}
VNet Integration Subnet (Delegated)
resource "azurerm_subnet" "func_integration" {
name = "snet-func-integration"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.spoke.name
address_prefixes = ["10.x.x.x/28"]
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
}
}
}
Private Endpoint for Function App (Inbound)
resource "azurerm_private_endpoint" "func" {
name = "pe-func-workload"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
subnet_id = azurerm_subnet.private_endpoint.id
private_service_connection {
name = "psc-func-workload"
private_connection_resource_id = azurerm_linux_function_app.workload.id
subresource_names = ["sites"]
is_manual_connection = false
}
}