Azure Networking + Terraform

Build Azure Private Link with Terraform

Once you understand Azure Private Link conceptually, the next step is to build a private endpoint with Terraform. This helps turn private service access into reusable infrastructure as code that can be reviewed, versioned, and deployed consistently.

This page shows a practical Azure Private Link Terraform example, including a private endpoint, subnet, and private DNS pattern, because DNS is a key part of making Private Link work correctly in real environments. :contentReference[oaicite:1]{index=1}

Quick summary

Before going deeper, here is the simplest way to think about Azure Private Link Terraform.

What Terraform code that creates a private endpoint and usually its related private DNS pattern.
Why It makes private service access consistent, reviewable, and reusable across environments.
When Use it whenever you want repeatable private access to supported Azure services.
Where It fits into secure landing zones, data-service access, internal platforms, and private-first architectures.
Who Cloud, DevOps, platform, and security teams usually manage it.
How Using 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.
Simple idea: Terraform turns Private Link from a one-time portal setup into a reusable private-access blueprint.

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
Important reminder: A private endpoint can be created successfully but still fail functionally if the wrong subresource is chosen or DNS is not aligned.

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
Simple idea: Private Link without correct DNS often looks deployed but behaves incorrectly.

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.

Good habit: Always run 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.

Important mindset: Start with one clear private endpoint use case, then expand into DNS standards, subnet patterns, and reusable private-access modules.

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.
Strong next step: After this Private Link example, a natural progression is private DNS design, subnet planning for private endpoints, and broader private service access architecture.

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.
Common beginner trap: Just because Terraform created the private endpoint successfully does not mean your app is using the private path. DNS and real client testing still matter.

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.