Custom Service Should Expose Private Link? Configure a Private Link Service

Published on:

Private endpoints are not limited to Microsoft PaaS resources. A service provider can place a custom application behind a Standard Load Balancer and publish one internal frontend through Azure Private Link Service.

Build this provider-and-consumer path for the existing CloudTrips web service:

WEB02 consumer
    |
    v
Private Endpoint in snet-data
    |
    | Azure Private Link
    v
Private Link Service NAT IP in snet-pls
    |
    v
Internal Load Balancer frontend -> WEB01 / WEB02

This trip builds on A Web App Needs Traffic Distribution? Create a Load Balancer and Storage Must Stay Private? Create a Private Endpoint. Keep both web VMs and their running HTTP services, vnet-cloudtrips-test-weu, and snet-data. The existing public Load Balancer remains unchanged; this trip creates a separate internal Load Balancer because its frontend must use a private VNet address.

Understand Provider and Consumer

Private Link connects two roles:

  • The provider runs a service and makes it available privately. Here, CloudTrips provides the website running on WEB01 and WEB02. The provider creates the Load Balancer and Private Link Service.
  • The consumer wants to call that service from its own network. The consumer creates a private endpoint, which gives the provider’s service a private IP address inside the consumer’s VNet.

In a real design, the provider and consumer are often different companies, subscriptions, or VNets. For example, a software company provides a private API and a customer consumes that API without sending traffic over the Internet.

Consumer network                          Provider network
----------------                          ----------------
Client                                    WEB01 / WEB02
  |                                             ^
  v                                             |
Private Endpoint -- Azure Private Link --> Private Link Service
                                                ^
                                                |
                                      Internal Load Balancer

This low-cost lab places both roles in the same subscription and VNet. WEB01 and WEB02 remain the provider’s application servers, while WEB02 is also reused as the test client after the consumer private endpoint is created in snet-data. Reusing one VM makes the roles less visually separate, but the Azure resources still have distinct responsibilities:

Resource Role
pls-cloudtrips-web-test-weu Provider publishes the service
pep-cloudtrips-custom-web-test-weu Consumer connects to the published service

Private Link Service performs source NAT so overlapping provider and consumer address spaces do not create ambiguity. The application normally sees the Private Link Service NAT address, not the consumer’s original private IP.

Compare the Last Three Trips

The previous two trips connected to Azure Storage, which is a Microsoft-owned PaaS service. This trip uses the same Private Link platform differently: you are the provider and publish your own service running on VMs.

Question Storage private endpoint Storage service endpoint Custom Private Link Service
Who owns the service? Microsoft owns Azure Storage Microsoft owns Azure Storage CloudTrips owns WEB01 and WEB02
What does the client connect through? Private endpoint Service endpoint route Private endpoint connected to the provider’s Private Link Service
Destination address Private IP in the consumer VNet Public Storage IP Private IP in the consumer VNet
DNS behavior Private DNS resolves Storage to the private IP DNS still resolves to a public Storage IP Custom DNS is optional; this lab tests the private IP directly
Provider-side access control Storage approves the private endpoint connection Storage firewall allows snet-app CloudTrips approves the private endpoint connection
Load Balancer required? No No Yes, for the custom service in this lab
Public endpoint required? No; Storage public access was disabled Yes; it remains public but restricted by the Storage firewall No public endpoint is required for Private Link consumers

Use a private endpoint to Azure Storage when a Microsoft PaaS resource must appear privately inside your VNet. Use a service endpoint when reaching the PaaS public endpoint over the Azure backbone with simpler subnet-based access control is sufficient. Use Private Link Service when you own the application and want other consumers to reach it through private endpoints.

Private Link Service and private endpoints incur charges for provisioned time and processed data. Remove this lab when testing is complete.

Open Virtual networks > vnet-cloudtrips-test-weu > Settings > Subnets, select + Subnet, and configure:

Name: snet-pls
Starting address: 10.20.5.0
Subnet size: /24
Default outbound access: Disabled
Private link service network policy: Disabled

Leave delegation, NSG, route table, NAT Gateway, and service endpoints unconfigured. 10.20.5.0/24 does not overlap snet-app at 10.20.1.0/24, snet-data at 10.20.2.0/24, or the Application Gateway subnet at 10.20.4.0/24. Select Save.

Dedicated snet-pls subnet with Private Link Service network policies disabled

The equivalent CLI commands are:

az network vnet subnet create \
  --resource-group rg-cloudtrips-network-test-weu \
  --vnet-name vnet-cloudtrips-test-weu \
  --name snet-pls \
  --address-prefixes 10.20.5.0/24 \
  --default-outbound false

az network vnet subnet update \
  --resource-group rg-cloudtrips-network-test-weu \
  --vnet-name vnet-cloudtrips-test-weu \
  --name snet-pls \
  --disable-private-link-service-network-policies true

Create a Separate Internal Load Balancer

The existing lb-cloudtrips-web-test-weu is a public Load Balancer. Its Add frontend IP configuration screen therefore asks for a public IP address and does not offer VNet or subnet fields. Do not add a frontend there.

Search for Load balancers, select Create > Standard Load Balancer, and configure Basics:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-network-test-weu
Name: lb-cloudtrips-private-link-test-weu
Region: West Europe
SKU: Standard
Type: Internal
Tier: Regional

On Frontend IP configuration, add:

Name: fe-cloudtrips-private-link-test-weu
Virtual network: vnet-cloudtrips-test-weu
Subnet: snet-app
IP address assignment: Dynamic
Availability zone: Zone-redundant, if available

On Backend pools, add be-cloudtrips-private-link-test-weu for vnet-cloudtrips-test-weu with NIC configuration. Add the primary IP configurations of both WEB01 and WEB02 to this pool. A VM NIC can participate in backend pools on more than one Load Balancer, so this does not remove the VMs from the existing public Load Balancer.

On Inbound rules, add a Load Balancer rule and create its health probe:

Name: rule-private-link-http-80
IP version: IPv4
Frontend IP address: fe-cloudtrips-private-link-test-weu
Backend pool: be-cloudtrips-private-link-test-weu
Protocol: TCP
Port: 80
Backend port: 80
Health probe: Create new
Health probe name: probe-private-link-tcp-80
Health probe protocol: TCP
Health probe port: 80
Session persistence: None
Idle timeout: 15 minutes
TCP reset: Enabled
Floating IP: Disabled

Select Review + create > Create. Open the deployed internal Load Balancer and confirm that both web backends are healthy. This inbound rule does not configure outbound Internet access for the backend VMs; outbound connectivity is outside this lab and is not required for serving the test page.

Separate internal Load Balancer with a private frontend and both web VMs in its backend pool

Search for Private link services, select + Create, and configure Basics:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-network-test-weu
Name: pls-cloudtrips-web-test-weu
Region: West Europe

On Outbound settings, configure:

Load balancer: lb-cloudtrips-private-link-test-weu
Load balancer frontend IP address: fe-cloudtrips-private-link-test-weu
Source NAT virtual network: vnet-cloudtrips-test-weu
Source NAT subnet: snet-pls
Enable TCP proxy V2: No
Private IP address allocation: Dynamic

Keep Visibility restricted to the current subscription for this lab. Keep Auto-approval empty so the provider must explicitly approve consumer requests. Select Review + create > Create.

Private Link Service outbound settings bound to the internal Load Balancer frontend and snet-pls

After deployment, open the service overview and copy its Alias. Consumers can use this alias without receiving access to the provider’s subscription or resource IDs.

Create a Consumer Private Endpoint

In a production design, the private endpoint normally belongs to a separate consumer VNet—often in another subscription or tenant. This lab reuses the existing CloudTrips VNet to avoid another VNet and test VM, so snet-data simulates the consumer network while snet-app and snet-pls represent the provider side:

vnet-cloudtrips-test-weu
├── snet-data → simulated consumer private endpoint
├── snet-app  → provider Load Balancer and web VMs
└── snet-pls  → provider Private Link Service NAT addresses

The roles remain separate even though the subnets share one VNet. A realistic deployment would place the private endpoint in a consumer-owned VNet, such as vnet-cloudtrips-consumer-test-weu, and connect it to this provider Private Link Service without VNet peering. The consumer creates the private endpoint because it needs a local private IP that represents the provider’s service inside its own network.

Search for Private endpoints, select + Create, and configure:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-network-test-weu
Name: pep-cloudtrips-custom-web-test-weu
Network interface name: nic-pep-cloudtrips-custom-web-test-weu
Region: West Europe

On Resource, select Connect to an Azure resource by resource ID or alias, paste the Private Link Service alias, and enter:

Request message: CloudTrips consumer lab

On Virtual Network, configure:

Virtual network: vnet-cloudtrips-test-weu
Subnet: snet-data
Private IP configuration: Dynamically allocate IP address

No Azure-managed private DNS zone is offered for a custom service. Select Review + create > Create.

Consumer private endpoint created in snet-data for the custom Private Link Service

Approve the Consumer Connection

Open Private link services > pls-cloudtrips-web-test-weu > Private endpoint connections. Select the pending connection, choose Approve, add an optional description, and confirm.

The consumer endpoint should change to Approved. Approval is the provider’s consent boundary; creating a private endpoint does not automatically grant a consumer access when auto-approval is not configured.

Approved consumer private endpoint connection on the Private Link Service

Verify the state with CLI:

az network private-link-service show \
  --resource-group rg-cloudtrips-network-test-weu \
  --name pls-cloudtrips-web-test-weu \
  --query '{State:provisioningState,Alias:alias,Connections:privateEndpointConnections[].privateLinkServiceConnectionState.status}' \
  --output yaml

Expect State: Succeeded and an Approved connection.

Test the Custom Service Privately

Read the private endpoint’s IP address:

CUSTOM_SERVICE_NIC=$(az network private-endpoint show \
  --resource-group rg-cloudtrips-network-test-weu \
  --name pep-cloudtrips-custom-web-test-weu \
  --query 'networkInterfaces[0].id' \
  --output tsv)

CUSTOM_SERVICE_IP=$(az network nic show \
  --ids "$CUSTOM_SERVICE_NIC" \
  --query 'ipConfigurations[0].privateIPAddress' \
  --output tsv)

printf 'Custom service private IP: %s\n' "$CUSTOM_SERVICE_IP"

Start WEB02 if necessary and call that address from inside the VNet:

az vm start \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu

az vm run-command invoke \
  --resource-group rg-cloudtrips-network-test-weu \
  --name vm-cloudtrips-web02-test-weu \
  --command-id RunShellScript \
  --scripts "for request in 1 2 3 4 5 6; do curl --silent --show-error --connect-timeout 5 http://${CUSTOM_SERVICE_IP}/; done"

The output should contain responses from WEB01, WEB02, or both. The request entered through the consumer private endpoint, crossed Private Link, reached the internal Load Balancer frontend, and was distributed to a healthy backend.

WEB02 receiving custom web responses through the Private Link Service private endpoint

Deallocate WEB02 after testing. If you remove this lab, delete resources in this order:

  1. pep-cloudtrips-custom-web-test-weu
  2. pls-cloudtrips-web-test-weu
  3. lb-cloudtrips-private-link-test-weu
  4. snet-pls

Keep the existing public Load Balancer, VMs, VNet, and snet-data because other networking trips use them.