In order to deploy my Azure Function code to an Azure Function deployed inside a vNet (using private endpoints), I needed to connect my local machine to the vNet running in Azure using Azure VPN Gateway.
My Azure Functions were deployed inside a vNet (not directly publicly exposed). I deployed them inside the vNet using private endpoints to ensure that they were only accessed via my reverse proxy (Azure Application Gateway).

This is great from a security standpoint, but it makes it hard to do local development & deploy my code to the Azure Function to test it.

If I try to deploy my Function code from my local computer without some way into the vNet, I get the following error.
az functionapp deployment source config-zip -g rg-apimTrafficMgr-ussc-dev -n func-apimTrafficMgr-ussc-dev --src ./app.zip
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
The command failed with an unexpected error. Here is the traceback:
HTTPSConnectionPool(host='func-apimtrafficmgr-ussc-dev.scm.azurewebsites.net', port=443): Max retries exceeded with url: /api/zipdeploy?isAsync=true (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x075B37F0>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed'))
Traceback (most recent call last):
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\urllib3/connection.py", line 174, in _new_conn
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\urllib3/util/connection.py", line 73, in create_connection
File "socket.py", line 955, in getaddrinfo
socket.gaierror: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\urllib3/connectionpool.py", line 699, in urlopen
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\urllib3/connectionpool.py", line 382, in _make_request
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\urllib3/connectionpool.py", line 1010, in _validate_conn
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\urllib3/connection.py", line 358, in connect
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\urllib3/connection.py", line 186, in _new_conn
urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPSConnection object at 0x075B37F0>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/__init__.py", line 697, in _run_job
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/__init__.py", line 333, in __call__
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\azure/cli/core/commands/command_operation.py", line 121, in handler
File "C:\Users\jordanbean\.azure\cliextensions\appservice-kube\azext_appservice_kube\custom.py", line 1303, in enable_zip_deploy_functionapp
return enable_zip_deploy(cmd, resource_group_name, name, src, timeout, slot) File "C:\Users\jordanbean\.azure\cliextensions\appservice-kube\azext_appservice_kube\custom.py", line 1338, in enable_zip_deploy
res = requests.post(zip_url, data=zip_content, headers=headers, verify=not should_disable_connection_verify())
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\requests/api.py", line 117, in post
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\requests/api.py", line 61, in request
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\requests/sessions.py", line 542, in request
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\requests/sessions.py", line 655, in send
File "D:\a\1\s\build_scripts\windows\artifacts\cli\Lib\site-packages\requests/adapters.py", line 516, in send
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='func-apimtrafficmgr-ussc-dev.scm.azurewebsites.net', port=443): Max retries exceeded with url: /api/zipdeploy?isAsync=true (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x075B37F0>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed'))
In order to overcome this, I need to connect my local computer to the vNet so it can communicate with the Azure Function. Normally, this would be accomplished by me being on a machine connected to the same vNet (such as over ExpressRoute).
In my case, I decided to stand up a point-to-site VPN from my local computer to my Azure vNet using Azure VPN Gateway. This is because I am testing locally, and I don’t have an ExpressRoute from my local home computer to an Azure vNet.

First, I need to allocate a new subnet GatewaySubnet
for the VPN Gateway to be attached to. In the Azure portal, open your vNet, navigate to the Subnets
blade & click on Gateway subnet
. Choose an appropriate subnet address range and click Save
.

Next, go back to your Resource Group
and click Create
and select Virtual network gateway
. Give it a Name
, pick a Region
, select VPN
as the Gateway type, select Route-based
as the VPN type, pick the VpnGw1
SKU (since I don’t need much), select Generation 1
, pick my vNet
and subnet
, select Standard
as the Public IP Address type, create a new Public IP address and click Review + create
.

Once the VPN Gateway has been created (this may take a while), you can select it in the Azure portal. Select the Point-to-site configuration
blade. Add an Address Pool
that works for your local home network, select OpenVPN (SSL)
as the Tunnel type, set the Authentication typ
e to Azure Active Directory
. Set the following values for the AAD authentication (check the Azure docs for more specific guidance depending on your use case). You will need to get the unique identifier of your AAD tenant (go to the Azure Active Directory
page in the Azure portal and look in the Overview
page).
- Tenant – https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/
- Audience – 41b23e61-6c1e-4545-b367-cd054e0ed4b4
- Issuer – https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/
Click Save. This will take a few minutes to set up. You may need to navigate away from this page and navigate back after the Save to enable the Download VPN client
button.

Look in your Downloads
directory to see the zip file that contains the configuration XML files. In this example, I will use the Azure VPN client, so navigate to the AzureVPN
directory.

Now I need a VPN client. I can use the Azure VPN client from the Microsoft store.

Open the Azure VPN Client
on your local machine. Click the +
button, select Import
and select the azurevpnconfig.xml
file you downloaded.

Now you can click the Connect
button to sign-in to AAD & connect to your vNet. We can see it connected to both our local network (using the Address space
specified earlier) and the Azure network.

This connects you to the vNet running in Azure, but the Function deployment still fails! What gives?
We have connected our networks, but our local computer does not know how to resolve the FQDN of the Azure Function to the correct IP address in my Azure vNet. It is still trying to go out to the Internet to resolve it but will be unable to do so because we have enabled private endpoints
on our Azure Function.
ping func-apimtrafficmgr-ussc-dev.azurewebsites.net
Ping request could not find host func-apimtrafficmgr-ussc-dev.azurewebsites.net. Please check the name and try again.
We need to tell our local computer how to resolve this FQDN. Since I do not have a DNS forwarder setup, I can modify the local hosts
file to give it an A
record to resolve to.
We need to find the “local” IP address of the private endpoint deployed to Azure. Navigate to the private endpoint resource and click on the DNS configuration
blade. We can see the IP address that has been assigned to it and the equivalent FQDNs.

Open the C:\Windows\System32\drivers\etc\hosts
file using Notepad running in Administrator mode. Add records for both the Function API endpoint & the SCM endpoint (which lets us deploy our code).
10.0.1.5 func-apimtrafficmgr-ussc-dev.azurewebsites.net
10.0.1.5 func-apimtrafficmgr-ussc-dev.scm.azurewebsites.net
Save the hosts
file.
We can now see that ping
is able to resolve the correct 10.
IP address (even though it won’t get a response from the Function App, we know the IP address is now correct).
ping func-apimtrafficmgr-ussc-dev.azurewebsites.net
Pinging func-apimtrafficmgr-ussc-dev.azurewebsites.net [10.0.1.5] with 32 bytes of data:
Request timed out.
Try the deployment again and it will succeed.
az functionapp deployment source config-zip -g rg-apimTrafficMgr-ussc-dev -n func-apimTrafficMgr-ussc-dev --src ./app.zip
The behavior of this command has been altered by the following extension: appservice-kube
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
{
"active": true,
"author": "N/A",
"author_email": "N/A",
"complete": true,
"deployer": "ZipDeploy",
"end_time": "2022-05-27T21:22:44.8618148Z",
"id": "60ed3ee9cc5947648e9cb0b94b65f7ce",
"is_readonly": true,
"is_temp": false,
"last_success_end_time": "2022-05-27T21:22:44.8618148Z",
"log_url": "https://func-apimtrafficmgr-ussc-dev.scm.azurewebsites.net/api/deployments/latest/log",
"message": "Created via a push deployment",
"progress": "",
"provisioningState": "Succeeded",
"received_time": "2022-05-27T21:22:38.3617789Z",
"site_name": "func-apimTrafficMgr-ussc-dev",
"start_time": "2022-05-27T21:22:38.6742432Z",
"status": 4,
"status_text": "",
"url": "https://func-apimtrafficmgr-ussc-dev.scm.azurewebsites.net/api/deployments/latest"
}