Azure SQL Private End Points with Pulumi

by Frans Lytzen | 16/02/2021

Private End Points are the right way to connect PaaS Services to vNets so they can be accessed from other resources on the vNet. There are other, older, ways but they are not as good. Unfortunately, Private End Points require understanding quite a few things, including DNS resolution, split-brain DNS and a bunch of weird abstractions. This Microsoft post gives a good example of how to set it up with Azure SQL. This post does essentially the same thing, except it does not create a VM. Anything related to the VM is left out here.

This is part of a bigger project I am working on to set up a complex vNet-and-PasS setup in Azure with multiple services, regions and VPN clients.

Before we can add the private end point

This bit here sets up a network and a SQL Server so we can add the private end point.

1 const resourceGroup = new azure.resources.latest.ResourceGroup("fl-vnettest", {
2 location,
3 resourceGroupName: "fl-vnettest"
4 })
5
6 const vnet = new azure.network.latest.VirtualNetwork("fl-vnettest-vn", {
7 resourceGroupName: resourceGroup.name,
8 virtualNetworkName: "fl-vnettest-vn",
9 addressSpace: { addressPrefixes: ["10.0.0.0/16"] },
10 location
11 });
12
13 const azureSqlSubnet = new azure.network.latest.Subnet("azure-sql", {
14 subnetName: "azure-sql",
15 addressPrefix: "10.0.4.0/24",
16 virtualNetworkName: vnet.name,
17 resourceGroupName: resourceGroup.name,
18 privateEndpointNetworkPolicies: "Disabled"
19 });
20
21 const passSqlServer = new azure.sql.v20200801preview.Server("fl-vnettest-ss",
22 {
23 serverName: "fl-vnettest-ss",
24 administratorLogin,
25 administratorLoginPassword,
26 location,
27 resourceGroupName: resourceGroup.name,
28 tags,
29 minimalTlsVersion: "1.2",
30 });
31

Setting up the private end point

This is the important bit

You need to do this once per vnet, no matter how many SQL Servers you have in that vNet. You will need to do this for each type of service, i.e you'd need something similar for Storage etc. This is required in order to make DNS lookups for the SQL Server return the local IP address, rather than the public one. The tricky bit is that an Azure SQL Server will always have a public IP Address, even if no public access is allowed. When you set up the DNS resolution just right, a DNS lookup on your private network will return the private IP address and when you look it up on the public internet, you will see the public IP address. There is a lot more detail to this, but that is for another post. What is described here is the easiest and most reliable way to make this work. There are other ways, but they require more knowledge and more work.

1 // Sets up a private DNS Zone for SQL private link entries
2 const sqlPrivateDns = new azure.network.latest.PrivateZone("fl-vnettest-sqldns", {
3 privateZoneName: "privatelink.database.windows.net",
4 resourceGroupName: resourceGroup.name,
5 location: "global" //https://github.com/Azure/azure-cli/issues/6052
6 })
7
8 // Links the private DNS Zone to the vnet
9 const dnsVnetLink = new azure.network.latest.VirtualNetworkLink("fl-vnettest-vnl", {
10 privateZoneName: sqlPrivateDns.name,
11 resourceGroupName: resourceGroup.name,
12 virtualNetworkLinkName: "fl-vnettest-vnl",
13 registrationEnabled: true,
14 virtualNetwork: { id: vnet.id },
15 location: "global"
16 })
17

Set up a Private End Point for the SQL Server

You need to do this for each SQL Server in your vnet

1 // Create the private end point for the SQL Server
2 const sqlPrivateEndPoint = new azure.network.latest.PrivateEndpoint("fl-vnettest-sqlpep", {
3 privateEndpointName: "fl-vnettest-sqlpep",
4 resourceGroupName: resourceGroup.name,
5 location,
6 subnet: { id: azureSqlSubnet.id },
7 privateLinkServiceConnections: [
8 {
9 name: "sql",
10 privateLinkServiceId: passSqlServer.id,
11 groupIds: ["sqlServer"], // This particular group id is not documented by Azure that I can see - I found it by trying to do this manually in the portal
12 privateLinkServiceConnectionState: {
13 actionsRequired: "None",
14 description: "Auto-approved",
15 status: "Approved"
16 }
17 }
18 ]
19 })
20
21 // Connects the private DNS Zone and the private end point (I think)
22 const sqlPrivateDnsZoneGroup = new azure.network.latest.PrivateDnsZoneGroup("fl-vnettest-sqldnsgroup", {
23 privateDnsZoneGroupName: "fl-vnettest-sqldnsgroup",
24 privateEndpointName: sqlPrivateEndPoint.name,
25 resourceGroupName: resourceGroup.name,
26 name: "fl-vnettest-sqldns",
27 privateDnsZoneConfigs: [{
28 name: sqlPrivateDns.name,
29 privateDnsZoneId: sqlPrivateDns.id
30 }]
31 })
32
33

Connect a Web App

If you want to connect a web app to the vNet so it can talk to the SQL (not really the scope of this post, but hey) then do the following. Note, this is outbound from the Web App. Inbound traffic to the Web App is best handled by setting up a Private End Point for the Web App - though that currently requires a Premium App Service Plan. Service End Points is another option but they are much harder to use in my experience and I don't recommend them.

1 // Set up a subnet dedicated as an entry point for Web Apps
2 const frontEndSubnet = new azure.network.latest.Subnet("front-end", {
3 subnetName: "front-end",
4 addressPrefix: "10.0.1.0/24",
5 virtualNetworkName: vnet.name,
6 resourceGroupName: resourceGroup.name,
7 delegations: [
8 {
9 serviceName: "Microsoft.Web/serverfarms",
10 name: "front-end-delegation"
11 }
12 ]
13 });
14
15 // Add an app service plan and a web app
16 const appServicePlan = new azure.web.latest.AppServicePlan("fl-vnettest-as", {
17 name: "fl-vnettest-as",
18 location,
19 resourceGroupName: resourceGroup.name,
20 sku: {
21 family: "S",
22 capacity: 1,
23 size: "S1",
24 name: "S1"
25 }
26 });
27
28 const appService = new azure.web.latest.WebApp("fl-vnettest-wa",
29 {
30 name: "fl-vnettest-wa",
31 location,
32 resourceGroupName: resourceGroup.name,
33 clientAffinityEnabled: false,
34 httpsOnly: true,
35 serverFarmId: appServicePlan.id,
36 tags,
37 siteConfig: {
38 appSettings: [
39 // This is critical to make DNS lookups return the internal IP Address.
40 // Without it, your web app will get the public IP address of the SQL Server and will be denied access.
41 // 168.63.129.16 is a magic constant provided by Microsoft and works with the setup above.
42 // You will only need something different if you use your own DNS servers.
43 // See https://feedback.azure.com/forums/169385-web-apps/suggestions/38383642-web-app-and-private-dns-zone-support
44 { name: "WEBSITE_DNS_SERVER", value: "168.63.129.16" },
45 { name: "WEBSITE_VNET_ROUTE_ALL", value: "1" },
46 { name: "hostname", value: passSqlServer.name.apply(n => n + ".database.windows.net") },
47 {
48 name: "sql",
49 value: "Server=tcp:fl-vnettest-ss.database.windows.net,1433;Initial Catalog=fl-vnettest-db;Persist Security Info=False;User ID=" + administratorLogin + ";Password=" + administratorLoginPassword + ";MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
50 // Note: This isn't ideal for many reasons, but I haven't got the brain power to nest apply calls :)
51 }
52 ]
53 },
54 })
55
56 // Give the web app access to the vnet <- the thing you actually wanted to see :)
57 // Note that "Swift" is the "modern" way of doing this. Don't do what I do and spend ours trying to make WebAppVnetConnection work - it won't.
58 const webAppVNetConnection = new azure.web.latest.WebAppSwiftVirtualNetworkConnection("fl-vnettest-wa-vc", {
59 name: appService.name,
60 resourceGroupName: resourceGroup.name,
61 subnetResourceId: frontEndSubnet.id
62 })
63
64

References


Originally posted on Frans' blog.


Share this article

You Might Also Like

Explore more articles that dive into similar topics. Whether you’re looking for fresh insights or practical advice, we’ve handpicked these just for you.

AI Governance & Ethics: The Non-Negotiables for Leaders

by Frans Lytzen | 11/09/2025

AI adoption is racing ahead, but governance and ethics often lag behind. Nearly half of leaders admit they’re not prepared to use AI responsibly. Discover the four non-negotiables every organisation needs to build trust and ensure Responsible AI.

How BDO UK Elevates Payroll Excellence with Payflow by NewOrbit

by George Elkington | 25/07/2025

Discover how BDO UK streamlined complex payroll operations with Payflow by NewOrbit — gaining full audit transparency and tailored workflows through a long-term product partnership.

Design, Code, AI: Behind the Scenes of Our Craft IT Logo Generator and Gallery

by Marcin Prystupa | 07/07/2025

A behind-the-scenes look at how we built two interactive apps for the Craft IT conference booth – and how I, a UX designer, ended up deep in React code with a little help from AI.

Cookie Settings

Contact Us

NewOrbit Ltd.
Hampden House
Chalgrove
OX44 7RW


020 3757 9100

NewOrbit Logo

Copyright © NewOrbit Ltd.