The azure-native:network:PrivateEndpoint resource, part of the Pulumi Azure Native provider, creates private endpoints that connect virtual networks to Azure services over private IP addresses, eliminating public internet exposure. This guide focuses on three capabilities: Private Link service connections with static IPs, application security group integration, and manual approval workflows for cross-tenant access.
Private endpoints require existing virtual networks, subnets, and target Private Link services or Azure service endpoints. The examples are intentionally small. Combine them with your own VNet infrastructure and DNS configuration.
Connect to a Private Link service with static IP
Most deployments connect applications to Azure services over private IPs, eliminating public internet exposure and ensuring predictable network addressing.
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const privateEndpoint = new azure_native.network.PrivateEndpoint("privateEndpoint", {
customNetworkInterfaceName: "testPeNic",
ipConfigurations: [{
groupId: "file",
memberName: "file",
name: "pestaticconfig",
privateIPAddress: "192.168.0.6",
}],
location: "eastus2euap",
privateEndpointName: "testPe",
privateLinkServiceConnections: [{
groupIds: ["groupIdFromResource"],
privateLinkServiceId: "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
requestMessage: "Please approve my connection.",
}],
resourceGroupName: "rg1",
subnet: {
id: "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
},
});
import pulumi
import pulumi_azure_native as azure_native
private_endpoint = azure_native.network.PrivateEndpoint("privateEndpoint",
custom_network_interface_name="testPeNic",
ip_configurations=[{
"group_id": "file",
"member_name": "file",
"name": "pestaticconfig",
"private_ip_address": "192.168.0.6",
}],
location="eastus2euap",
private_endpoint_name="testPe",
private_link_service_connections=[{
"group_ids": ["groupIdFromResource"],
"private_link_service_id": "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
"request_message": "Please approve my connection.",
}],
resource_group_name="rg1",
subnet={
"id": "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
})
package main
import (
network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := network.NewPrivateEndpoint(ctx, "privateEndpoint", &network.PrivateEndpointArgs{
CustomNetworkInterfaceName: pulumi.String("testPeNic"),
IpConfigurations: network.PrivateEndpointIPConfigurationArray{
&network.PrivateEndpointIPConfigurationArgs{
GroupId: pulumi.String("file"),
MemberName: pulumi.String("file"),
Name: pulumi.String("pestaticconfig"),
PrivateIPAddress: pulumi.String("192.168.0.6"),
},
},
Location: pulumi.String("eastus2euap"),
PrivateEndpointName: pulumi.String("testPe"),
PrivateLinkServiceConnections: network.PrivateLinkServiceConnectionArray{
&network.PrivateLinkServiceConnectionArgs{
GroupIds: pulumi.StringArray{
pulumi.String("groupIdFromResource"),
},
PrivateLinkServiceId: pulumi.String("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls"),
RequestMessage: pulumi.String("Please approve my connection."),
},
},
ResourceGroupName: pulumi.String("rg1"),
Subnet: &network.SubnetTypeArgs{
Id: pulumi.String("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;
return await Deployment.RunAsync(() =>
{
var privateEndpoint = new AzureNative.Network.PrivateEndpoint("privateEndpoint", new()
{
CustomNetworkInterfaceName = "testPeNic",
IpConfigurations = new[]
{
new AzureNative.Network.Inputs.PrivateEndpointIPConfigurationArgs
{
GroupId = "file",
MemberName = "file",
Name = "pestaticconfig",
PrivateIPAddress = "192.168.0.6",
},
},
Location = "eastus2euap",
PrivateEndpointName = "testPe",
PrivateLinkServiceConnections = new[]
{
new AzureNative.Network.Inputs.PrivateLinkServiceConnectionArgs
{
GroupIds = new[]
{
"groupIdFromResource",
},
PrivateLinkServiceId = "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
RequestMessage = "Please approve my connection.",
},
},
ResourceGroupName = "rg1",
Subnet = new AzureNative.Network.Inputs.SubnetArgs
{
Id = "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.PrivateEndpoint;
import com.pulumi.azurenative.network.PrivateEndpointArgs;
import com.pulumi.azurenative.network.inputs.PrivateEndpointIPConfigurationArgs;
import com.pulumi.azurenative.network.inputs.PrivateLinkServiceConnectionArgs;
import com.pulumi.azurenative.network.inputs.SubnetArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var privateEndpoint = new PrivateEndpoint("privateEndpoint", PrivateEndpointArgs.builder()
.customNetworkInterfaceName("testPeNic")
.ipConfigurations(PrivateEndpointIPConfigurationArgs.builder()
.groupId("file")
.memberName("file")
.name("pestaticconfig")
.privateIPAddress("192.168.0.6")
.build())
.location("eastus2euap")
.privateEndpointName("testPe")
.privateLinkServiceConnections(PrivateLinkServiceConnectionArgs.builder()
.groupIds("groupIdFromResource")
.privateLinkServiceId("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls")
.requestMessage("Please approve my connection.")
.build())
.resourceGroupName("rg1")
.subnet(SubnetArgs.builder()
.id("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet")
.build())
.build());
}
}
resources:
privateEndpoint:
type: azure-native:network:PrivateEndpoint
properties:
customNetworkInterfaceName: testPeNic
ipConfigurations:
- groupId: file
memberName: file
name: pestaticconfig
privateIPAddress: 192.168.0.6
location: eastus2euap
privateEndpointName: testPe
privateLinkServiceConnections:
- groupIds:
- groupIdFromResource
privateLinkServiceId: /subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls
requestMessage: Please approve my connection.
resourceGroupName: rg1
subnet:
id: /subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet
The privateLinkServiceConnections property establishes the connection to your target service. The subnet property places the endpoint in your VNet. The ipConfigurations array assigns a static IP (192.168.0.6) to the endpoint’s network interface, with groupId and memberName specifying which service subresource to connect to (in this case, “file” for Azure Storage file shares).
Apply network security policies with application security groups
Application security groups organize private endpoints into logical groups, simplifying NSG rules across multiple endpoints.
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const privateEndpoint = new azure_native.network.PrivateEndpoint("privateEndpoint", {
applicationSecurityGroups: [{
id: "/subscriptions/subId/resourceGroups/rg1/provders/Microsoft.Network/applicationSecurityGroup/asg1",
}],
location: "eastus2euap",
privateEndpointName: "testPe",
privateLinkServiceConnections: [{
groupIds: ["groupIdFromResource"],
privateLinkServiceId: "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
requestMessage: "Please approve my connection.",
}],
resourceGroupName: "rg1",
subnet: {
id: "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
},
});
import pulumi
import pulumi_azure_native as azure_native
private_endpoint = azure_native.network.PrivateEndpoint("privateEndpoint",
application_security_groups=[{
"id": "/subscriptions/subId/resourceGroups/rg1/provders/Microsoft.Network/applicationSecurityGroup/asg1",
}],
location="eastus2euap",
private_endpoint_name="testPe",
private_link_service_connections=[{
"group_ids": ["groupIdFromResource"],
"private_link_service_id": "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
"request_message": "Please approve my connection.",
}],
resource_group_name="rg1",
subnet={
"id": "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
})
package main
import (
network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := network.NewPrivateEndpoint(ctx, "privateEndpoint", &network.PrivateEndpointArgs{
ApplicationSecurityGroups: network.ApplicationSecurityGroupTypeArray{
&network.ApplicationSecurityGroupTypeArgs{
Id: pulumi.String("/subscriptions/subId/resourceGroups/rg1/provders/Microsoft.Network/applicationSecurityGroup/asg1"),
},
},
Location: pulumi.String("eastus2euap"),
PrivateEndpointName: pulumi.String("testPe"),
PrivateLinkServiceConnections: network.PrivateLinkServiceConnectionArray{
&network.PrivateLinkServiceConnectionArgs{
GroupIds: pulumi.StringArray{
pulumi.String("groupIdFromResource"),
},
PrivateLinkServiceId: pulumi.String("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls"),
RequestMessage: pulumi.String("Please approve my connection."),
},
},
ResourceGroupName: pulumi.String("rg1"),
Subnet: &network.SubnetTypeArgs{
Id: pulumi.String("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;
return await Deployment.RunAsync(() =>
{
var privateEndpoint = new AzureNative.Network.PrivateEndpoint("privateEndpoint", new()
{
ApplicationSecurityGroups = new[]
{
new AzureNative.Network.Inputs.ApplicationSecurityGroupArgs
{
Id = "/subscriptions/subId/resourceGroups/rg1/provders/Microsoft.Network/applicationSecurityGroup/asg1",
},
},
Location = "eastus2euap",
PrivateEndpointName = "testPe",
PrivateLinkServiceConnections = new[]
{
new AzureNative.Network.Inputs.PrivateLinkServiceConnectionArgs
{
GroupIds = new[]
{
"groupIdFromResource",
},
PrivateLinkServiceId = "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
RequestMessage = "Please approve my connection.",
},
},
ResourceGroupName = "rg1",
Subnet = new AzureNative.Network.Inputs.SubnetArgs
{
Id = "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.PrivateEndpoint;
import com.pulumi.azurenative.network.PrivateEndpointArgs;
import com.pulumi.azurenative.network.inputs.ApplicationSecurityGroupArgs;
import com.pulumi.azurenative.network.inputs.PrivateLinkServiceConnectionArgs;
import com.pulumi.azurenative.network.inputs.SubnetArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var privateEndpoint = new PrivateEndpoint("privateEndpoint", PrivateEndpointArgs.builder()
.applicationSecurityGroups(ApplicationSecurityGroupArgs.builder()
.id("/subscriptions/subId/resourceGroups/rg1/provders/Microsoft.Network/applicationSecurityGroup/asg1")
.build())
.location("eastus2euap")
.privateEndpointName("testPe")
.privateLinkServiceConnections(PrivateLinkServiceConnectionArgs.builder()
.groupIds("groupIdFromResource")
.privateLinkServiceId("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls")
.requestMessage("Please approve my connection.")
.build())
.resourceGroupName("rg1")
.subnet(SubnetArgs.builder()
.id("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet")
.build())
.build());
}
}
resources:
privateEndpoint:
type: azure-native:network:PrivateEndpoint
properties:
applicationSecurityGroups:
- id: /subscriptions/subId/resourceGroups/rg1/provders/Microsoft.Network/applicationSecurityGroup/asg1
location: eastus2euap
privateEndpointName: testPe
privateLinkServiceConnections:
- groupIds:
- groupIdFromResource
privateLinkServiceId: /subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls
requestMessage: Please approve my connection.
resourceGroupName: rg1
subnet:
id: /subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet
The applicationSecurityGroups property attaches the endpoint to an existing ASG. This lets you write NSG rules that reference the ASG rather than individual IP addresses, centralizing policy management as you add more endpoints.
Request manual approval for cross-tenant connections
When connecting to Private Link services in other subscriptions or tenants, the service owner must manually approve the connection before traffic flows.
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const privateEndpoint = new azure_native.network.PrivateEndpoint("privateEndpoint", {
customNetworkInterfaceName: "testPeNic",
ipConfigurations: [{
groupId: "file",
memberName: "file",
name: "pestaticconfig",
privateIPAddress: "192.168.0.5",
}],
location: "eastus",
manualPrivateLinkServiceConnections: [{
groupIds: ["groupIdFromResource"],
privateLinkServiceId: "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
requestMessage: "Please manually approve my connection.",
}],
privateEndpointName: "testPe",
resourceGroupName: "rg1",
subnet: {
id: "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
},
});
import pulumi
import pulumi_azure_native as azure_native
private_endpoint = azure_native.network.PrivateEndpoint("privateEndpoint",
custom_network_interface_name="testPeNic",
ip_configurations=[{
"group_id": "file",
"member_name": "file",
"name": "pestaticconfig",
"private_ip_address": "192.168.0.5",
}],
location="eastus",
manual_private_link_service_connections=[{
"group_ids": ["groupIdFromResource"],
"private_link_service_id": "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
"request_message": "Please manually approve my connection.",
}],
private_endpoint_name="testPe",
resource_group_name="rg1",
subnet={
"id": "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
})
package main
import (
network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := network.NewPrivateEndpoint(ctx, "privateEndpoint", &network.PrivateEndpointArgs{
CustomNetworkInterfaceName: pulumi.String("testPeNic"),
IpConfigurations: network.PrivateEndpointIPConfigurationArray{
&network.PrivateEndpointIPConfigurationArgs{
GroupId: pulumi.String("file"),
MemberName: pulumi.String("file"),
Name: pulumi.String("pestaticconfig"),
PrivateIPAddress: pulumi.String("192.168.0.5"),
},
},
Location: pulumi.String("eastus"),
ManualPrivateLinkServiceConnections: network.PrivateLinkServiceConnectionArray{
&network.PrivateLinkServiceConnectionArgs{
GroupIds: pulumi.StringArray{
pulumi.String("groupIdFromResource"),
},
PrivateLinkServiceId: pulumi.String("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls"),
RequestMessage: pulumi.String("Please manually approve my connection."),
},
},
PrivateEndpointName: pulumi.String("testPe"),
ResourceGroupName: pulumi.String("rg1"),
Subnet: &network.SubnetTypeArgs{
Id: pulumi.String("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;
return await Deployment.RunAsync(() =>
{
var privateEndpoint = new AzureNative.Network.PrivateEndpoint("privateEndpoint", new()
{
CustomNetworkInterfaceName = "testPeNic",
IpConfigurations = new[]
{
new AzureNative.Network.Inputs.PrivateEndpointIPConfigurationArgs
{
GroupId = "file",
MemberName = "file",
Name = "pestaticconfig",
PrivateIPAddress = "192.168.0.5",
},
},
Location = "eastus",
ManualPrivateLinkServiceConnections = new[]
{
new AzureNative.Network.Inputs.PrivateLinkServiceConnectionArgs
{
GroupIds = new[]
{
"groupIdFromResource",
},
PrivateLinkServiceId = "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls",
RequestMessage = "Please manually approve my connection.",
},
},
PrivateEndpointName = "testPe",
ResourceGroupName = "rg1",
Subnet = new AzureNative.Network.Inputs.SubnetArgs
{
Id = "/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.PrivateEndpoint;
import com.pulumi.azurenative.network.PrivateEndpointArgs;
import com.pulumi.azurenative.network.inputs.PrivateEndpointIPConfigurationArgs;
import com.pulumi.azurenative.network.inputs.PrivateLinkServiceConnectionArgs;
import com.pulumi.azurenative.network.inputs.SubnetArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var privateEndpoint = new PrivateEndpoint("privateEndpoint", PrivateEndpointArgs.builder()
.customNetworkInterfaceName("testPeNic")
.ipConfigurations(PrivateEndpointIPConfigurationArgs.builder()
.groupId("file")
.memberName("file")
.name("pestaticconfig")
.privateIPAddress("192.168.0.5")
.build())
.location("eastus")
.manualPrivateLinkServiceConnections(PrivateLinkServiceConnectionArgs.builder()
.groupIds("groupIdFromResource")
.privateLinkServiceId("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls")
.requestMessage("Please manually approve my connection.")
.build())
.privateEndpointName("testPe")
.resourceGroupName("rg1")
.subnet(SubnetArgs.builder()
.id("/subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet")
.build())
.build());
}
}
resources:
privateEndpoint:
type: azure-native:network:PrivateEndpoint
properties:
customNetworkInterfaceName: testPeNic
ipConfigurations:
- groupId: file
memberName: file
name: pestaticconfig
privateIPAddress: 192.168.0.5
location: eastus
manualPrivateLinkServiceConnections:
- groupIds:
- groupIdFromResource
privateLinkServiceId: /subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/privateLinkServices/testPls
requestMessage: Please manually approve my connection.
privateEndpointName: testPe
resourceGroupName: rg1
subnet:
id: /subscriptions/subId/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet
The manualPrivateLinkServiceConnections property sends a connection request with a custom message. The connection remains in a pending state until the service owner approves it through the Azure portal or API. Use this when you don’t have direct access to approve connections on the target service.
Beyond these examples
These snippets focus on specific private endpoint features: Private Link service connections (automatic and manual approval), static IP assignment and custom NIC naming, and application security group integration. They’re intentionally minimal rather than full networking solutions.
The examples reference pre-existing infrastructure such as virtual networks and subnets, Private Link services or Azure service endpoints, and application security groups (for relevant examples). They focus on configuring the private endpoint rather than provisioning the surrounding network.
To keep things focused, common private endpoint patterns are omitted, including:
- Custom DNS configurations (customDnsConfigs)
- Extended location for edge deployments
- Multiple IP configurations per endpoint
- Tags for resource organization
These omissions are intentional: the goal is to illustrate how each private endpoint feature is wired, not provide drop-in networking modules. See the Private Endpoint resource reference for all available configuration options.
Let's configure Azure Private Endpoints
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Connection & Approval
privateLinkServiceConnections for automatic approval when you have access to approve the connection. Use manualPrivateLinkServiceConnections when the network admin doesn’t have access to approve connections to the remote resource and manual approval is required.manualPrivateLinkServiceConnections with the target service ID, group IDs, and a requestMessage explaining why approval is needed. The connection will require manual approval from the resource owner.Network Configuration
ipConfigurations with groupId, memberName, and privateIPAddress properties. For example, to assign 192.168.0.6 to a file share endpoint, set groupId and memberName to “file” and specify the desired IP address.customNetworkInterfaceName to specify a custom name for the network interface attached to the private endpoint. If not specified, Azure generates a default name.subnet, privateEndpointName, and resourceGroupName properties are immutable and cannot be changed after creation. Modifying these requires recreating the private endpoint.Advanced Features
applicationSecurityGroups array with the resource IDs of your ASGs. The private endpoint’s IP configuration will be included in those security groups for network policy enforcement.pulumi package add azure-native network [ApiVersion].