All Posts
2026-03-21

Building a Custom Dynamic DNS Service with Azure

Building a Custom Dynamic DNS Service with Azure

Milestone 2a — DNS Migration and Home Lab Hosting

Published: March 2026 | Tags: Azure, DNS, PowerShell, Home Lab, Dynamic DNS

Overview

This guide covers how to build your own Dynamic DNS (DDNS) service using Azure DNS and a PowerShell script. If you want to host a website from your home lab but your ISP gives you a dynamic public IP address, this is the solution.

Rather than paying for a commercial DDNS service, we use Azure DNS — which we already have as part of our Microsoft 365 / Azure subscription — and a lightweight PowerShell script that runs on a schedule and keeps our DNS record updated automatically.

The Problem

Most home internet connections use a dynamic public IP address — meaning your ISP can change it at any time. Hosting a website at home with a dynamic IP creates two challenges:

  • Your domain's A record needs to always point to your current public IP
  • Commercial DDNS services (DynDNS, No-IP, etc.) add monthly costs and dependencies
  • The solution: use Azure DNS as your DNS provider and write a script that automatically updates the A record whenever your IP changes. Azure DNS provides REST APIs that make this straightforward.

    What You Need — Before You Start

    What You Will Have When Done

  • Azure DNS zone managing all DNS for your domain
  • Email still working via Microsoft 365 (MX, SPF, autodiscover records migrated)
  • A record for your home server updating automatically every 15 minutes
  • A Service Principal with least-privilege access (DNS Zone Contributor only)
  • Step 1 — Create the Azure DNS Zone

    In the Azure Portal, click Create a resource and search for DNS Zone.

    Figure 1 — Create a resource button in Azure Portal

    Figure 2 — Search for DNS Zone in the marketplace

    Figure 3 — Select Microsoft DNS Zone

    Click Create, then fill in the form:

  • Resource Group: Create new → name it rg-dns-kodakcafe
  • Name: kodakcafe.com (your domain name)
  • Child zone: Leave unchecked (only needed for subdomains like lab.kodakcafe.com)
  • Figure 4 — DNS Zone creation form

    Figure 5 — Review + Create summary

    Click Review + Create, wait for validation, then click Create. Once deployment completes:

    Figure 6 — Deployment complete

    Step 2 — Mirror Your Email DNS Records

    If Microsoft 365 is handling your email (as it is for kodakcafe.com), you need to create matching DNS records in Azure DNS before switching your nameservers. This ensures email keeps working without interruption.

    Click Record Sets, then Add to create the following three records:

    Figure 7 — Creating the MX record for Microsoft 365

    Figure 8 — Creating the SPF TXT record

    Figure 9 — Creating the autodiscover CNAME record

    After adding all three records your DNS zone should look like this:

    Figure 10 — DNS zone with all email records in place

    Step 3 — Update Nameservers in Squarespace

    Now that Azure DNS has all your email records, it is safe to point your domain at Azure. In Squarespace, navigate to your domain DNS settings and update the nameservers:

    Figure 11 — Squarespace DNS settings before nameserver change

    Figure 12 — Updating nameservers to Azure DNS

    Replace the existing nameservers with these four Azure DNS nameservers:

    ns1-09.azure-dns.com

    ns2-09.azure-dns.net

    ns3-09.azure-dns.org

    ns4-09.azure-dns.info

    Save the changes. Propagation typically takes 15-30 minutes. Verify with:

    nslookup -type=ns kodakcafe.com

    Expected output — you should see all four Azure nameservers listed. Also verify email is routing correctly:

    nslookup -type=mx kodakcafe.com

    Figure 13 — DNS propagation confirmed, Azure nameservers active

    Step 4 — Create the Home Lab A Record

    Add an A record for your home server. We will use a placeholder IP for now — the DDNS script will update it with your real public IP automatically.

    Figure 14 — Creating the home A record with placeholder IP

  • Name: home
  • Type: A
  • TTL: 300 (5 minutes — short so IP changes propagate quickly)
  • IP: 1.1.1.1 (placeholder — script will update this)
  • Step 5 — Create the Service Principal

    The DDNS script needs to authenticate to Azure and update DNS records. We use a Service Principal — a dedicated identity with the minimum permissions needed. This follows the principle of least privilege.

    The Service Principal will have exactly one permission: DNS Zone Contributor on the rg-dns-kodakcafe resource group. Nothing else.

    Step 5a — Register the Application in Entra ID

    From the Azure Portal home, click Entra ID → App registrations → New registration:

    Figure 15 — Entra ID icon on Azure Portal home

    Figure 16 — App registration form — name it sp-ddns-kodakcafe

    After registering, document the Client ID and Tenant ID — you will need these in the script:

    Figure 17 — Client ID and Tenant ID on the app registration overview

    Step 5b — Create a Client Secret

    Click Certificates & secrets → New client secret. Document the secret value immediately — it is only shown once:

    Figure 18 — Client secret value (copy this immediately, it will not be shown again)

    Step 5c — Assign DNS Zone Contributor Role

    Navigate to your DNS zone resource group (rg-dns-kodakcafe) → Access control (IAM) → Add role assignment:

    Figure 19 — Finding the DNS Zone Contributor role

    Figure 20 — Selecting the sp-ddns-kodakcafe service principal as the member

    Review and assign. The Service Principal now has exactly the permissions it needs — and nothing more:

    Figure 21 — Role assignment confirmed: DNS Zone Contributor

    Here is the full authentication flow the script uses:

    Figure 22 — How the script authenticates and updates DNS

    Step 6 — Create and Configure the PowerShell Script

    Create a file at C:\ddns\ddnsupdate.ps1 on your home server with the following script. Fill in your own values for TenantId, ClientId, ClientSecret, and SubscriptionId:

    $ErrorActionPreference = "Stop"

    # ================= CONFIG =================

    $TenantId = "your-tenant-id-here"

    $ClientId = "your-client-id-here"

    $ClientSecret = "your-client-secret-here"

    $SubscriptionId = "your-subscription-id-here"

    $ResourceGroup = "rg-dns-kodakcafe"

    $ZoneName = "kodakcafe.com"

    $RecordName = "home"

    $Ttl = 300

    # ==========================================

    $LogDir = "C:\Scripts\DDNS\Logs"

    New-Item -ItemType Directory -Path $LogDir -Force | Out-Null

    $LogFile = Join-Path $LogDir ("ddns-" + (Get-Date -Format "yyyyMMdd") + ".log")

    function Log($msg) {

    Add-Content -Path $LogFile -Value "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") $msg"

    }

    function Get-PublicIP { Invoke-RestMethod "https://api.ipify.org" }

    function Get-Token {

    $body = @{ grant_type = "client_credentials"; client_id = $ClientId;

    client_secret = $ClientSecret; resource = "https://management.azure.com/" }

    (Invoke-RestMethod -Method POST -ContentType "application/x-www-form-urlencoded"

    -Uri "https://login.microsoftonline.com/$TenantId/oauth2/token" -Body $body).access_token

    }

    $ip = (Get-PublicIP).ToString().Trim()

    Log "Detected public IP: $ip"

    $token = Get-Token

    $uri = "https://management.azure.com/subscriptions/${SubscriptionId}/resourceGroups/

    ${ResourceGroup}/providers/Microsoft.Network/dnsZones/${ZoneName}/A/${RecordName}

    ?api-version=2018-05-01"

    $headers = @{ Authorization = "Bearer $token" }

    $current = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers

    $currentIp = $current.properties.ARecords[0].ipv4Address

    Log "Current DNS IP: $currentIp"

    if ($currentIp -eq $ip) { Log "No update required."; exit 0 }

    $payload = @{ properties = @{ TTL = 300; ARecords = @(@{ ipv4Address = $ip }) } } | ConvertTo-Json -Depth 5

    Invoke-RestMethod -Method PUT -Uri $uri -Headers $headers -Body $payload -ContentType "application/json"

    Log "DNS updated from $currentIp to $ip"

    Test the script manually first:

    powershell.exe -ExecutionPolicy Bypass -File C:\ddns\ddnsupdate.ps1

    Verify by checking your public IP at whatismyipaddress.com, then running:

    nslookup home.kodakcafe.com

    The IP addresses should match.

    Step 7 — Schedule the Script with Task Scheduler

    Open Task Scheduler on your home server. Right-click and select Create Task (not Create Basic Task):

    Figure 23 — Task Scheduler — use Create Task, not Create Basic Task

    Configure the task:

    Figure 24 — General tab — name the task and set security options

    On the Triggers tab, add a new trigger set to repeat every 15 minutes indefinitely:

    Figure 25 — Trigger configuration — repeat every 15 minutes

    On the Actions tab, add a new action:

  • Program/Script: powershell.exe
  • Arguments: -NoProfile -ExecutionPolicy Bypass -File "C:\ddns\ddnsupdate.ps1"
  • Start in: C:\ddns
  • Figure 26 — Action configuration for the PowerShell script

    Save the task. Test it by right-clicking → Run:

    Figure 27 — Task running successfully

    Verifying Everything Works

    Run these three commands to confirm the full setup is working:

    # Confirm Azure is your DNS provider

    nslookup -type=ns kodakcafe.com

    # Confirm email is routing to Microsoft 365

    nslookup -type=mx kodakcafe.com

    # Confirm home server IP is correct

    nslookup home.kodakcafe.com

    Migrating from Home Lab to Azure App Service

    Milestone 2b — Custom Domain, SSL, and Decommissioning DDNS

    Published: March 2026 | Tags: Azure, App Service, Custom Domain, SSL, DNS

    Overview

    With KodakCafe deployed on Azure App Service (see Milestone 1), the next step is pointing our custom domain kodakcafe.com at the new Azure-hosted site and enabling HTTPS with a free managed SSL certificate.

    This guide also covers decommissioning the DDNS setup from Part 1 — since we are no longer hosting the site at home, that infrastructure is no longer needed.

    What You Need — Before You Start

    Step 1 — Get the App Service IP Address

    Run this command in Terminal to get the current IP of your App Service:

    nslookup kodakcafe-dnd9fuagfzf8fgfg.eastus-01.azurewebsites.net

    The IP address at the end of the output is what we need. In our case: 40.71.11.140

    Step 2 — Update DNS Records

    In the Azure Portal, navigate to your DNS zone (rg-dns-kodakcafe → kodakcafe.com → Recordsets) and make these three changes:

    Change 1 — Update the Root A Record

    Click the pencil icon next to the @ A record and update:

  • IP address: 40.71.11.140 (your App Service IP)
  • TTL: 300 seconds
  • Change 2 — Update the www CNAME

    Click the pencil icon next to the www CNAME record and update the value to:

    kodakcafe-dnd9fuagfzf8fgfg.eastus-01.azurewebsites.net

    Change 3 — Delete the home A Record

    Click the trash icon next to the home A record and confirm deletion. This record was used for DDNS home lab hosting and is no longer needed.

    Your final DNS record set should look like this:

    Step 3 — Add Custom Domain Verification Record

    Azure requires a TXT record to verify you own the domain before it will issue an SSL certificate. In the App Service portal, go to Settings → Custom Domains and copy the Custom Domain Verification ID.

    Then add a new TXT record in your DNS zone:

  • Name: asuid
  • Type: TXT
  • TTL: 300
  • Value: paste your full Verification ID here
  • Step 4 — Add the Custom Domain in App Service

    In the App Service portal, go to Settings → Custom Domains → + Add custom domain. Fill in the form:

  • Domain provider: All other domain services
  • TLS/SSL certificate: App Service Managed Certificate (free, auto-renewing)
  • TLS/SSL type: SNI SSL
  • Domain: kodakcafe.com
  • Azure will show a domain validation table. Both the A record and TXT record should validate. Click Add.

    Step 5 — Add SSL Binding

    After the custom domain is added, it will show "No binding" status. Click Add binding next to kodakcafe.com:

  • Certificate: Add new certificate
  • TLS/SSL type: SNI SSL
  • Source: Create App Service Managed Certificate
  • Click Validate, then Add. Azure will generate and install a free SSL certificate automatically. This takes 2-5 minutes.

    Watch the notifications bell in the top right for the completion message:

    Step 6 — Verify Everything

    Open your browser and navigate to https://kodakcafe.com. You should see:

  • Your site loading correctly
  • A padlock icon in the browser address bar
  • The URL showing https:// not http://
  • Also verify from Terminal that DNS is resolving correctly:

    nslookup kodakcafe.com 8.8.8.8

    This should return 40.71.11.140 (or your App Service IP).

    Troubleshooting — DNS Propagation Delays

    The most common issue when updating DNS is propagation delay. If Azure shows errors about the A record not matching, it is because DNS servers cached your old record.

    Key things to know:

  • The delay is determined by the TTL of the old record — if it was set to 18000 seconds (5 hours), you may wait up to 5 hours
  • You can check propagation progress using Google's DNS: nslookup kodakcafe.com 8.8.8.8
  • Flush your local DNS cache on Mac with: sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
  • You cannot force other DNS servers to update — they refresh on their own schedule
  • What You Built

  • kodakcafe.com pointing to Azure App Service
  • www.kodakcafe.com also pointing to Azure App Service
  • Free auto-renewing SSL certificate managed by Azure
  • HTTPS enforced — site accessible at https://kodakcafe.com
  • Old home lab DDNS infrastructure fully decommissioned
  • Next Steps — Milestone 3

  • Set up Azure Blob Storage for photo and media hosting
  • Create public CDN container for serving images
  • Update KodakCafe to serve photos from Blob Storage instead of local files
  • Set up GitHub repository and CI/CD pipeline for automatic deployments
  • KodakCafe | kodakcafe.com | Technology & Azure Series

    ← All Posts