Skip to content
  • Home

Jack Pye

A blog about all things cloud related

Configuring Private DNS In Azure

March 28, 2025 by Jack Pye

Within Azure, Private DNS Zones are vital for managing private endpoints for services such as Azure Storage Accounts, Key Vault, and Recovery Services Vaults. However, these zones are often misconfigured, resulting in the inability to resolve private DNS endpoints.

This blog post will undertake the following:

  1. Creation of a module to create private DNS Zones and Links.
  2. Creation of the terraform module call.
  3. PowerShell Script to configure Conditional Forwarders.

All the code that is listed within this article has been saved to the following GitHub Repo. https://github.com/JackPye88/Azure_Private_DNS

1. Creating the Terraform Module

The first step is to create the Private DNS Zones within Azure. The steps below detail how to create Private DNS Zones using Terraform!

We will start with creating a module to create this. The directory tree shows the structure that we will take for this.

AZURE_PRIVATE_DNS_ZONE/
├── modules/
│ └── private_dns_zone/
│ ├── main.tf
│ ├── provider.tf
│ └── variables.tf
├── .gitignore
├── main.tf
└── provider.tf

Within the private_dns_zone folder, we will create several files to form the Private DNS Zones module.

  • provider.tf – This contains the configuration for what version of AzureRM should be used and any other providers.
  • main.tf – This contains the Terraform code to create the Private DNS Zone and the Private DNS Zone network link.
  • variables.tf – This contains the variables that will be passed into the module.

provider.tf

The Provider.tf file is configured with the following code:

terraform {
  required_providers {
    azurerm = {
      source                = "hashicorp/azurerm"
      version               = ">= 4.16.0"
    }
  }
}

main.tf

The code listed below forms the main part of the module and will create the following:

  • Private DNS Zone
  • Private DNS Zone Network Link Primary
  • Private DNS Zone Network Link Secondary (Only if the value passed into the module is not null)

Private DNS Zone.


# Private DNS Zone creation
resource "azurerm_private_dns_zone" "create" {
  name                = var.dns_zone_name
  resource_group_name = var.resource_group_name
  tags = merge( var.tags,
 {"creationdate" = formatdate("DD-MM-YYYY", timestamp()),
  })
lifecycle {
  ignore_changes = [ tags["creationdate"] ]
}
}
# Private DNS Zone Virtual Network Link Primary
resource "azurerm_private_dns_zone_virtual_network_link" "primary" {
  name                = "vnet-link-${var.dns_zone_name}-identity-spoke-primary"
  resource_group_name = var.resource_group_name
  private_dns_zone_name = azurerm_private_dns_zone.example.name
  virtual_network_id  = var.primary_virtual_network_id
  depends_on          = [azurerm_private_dns_zone.example]
}
# Private DNS Zone Virtual Network Link Secondary

resource "azurerm_private_dns_zone_virtual_network_link" "secondary" {
  count                 = var.secondary_virtual_network_id != null ? 1 : 0
  name                  = "vnet-link-${var.dns_zone_name}-identity-secondary"
  resource_group_name   = var.resource_group_name
  private_dns_zone_name = azurerm_private_dns_zone.example.name
  virtual_network_id    = var.secondary_virtual_network_id
  depends_on            = [azurerm_private_dns_zone.example]
}

Variables.tf

The following variables have been defined for the module.

variable "dns_zone_name" {
  type        = string
  description = "The name of the private DNS zone."
}

variable "resource_group_name" {
  type        = string
  description = "The name of the resource group."
}

variable "primary_virtual_network_id" {
  type        = string
  description = "Primary virtual network ID to link to."
}

variable "secondary_virtual_network_id" {
  type        = string
  description = "Optional secondary virtual network ID to link to."
  default     = null
}

variable "tags" {
  type        = map(string)
  default     = {}
  description = "A map of tags to assign to the resource."
}

2. Calling The Terraform Module

Now that the module configuration has been defined, we will look at how to call the module.

There are two required files to create the module call.

  • provider.tf
  • main.tf

provider.tf

This is an example provider.tf that can be used for the module call.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version               = ">= 4.16.0"
    }
  }

}
provider "azurerm" {
  subscription_id = "Connectivity Subscription - ID UPDATE ME!"
    features {}

}

provider "azurerm" {
  alias   = "identity"
    subscription_id = "Identity Subscription ID - UPDATE ME!"
  features {}

}

Main.tf

The main.tf definition listed below shows the module call for the DNS and comprises 3 sections.

  1. Locals this has been configured with a list of private DNS zones that can be used by the module call. This enables the module to loop through this list and create a DNS zone and DNS virtual link for each.
  2. Data lookups are undertaken to get the VNet ID for the primary and secondary spoke virtual networks within the Identity subscription.
  3. Then there is the module call, module “private_dns_zones”. From here, we pass the following key values:
    • Module Source Location
    • DNS Zone Name
    • Resource Group Name for the private DNS zones
    • Primary & Secondary VNet IDs
    • Tags to be applied to the private DNS zones


locals {
  dns_zones = [
    "privatelink.adf.azure.com",
    "privatelink.afs.azure.net",
    "privatelink.agentsvc.azure-automation.net",
    "privatelink.analysis.windows.net",
    "privatelink.api.azureml.ms",
    "privatelink.azconfig.io",
    "privatelink.azure-api.net",
    "privatelink.azure-automation.net",
    "privatelink.azure-devices-provisioning.net",
    "privatelink.azure-devices.net",
    "privatelink.azurecr.io",
    "privatelink.azurehdinsight.net",
    "privatelink.azurehealthcareapis.com",
    "privatelink.azurestaticapps.net",
    "privatelink.azuresynapse.net",
    "privatelink.azurewebsites.net",
    "privatelink.batch.azure.com",
    "privatelink.blob.core.windows.net",
    "privatelink.cassandra.cosmos.azure.com",
    "privatelink.cognitiveservices.azure.com",
    "privatelink.database.windows.net",
    "privatelink.datafactory.azure.net",
    "privatelink.dev.azuresynapse.net",
    "privatelink.developer.azure-api.net",
    "privatelink.dfs.core.windows.net",
    "privatelink.dicom.azurehealthcareapis.com",
    "privatelink.digitaltwins.azure.net",
    "privatelink.directline.botframework.com",
    "privatelink.documents.azure.com",
    "privatelink.eventgrid.azure.net",
    "privatelink.file.core.windows.net",
    "privatelink.gremlin.cosmos.azure.com",
    "privatelink.guestconfiguration.azure.com",
    "privatelink.his.arc.azure.com",
    "privatelink.kubernetesconfiguration.azure.com",
    "privatelink.managedhsm.azure.net",
    "privatelink.mariadb.database.azure.com",
    "privatelink.media.azure.net",
    "privatelink.mongo.cosmos.azure.com",
    "privatelink.monitor.azure.com",
    "privatelink.mysql.database.azure.com",
    "privatelink.notebooks.azure.net",
    "privatelink.ods.opinsights.azure.com",
    "privatelink.oms.opinsights.azure.com",
    "privatelink.pbidedicated.windows.net",
    "privatelink.postgres.database.azure.com",
    "privatelink.prod.migration.windowsazure.com",
    "privatelink.purview.azure.com",
    "privatelink.purviewstudio.azure.com",
    "privatelink.queue.core.windows.net",
    "privatelink.redis.cache.windows.net",
    "privatelink.redisenterprise.cache.azure.net",
    "privatelink.search.windows.net",
    "privatelink.service.signalr.net",
    "privatelink.servicebus.windows.net",
    "privatelink.siterecovery.windowsazure.com",
    "privatelink.sql.azuresynapse.net",
    "privatelink.table.core.windows.net",
    "privatelink.table.cosmos.azure.com",
    "privatelink.tip1.powerquery.microsoft.com",
    "privatelink.token.botframework.com",
    "privatelink.uks.backup.windowsazure.com",
    "privatelink.ukw.backup.windowsazure.com",
    "privatelink.vaultcore.azure.net",
    "privatelink.web.core.windows.net",
    "privatelink.webpubsub.azure.com",
  ]
}

data "azurerm_virtual_network" "primary" {
    provider = azurerm.identity
  name                = "vnet-jpd-iden-spoke-uks-001"
  resource_group_name = "rg-jpd-iden-spoke-uks-001"
}
data "azurerm_virtual_network" "secondary" {
        provider = azurerm.identity

  name                = "vnet-jpd-iden-spoke-ukw-001"
  resource_group_name = "rg-jpd-iden-spoke-ukw-001"
}

module "private_dns_zones" {

  for_each             = toset(local.dns_zones)
  source               = "./modules/private_dns_zone"
  dns_zone_name        = each.value
  resource_group_name  = "rg-jpd-con-dns-uks-001"
  primary_virtual_network_id   = data.azurerm_virtual_network.primary.id #VNET ID OF Network Primary Domain Controller is on
  secondary_virtual_network_id = data.azurerm_virtual_network.secondary.id #VNET ID OF Network Secondary Domain Controller is on
 tags = {
    creationdate = "16.10.2024", # Date of creation
  deployedBy   = "jack@jackpye.co.uk", # Deployed by email
  approvedby   = "joe@bloggs.com", # Approved by email
  owner        = "joe@bloggs.com", # Customer Owner email
  BU           = "IT", # Business Unit
  role         = "Private DNS Zone"
  environment  = "Landing Zone Platform" # Environment description
 }
}

We have now created Private DNS Zones and linked them with the Virtual Network that the Domain Controllers will be running on!

3. Conditional Forwarders Configuration

The next step is the key step that is often forgotten! We need to configure the DNS server on the primary domain controller initially to forward all private DNS resolutions to the Azure DNS IP. We need to ensure that this is undertaken only on the Domain Controllers residing in Azure. If we have Domain Controllers on-premises that require the ability to resolve DNS records of the Private DNS Zones, we will need to create additional conditional forwarders to forward the traffic to the Azure Domain Controllers. This article on Microsoft Learn also explains the process. https://learn.microsoft.com/en-us/azure/storage/files/storage-files-networking-dns

The PowerShell script below needs to be run on the Azure Domain Controllers. This will forward the private DNS Zones listed within the CSV to the Azure DNS IP 168.63.129.16 so that it can be resolved.

Write-host @"
Starting script to create New Conditional Forwarder zones on Azure Domain Controllers.
"@


$DNSzones = Import-Csv C:\Temp\AzureprivateDnsZones.csv

foreach ($PrivateDNSzone in $DNSzones) {
    
    if ($PrivateDNSzone.NAME -like "privatelink.*") {
        $PrivateDNSzone.NAME = $PrivateDNSzone.NAME.Substring($("privatelink.").Length)
    }

    Write-Host "Creating new Conditional Forwarder zone for $($PrivateDNSzone.NAME)"

    Add-DnsServerConditionalForwarderZone -Name $PrivateDNSzone.NAME -MasterServers "168.63.129.16"  # Azure-provided DNS IP
}

Write-Host @"
Ending script. 
"@

Write-Host "New Conditional Forwarder zones created for $($DNSzones.Count) zones"

The above will get private DNS resolving for Azure. If there are any on-premises domain controllers, then the following PowerShell script will need to be run on each on-prem DC.

Write-host @"
Starting script to create New Conditional Forwarder zones in On premises Domain Controllers.

"@

$AzureDCs = @("10.0.0.4","10.0.1.4")  # Replace with your actual Azure DNS IPs
$DNSzones = import-csv c:\temp\AzureprivateDnsZones.csv


foreach($PrivateDNSzone in $DNSzones){
    
    if($PrivateDNSzone.NAME -like "privatelink.*"){
        $PrivateDNSzone.NAME = $PrivateDNSzone.NAME.substring($("privatelink.").Length)
    }

    write-host "Creating new Conditional Forwarder zone for"$PrivateDNSzone.NAME
    Add-DnsServerConditionalForwarderZone -Name $PrivateDNSzone.NAME -MasterServers $AzureDCs

}

write-host @"
    
    Ending script. 

"@
write-host "New Conditional Forwarder zones created for"$DNSzones.count"Zones"

The CSV file required for these conditional forwarders will also be uploaded to the GitHub repository.

Related

Post navigation

Previous Post:

Azure DevOps Docker Pipeline to Azure Container Registry

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *


Recent Posts

  • Configuring Private DNS In Azure
  • Azure DevOps Docker Pipeline to Azure Container Registry
  • Azure App Service Linux Container – Confirm Outbound Public IP
  • Stopping the Azure Application Gateway

Archives

  • March 2025
  • August 2022
  • May 2022

Categories

  • App Service
  • Application Gateway
  • Azure
  • Azure Container Registry
  • Azure DevOps
  • Azure Pipelines
  • Docker
  • Powershell
  • Private DNS
  • Terraform

Follow Me On LinkedIn

  • LinkedIn
© 2025 Jack Pye | WordPress Theme by Superbthemes