Quick summary
Before going deeper, here is the simplest way to think about Azure Private Link Terraform.
azurerm_private_endpoint and often private DNS resources or a private DNS zone group. :contentReference[oaicite:2]{index=2}
Why use Terraform for Azure Private Link?
Private endpoints can be created manually in the Azure portal, but Terraform is better when you need repeatable deployment, reviewable DNS behavior, subnet consistency, and reusable infrastructure patterns.
- It keeps private access configuration in code instead of clicks.
- It makes private endpoint and DNS changes visible in Git and code review.
- It reduces manual mistakes with service connections and subresources.
- It helps teams standardize private service access across environments.
- It fits naturally into CI/CD and reusable network modules.
Terraform code
This example creates:
- an Azure resource group
- a virtual network and subnet
- a private DNS zone
- a VNet link to the private DNS zone
- a private endpoint connected to a target service
- a private DNS zone group on the private endpoint
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.100"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
}
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
address_space = ["10.40.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_subnet" "private_endpoint_subnet" {
name = "private-endpoint-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.40.1.0/24"]
}
resource "azurerm_private_dns_zone" "sql_dns" {
name = "privatelink.database.windows.net"
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "sql_dns_link" {
name = "sql-dns-link"
resource_group_name = azurerm_resource_group.rg.name
private_dns_zone_name = azurerm_private_dns_zone.sql_dns.name
virtual_network_id = azurerm_virtual_network.vnet.id
}
resource "azurerm_private_endpoint" "sql_pe" {
name = var.private_endpoint_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
subnet_id = azurerm_subnet.private_endpoint_subnet.id
private_service_connection {
name = "sql-private-connection"
private_connection_resource_id = var.target_resource_id
subresource_names = ["sqlServer"]
is_manual_connection = false
}
private_dns_zone_group {
name = "sql-dns-zone-group"
private_dns_zone_ids = [azurerm_private_dns_zone.sql_dns.id]
}
tags = {
environment = var.environment
managed_by = "terraform"
purpose = "private-service-access"
}
}
Code breakdown
Virtual network and subnet
The private endpoint needs to live inside a subnet in your virtual network, because a private endpoint gets a private IP from that subnet. Azure’s Terraform quickstart follows this same overall pattern. :contentReference[oaicite:3]{index=3}
Private DNS zone
Private DNS is included because clients usually need the service hostname to resolve to the private endpoint IP instead of the public endpoint.
Private DNS zone virtual network link
This lets the VNet use the private DNS zone for resolution so workloads inside the VNet can resolve the private hostname properly.
Private endpoint
The azurerm_private_endpoint resource creates the private endpoint itself and connects it to the target service resource.
Private service connection
This block defines which Azure resource the endpoint connects to, which subresource to use, and whether the connection is manual or automatic.
Private DNS zone group
This links the private endpoint to the private DNS zone so name resolution can work with the intended private hostname pattern.
Why this structure is useful
This example is practical because it includes both the private endpoint and the private DNS pattern. In real Azure environments, these are usually tightly connected rather than treated as separate concerns. :contentReference[oaicite:4]{index=4}
Important private endpoint fields
A good Private Link Terraform page should explain that private endpoints are not just “create resource and done.” A few fields matter a lot for whether the design actually works.
| Field | What it controls | Example |
|---|---|---|
| subnet_id | Which subnet will host the private endpoint IP | private-endpoint-subnet |
| private_connection_resource_id | The Azure resource you want to reach privately | Azure SQL Server resource ID |
| subresource_names | The specific service subresource for the private connection | sqlServer |
| is_manual_connection | Whether the connection is auto-approved or manually approved | false |
| private_dns_zone_ids | Which private DNS zones should be connected to the private endpoint | privatelink.database.windows.net |
Why DNS is included in the Terraform example
DNS is a core part of Private Link. Azure’s own Terraform quickstart for private endpoints includes private DNS because the hostname usually needs to resolve to the private endpoint IP instead of the public endpoint. :contentReference[oaicite:5]{index=5}
In practical terms, this means:
- the client should keep using the normal service hostname
- DNS should return the private endpoint IP
- the traffic should then stay on the private access path
Suggested variables file
Variables make Private Link Terraform code more reusable across development, testing, and production environments.
variable "resource_group_name" {
description = "Name of the Azure resource group"
type = string
}
variable "location" {
description = "Azure region for deployment"
type = string
default = "South Africa North"
}
variable "vnet_name" {
description = "Name of the Azure Virtual Network"
type = string
}
variable "private_endpoint_name" {
description = "Name of the Azure private endpoint"
type = string
}
variable "target_resource_id" {
description = "Resource ID of the Azure service to connect privately"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
Example terraform.tfvars
resource_group_name = "rg-network-dev-san-1"
location = "South Africa North"
vnet_name = "vnet-private-dev-san-1"
private_endpoint_name = "pe-sql-dev-san-1"
target_resource_id = "/subscriptions/xxxx/resourceGroups/rg-data-dev-san-1/providers/Microsoft.Sql/servers/sql-dev-san-1"
environment = "dev"
Suggested outputs
Outputs help expose private endpoint IDs and related values for later modules or validation steps.
output "private_endpoint_id" {
value = azurerm_private_endpoint.sql_pe.id
}
output "private_endpoint_name" {
value = azurerm_private_endpoint.sql_pe.name
}
output "private_dns_zone_name" {
value = azurerm_private_dns_zone.sql_dns.name
}
How to run it
A simple Terraform execution flow looks like this:
terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
In real teams, these steps often run through CI/CD after review rather than manually every time.
terraform fmt and terraform validate before planning or applying changes.
Real-world usage
This Private Link Terraform pattern can grow naturally into real private-access architecture workflows.
Private access to SQL or storage
App workloads access data services through private endpoints so those service paths stay private-first.
Internal platform services
Shared internal services are exposed privately to consuming VNets using private endpoints and private DNS.
Landing zone security baseline
Platform teams build standard Terraform modules so approved services use private endpoints consistently across environments.
Best practices
- Plan subnet placement for private endpoints carefully.
- Treat DNS as part of the Private Link design, not a separate afterthought.
- Use the correct service subresource name.
- Keep private endpoint naming clear and predictable.
- Document which services use private endpoints and how clients resolve them.
- Test from the actual client network path, not only from deployment output.
- Keep Private Link design aligned with NSG and routing assumptions.
- Use reusable Terraform modules as the number of endpoints grows.
Common mistakes
- Creating the private endpoint but not fixing DNS.
- Choosing the wrong subresource name.
- Using the wrong target resource ID.
- Assuming successful Terraform apply means the application path is correct.
- Forgetting to test from the actual consuming subnet or VNet.
- Hardcoding too much instead of using variables in reusable environments.
Frequently asked questions
What Terraform resource creates an Azure private endpoint?
The azurerm_private_endpoint resource creates an Azure private endpoint.
Why do Terraform Private Link examples include private DNS?
Because the service hostname usually needs to resolve to the private endpoint IP instead of the public endpoint for the private path to work correctly. :contentReference[oaicite:6]{index=6}
What is a subresource in Private Link?
It is the specific service target inside the Azure resource that the private endpoint should connect to, such as a specific data-plane endpoint type.
What should I learn after Azure Private Link Terraform?
A good next step is private DNS design, subnet planning for private endpoints, and broader private service access patterns.
Summary
Azure Private Link Terraform is one of the best practical ways to understand how private service access becomes infrastructure as code. It teaches you how private endpoints are declared, why DNS matters, and how reusable private connectivity patterns are built in real Azure environments.
Official Azure documentation
These official references are useful if you want deeper platform details for Azure Private Link, private endpoints, and Terraform-based DNS patterns.