Theoretical Foundation: Map an Existing Custom DNS Name to an App Service
1. Initial Intuitionβ
Imagine you created a business and have a physical address: "123 Flower Street, SΓ£o Paulo". But on your business cards, you put "www.mybusiness.com.br" because it's easier to remember and more professional. There's a redirection service that, when someone searches for your web address, knows it should go to "123 Flower Street".
In Azure, when you create an App Service, it gets a default address like myapp.azurewebsites.net. It works, but it's not professional and doesn't represent your brand. DNS (Domain Name System) is the internet's "redirection service": you configure your custom domain www.mycompany.com.br to point to the App Service, and then add that custom domain to the App Service so it accepts requests on that name.
Mapping a custom DNS name is the process of doing these two steps: proving to Azure that you own the domain, and then configuring DNS records to point to the App Service.
2. Contextβ
Why custom domains matterβ
By default, all App Services have URLs in the format {name}.azurewebsites.net. This is acceptable for development and testing, but has limitations for production:
- Doesn't represent brand identity
- Changes if the app is renamed or recreated
- Users distrust unrecognized URLs
- SEO (Search Engine Optimization) is impacted
- Azure Managed Certificate free SSL certificates don't work without custom domain
Custom domains are a prerequisite for custom TLS certificates and for any production application facing external users.
What depends on custom domainsβ
- TLS/SSL certificates: the certificate must be issued for the custom domain
- HTTPS: without custom domain, HTTPS only works for
azurewebsites.net - Email and other services: subdomains of the same main domain
- SEO and analytics: per-domain metrics
3. Building the Conceptsβ
3.1 How DNS works (essential concepts)β
DNS is a hierarchical name translation system. When a user types www.ecommerce.com.br in the browser, a series of queries happen:
Authoritative DNS is the server that has the definitive answer for a domain. It's where you create records (A, CNAME, TXT, etc.) that control where the domain points.
3.2 Relevant DNS record typesβ
CNAME (Canonical Name): points a name to another name. www.ecommerce.com.br CNAME to myapp.azurewebsites.net. DNS then resolves myapp.azurewebsites.net to get the final IP.
A Record: points a name directly to an IPv4 address. ecommerce.com.br A to 40.76.100.1.
TXT Record: arbitrary text associated with a DNS name. Used for ownership verification (proving you control the domain).
AAAA Record: same as A Record but for IPv6.
3.3 Critical CNAME limitation for root domain (apex/naked domain)β
The DNS standard doesn't allow using CNAME for the root domain (ecommerce.com.br without subdomain). This occurs because the DNS standard requires the root domain to have SOA and NS records, and CNAME cannot coexist with other records at the same level.
For the root domain, options are:
- Use an A Record pointing to the App Service IP (but the IP may change)
- Use ALIAS/ANAME Record (if your DNS provider supports it) which works like CNAME but is allowed at apex
- Use Azure DNS which supports Alias Records natively
- Redirect root domain to www at the DNS provider
3.4 The ownership verification processβ
Azure doesn't allow anyone to add any domain to an App Service. Before adding a custom domain, you need to prove you own the domain. Azure offers two methods:
CNAME method (for subdomains):
Create a CNAME record from www.ecommerce.com.br pointing to myapp.azurewebsites.net. This proves ownership because only someone who controls the domain's DNS can create this record.
TXT method (recommended to avoid downtime):
Create a TXT record at asuid.www.ecommerce.com.br with the App Service Domain Verification ID value. This allows verifying ownership without pointing traffic to the App Service yet (useful for migrations).
The Domain Verification ID is a unique identifier of your App Service that Azure uses to confirm the domain belongs to you. It's found at Portal > App Service > Custom domains > Domain verification ID.
3.5 Complete process for subdomains vs. root domainβ
For subdomain (www.ecommerce.com.br):
| Record | Name | Type | Value |
|---|---|---|---|
| Verification | asuid.www | TXT | <domain-verification-id> |
| Mapping | www | CNAME | myapp.azurewebsites.net |
For root domain (ecommerce.com.br) with Azure DNS:
| Record | Name | Type | Value |
|---|---|---|---|
| Verification | asuid | TXT | <domain-verification-id> |
| Mapping | @ | A (Alias) | myapp.azurewebsites.net |
4. Structural Viewβ
Complete custom domain mapping flowβ
How App Service receives requests with custom domainβ
The Host header of the HTTP request is what Azure uses to route to the correct App Service. Multiple App Services can share the same Azure IP; the host header distinguishes which app the request should go to.
5. Practical Operationβ
TTL (Time to Live) and DNS propagationβ
When you create or modify DNS records, changes aren't immediate. A DNS record's TTL defines how many seconds other servers can cache the response. A TTL of 3600 means it can take up to 1 hour for the change to propagate globally.
Strategy for migration with minimal downtime:
- Weeks before: Reduce current record TTL to 60-300 seconds
- At migration time: Change record to new destination
- Since TTL is small, propagation happens in minutes instead of hours
- After confirming it works: increase TTL back to normal (3600+)
Traffic Manager and Front Door with custom domainsβ
If the App Service is behind Azure Traffic Manager or Azure Front Door, DNS mapping points to these services, not directly to the App Service:
With Azure Front Door:
- CNAME
www.ecommerce.com.brpoints toecommerce.azurefd.net - Front Door routes to App Service backend
- TLS certificates are managed by Front Door, not directly by App Service
With Traffic Manager:
- CNAME
www.ecommerce.com.brpoints toecommerce.trafficmanager.net - Traffic Manager routes to App Service based on policies (latency, priority, etc.)
Non-obvious behaviorsβ
App Service validates the domain at each verification. When you try to add a custom domain, Azure queries DNS at that moment. If the DNS record hasn't propagated yet (high TTL), verification fails. This isn't an error: you need to wait for propagation.
Minimum tier for custom domains is Shared (D1). The Free tier (F1) doesn't support custom domains. You need at least the Shared tier to add a custom domain.
Wildcard custom domains require wildcard certificate.
You can add a wildcard custom domain (*.ecommerce.com.br) to App Service, but you'll need a wildcard certificate to enable HTTPS on it.
Multiple custom domains can point to the same App Service.
A single App Service can have dozens of custom domains. For example: www.company.com.br, company.com.br, store.company.com.br, all can point to the same app.
The domain verification ID is specific per App Service, not per domain. The same verification ID is used to verify any domain on that App Service. If you have 5 different domains on the same app, each one's TXT record uses the same verification ID.
6. Implementation Formsβ
Azure Portalβ
When to use: manual configuration, visual verification, single-use environments
Step-by-step via portal:
- Portal > App Service > Custom domains
- Copy the Domain verification ID (shown on screen)
- Go to your DNS provider (GoDaddy, Registro.br, Cloudflare, etc.)
- Create TXT record:
asuid.wwwwith value<domain-verification-id> - Create CNAME record:
wwwpointing tomyapp.azurewebsites.net - Wait for DNS propagation
- Return to portal > + Add custom domain
- Type
www.ecommerce.com.br - Click Validate; portal confirms records found
- If valid: Add custom domain
Azure CLIβ
# View App Service Domain Verification ID
az webapp show \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "customDomainVerificationId" \
--output tsv
# List existing custom domains
az webapp config hostname list \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--output table
# Add custom domain to App Service
# (requires DNS records to be already configured and propagated)
az webapp config hostname add \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--hostname "www.ecommerce.com.br"
# Add root domain (apex domain)
az webapp config hostname add \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--hostname "ecommerce.com.br"
# Remove custom domain from App Service
az webapp config hostname delete \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--hostname "www.ecommerce.com.br"
# Check if DNS is configured correctly before adding
# (useful for debugging propagation issues)
az webapp show \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "outboundIpAddresses" \
--output tsv
# Check outbound IPs (for creating correct A records)
az webapp show \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "possibleOutboundIpAddresses" \
--output tsv
# If using Azure DNS: create records directly via CLI
# Create TXT record for verification
az network dns record-set txt add-record \
--resource-group "rg-dns" \
--zone-name "ecommerce.com.br" \
--record-set-name "asuid.www" \
--value "<domain-verification-id>"
# Create CNAME record for www
az network dns record-set cname set-record \
--resource-group "rg-dns" \
--zone-name "ecommerce.com.br" \
--record-set-name "www" \
--cname "ecommerce-frontend.azurewebsites.net" \
--ttl 300
# Create Alias record (ANAME) for apex domain via Azure DNS
az network dns record-set a create \
--resource-group "rg-dns" \
--zone-name "ecommerce.com.br" \
--name "@" \
--target-resource "/subscriptions/<sub-id>/resourceGroups/rg-webapp/providers/Microsoft.Web/sites/ecommerce-frontend"
# Create verification TXT for apex domain
az network dns record-set txt add-record \
--resource-group "rg-dns" \
--zone-name "ecommerce.com.br" \
--record-set-name "asuid" \
--value "<domain-verification-id>"
Azure PowerShellβ
# View Domain Verification ID
(Get-AzWebApp -ResourceGroupName "rg-webapp" -Name "ecommerce-frontend").CustomDomainVerificationId
# Add custom domain
New-AzWebAppCustomHostnameBinding `
-ResourceGroupName "rg-webapp" `
-WebAppName "ecommerce-frontend" `
-Hostname "www.ecommerce.com.br"
# List custom domains
Get-AzWebAppCustomHostnameSsl `
-ResourceGroupName "rg-webapp" `
-WebAppName "ecommerce-frontend"
# If using Azure DNS: create records
# TXT record for verification
$txtRecord = New-AzDnsRecordConfig -Value "<domain-verification-id>"
New-AzDnsRecordSet `
-ResourceGroupName "rg-dns" `
-ZoneName "ecommerce.com.br" `
-Name "asuid.www" `
-RecordType TXT `
-Ttl 300 `
-DnsRecords $txtRecord
# CNAME record for www
$cnameRecord = New-AzDnsRecordConfig -Cname "ecommerce-frontend.azurewebsites.net"
New-AzDnsRecordSet `
-ResourceGroupName "rg-dns" `
-ZoneName "ecommerce.com.br" `
-Name "www" `
-RecordType CNAME `
-Ttl 300 `
-DnsRecords $cnameRecord
Bicep with Azure DNSβ
// Azure DNS Zone (if managing DNS in Azure)
resource dnsZone 'Microsoft.Network/dnsZones@2018-05-01' = {
name: 'ecommerce.com.br'
location: 'global'
}
// TXT record for verification (www)
resource txtVerification 'Microsoft.Network/dnsZones/TXT@2018-05-01' = {
parent: dnsZone
name: 'asuid.www'
properties: {
TTL: 300
TXTRecords: [
{
value: [webApp.properties.customDomainVerificationId]
}
]
}
}
// CNAME record for www
resource cnameWww 'Microsoft.Network/dnsZones/CNAME@2018-05-01' = {
parent: dnsZone
name: 'www'
properties: {
TTL: 300
CNAMERecord: {
cname: '${webApp.name}.azurewebsites.net'
}
}
}
// Alias record for apex domain (@ record)
resource aliasApex 'Microsoft.Network/dnsZones/A@2018-05-01' = {
parent: dnsZone
name: '@'
properties: {
TTL: 300
targetResource: {
id: webApp.id // Alias pointing to App Service
}
}
}
// Add custom domain to App Service
resource customHostname 'Microsoft.Web/sites/hostNameBindings@2022-09-01' = {
parent: webApp
name: 'www.ecommerce.com.br'
properties: {
siteName: webApp.name
hostNameType: 'Verified'
}
dependsOn: [cnameWww, txtVerification]
}
7. Control and Securityβ
Verify DNS propagation before adding domainβ
# Check if CNAME is propagated (from multiple perspectives)
# Via Linux/Mac:
dig CNAME www.ecommerce.com.br
# Via Windows:
nslookup -type=CNAME www.ecommerce.com.br
# Via online tool: https://dnschecker.org or https://whatsmydns.net
# Check if verification TXT is propagated
dig TXT asuid.www.ecommerce.com.br
# Check app resolution directly
dig A ecommerce-frontend.azurewebsites.net
Prevent domain takeoverβ
Domain takeover is when someone creates an App Service and maps a domain that points to a deleted App Service. If www.company.com.br has CNAME to company.azurewebsites.net and the company App Service is deleted, anyone can create a new App Service named company and receive the company's domain traffic.
Protection: always remove DNS records before or together with App Service deletion. Never leave CNAME records pointing to App Services that no longer exist.
# When deleting an app: first check and remove custom domains
az webapp config hostname list \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--output table
# Remove each custom domain before deleting the app
az webapp config hostname delete \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--hostname "www.ecommerce.com.br"
--hostname "www.ecommerce.com.br"
---
## 8. Decision Making
### Choosing the verification method
| Situation | Method | Reason |
|---|---|---|
| New domain, new app | CNAME (www) + TXT | One CNAME already verifies and redirects |
| Zero downtime migration | TXT only (first) | Verifies without redirecting traffic yet |
| Root domain (apex) | TXT + A or ALIAS | CNAME not allowed on apex |
| Wildcard | TXT on asuid + wildcard CNAME | Wildcard CNAME covers all subdomains |
### DNS Manager: Azure DNS vs. External Registrar
| Criteria | Azure DNS | External Registrar (GoDaddy, Cloudflare) |
|---|---|---|
| Native App Service integration | Native Alias Records | Requires manual A record |
| Centralized management | Yes, everything in Azure | Separate DNS |
| Propagation | Global DNS, default TTL 3600 | Depends on provider |
| Apex CNAME-like support | Yes (Alias Records) | Depends: Cloudflare yes, GoDaddy no |
| Cost | ~$0.50/zone + $0.40/million queries | Usually included with registration |
| Recommendation | Recommended if possible | Use if domain is already there and works |
---
## 9. Best Practices
**Always use the TXT method for verification in migrations.**
The TXT method verifies ownership without changing where traffic goes. This allows: verifying the domain on the destination App Service, configuring the TLS certificate, testing via local hosts file, and only then changing the CNAME to redirect production traffic.
**Reduce TTL before migrations.**
A TTL of 3600 means propagation of up to 1 hour. Weeks before migration, reduce to 60-300 seconds. After confirming it works, return to 3600.
**Use Azure DNS to leverage Alias Records.**
Azure DNS supports Alias Records natively for Azure resources, including App Service. This solves the apex domain problem without needing static IPs.
**Document all custom domains and their DNS records.**
When an app is migrated or deleted, it's easy to forget to remove DNS records. Maintain an inventory: which domain, which CNAME/A Record, which App Service, creation date.
**Configure alerts for anomalous DNS responses.**
Azure Monitor can alert when a custom domain starts returning errors (NXDOMAIN, SERVFAIL), which may indicate domain expiration, accidental DNS changes, or domain takeover.
---
## 10. Common Errors
| Error | Why it happens | How to avoid |
|---|---|---|
| Verification fails even after configuring DNS | DNS propagation not yet complete | Wait for propagation; check current TTL; use dig/nslookup |
| CNAME created for apex domain without working | DNS RFC doesn't allow CNAME on apex | Use Azure DNS with Alias Records or A record with static IP |
| App Service deleted but CNAME still exists | DNS not updated after deletion | Remove custom domain from app BEFORE deleting; update DNS |
| Free tier without custom domain support | Free tier limitation | Use minimum Shared (D1) for custom domains |
| Wrong Domain Verification ID in TXT | Incorrect ID copy | Copy ID directly from portal, without spaces |
| TXT created with wrong name | `asuid.www` vs `asuid` | For www: `asuid.www`; for apex: `asuid` without subdomain |
| App accessible on azurewebsites.net but not on custom domain | Binding not created in App Service | After configuring DNS, also add domain via CLI/portal |
### The most common production error
Performing migration without reducing TTL in advance. With TTL of 3600, after changing the CNAME to point to the new App Service, some users continue being sent to the old server for up to an hour. In e-commerce, this means lost orders and degraded experience. Always reduce TTL to 60 seconds at least 48 hours before any domain migration.
---
## 11. Operation and Maintenance
### Check status of all custom domains
```bash
# List all apps with their custom domains
az graph query -q "
Resources
| where type == 'microsoft.web/sites'
| mv-expand hostNames = properties.hostNames
| where hostNames !endswith 'azurewebsites.net'
| where hostNames !endswith 'scm.azurewebsites.net'
| project AppName=name, RG=resourceGroup, CustomDomain=hostNames"
# Verify if a domain is correctly mapped
DOMAIN="www.ecommerce.com.br"
EXPECTED_CNAME="ecommerce-frontend.azurewebsites.net"
ACTUAL=$(dig CNAME "$DOMAIN" +short)
if [ "$ACTUAL" = "${EXPECTED_CNAME}." ]; then
echo "DNS OK: $DOMAIN -> $ACTUAL"
else
echo "ALERT: DNS mismatch for $DOMAIN. Expected: $EXPECTED_CNAME, Current: $ACTUAL"
fi
Domain renewal and expiration monitoringβ
Custom domains depend on the domain registration being active. If the domain expires at the registrar, all associated custom domains stop working.
Configure alerts in your registrar for automatic renewal and keep payment information updated. Domain expiration is one of the most catastrophic and at the same time most preventable failures.
12. Integration and Automationβ
Automatic DNS + custom domain provisioning via pipelineβ
# Azure DevOps: create DNS records and custom domain as part of deployment
steps:
- task: AzureCLI@2
displayName: 'Configure DNS and Custom Domain'
inputs:
azureSubscription: 'prod-subscription'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
APP_NAME="ecommerce-frontend"
RESOURCE_GROUP="rg-webapp"
DNS_ZONE="ecommerce.com.br"
DNS_RG="rg-dns"
SUBDOMAIN="www"
# Get Domain Verification ID
VERIFICATION_ID=$(az webapp show \
--resource-group "$RESOURCE_GROUP" \
--name "$APP_NAME" \
--query "customDomainVerificationId" -o tsv)
# Create TXT record for verification
az network dns record-set txt add-record \
--resource-group "$DNS_RG" \
--zone-name "$DNS_ZONE" \
--record-set-name "asuid.${SUBDOMAIN}" \
--value "$VERIFICATION_ID"
# Create CNAME record
az network dns record-set cname set-record \
--resource-group "$DNS_RG" \
--zone-name "$DNS_ZONE" \
--record-set-name "$SUBDOMAIN" \
--cname "${APP_NAME}.azurewebsites.net" \
--ttl 300
# Wait for propagation (in automated environment, retry may be needed)
sleep 60
# Add custom domain to App Service
az webapp config hostname add \
--resource-group "$RESOURCE_GROUP" \
--webapp-name "$APP_NAME" \
--hostname "${SUBDOMAIN}.${DNS_ZONE}"
echo "Custom domain ${SUBDOMAIN}.${DNS_ZONE} configured successfully"
Terraform for DNS + custom domainβ
# Azure DNS Zone
resource "azurerm_dns_zone" "main" {
name = "ecommerce.com.br"
resource_group_name = azurerm_resource_group.dns.name
}
# TXT record for verification
resource "azurerm_dns_txt_record" "verification" {
name = "asuid.www"
zone_name = azurerm_dns_zone.main.name
resource_group_name = azurerm_resource_group.dns.name
ttl = 300
record {
value = azurerm_linux_web_app.main.custom_domain_verification_id
}
}
# CNAME record
resource "azurerm_dns_cname_record" "www" {
name = "www"
zone_name = azurerm_dns_zone.main.name
resource_group_name = azurerm_resource_group.dns.name
ttl = 300
record = "${azurerm_linux_web_app.main.name}.azurewebsites.net"
}
# Custom domain in App Service
resource "azurerm_app_service_custom_hostname_binding" "www" {
hostname = "www.ecommerce.com.br"
app_service_name = azurerm_linux_web_app.main.name
resource_group_name = azurerm_resource_group.app.name
depends_on = [
azurerm_dns_txt_record.verification,
azurerm_dns_cname_record.www
]
}
13. Final Summaryβ
Essential points:
- Mapping a custom domain involves two steps: configure DNS records at the provider, and add the domain to App Service
- Azure requires ownership verification via TXT record (
asuid.{subdomain}) before accepting the custom domain - For subdomains: use CNAME pointing to
{appname}.azurewebsites.net - For root domain (apex): CNAME is not allowed by DNS standard; use A record with App Service IP, or Alias Record if DNS provider supports (Azure DNS supports natively)
- Custom domains require minimum Shared (D1) tier; Free (F1) tier doesn't support
Critical differences:
- CNAME vs. A Record: CNAME dynamically resolves to App Service current IP (recommended for subdomains); A Record needs manual update if IP changes
- CNAME vs. Alias/ANAME: CNAME not allowed on apex; Alias/ANAME resolves like CNAME but is allowed on apex (DNS provider specific)
- CNAME vs. TXT method for verification: CNAME as verification already redirects traffic; TXT verifies without redirecting (ideal for migrations)
- TTL 300 vs. TTL 3600: Low TTL allows fast migration; high TTL means slow propagation
What needs to be remembered for AZ-104:
- The Domain Verification ID is in Portal > App Service > Custom domains
- The verification TXT record for
www.domain.comis created atasuid.www(not atwww) - For apex domain
domain.com, the TXT record goes atasuid(no subdomain) - CLI command to add:
az webapp config hostname add --hostname <domain> - Free tier doesn't support custom domains; minimum is Shared
- DNS propagation can take up to TTL value in seconds; for debugging, use
digornslookup - Domain takeover happens when CNAME points to deleted App Service; always remove DNS when deleting apps