Web App Needs a Custom Domain? Map a Custom DNS Name

Published on:

CloudTrips needs a recognizable address instead of the generated azurewebsites.net hostname. A custom domain mapping tells App Service to accept requests for a DNS name you own. DNS must also direct that name to the web app.

This trip is independent of earlier App Service trips. Create:

Resource group: rg-cloudtrips-customdomain-test-weu
Web app: app-cloudtrips-domain-dmytro-test-weu
App Service plan: asp-cloudtrips-domain-test-weu
Pricing tier: Basic B1
DNS provider for cloudtrips.dev: Vercel
Custom hostname: appservice.cloudtrips.dev

Custom domains require a paid App Service tier. You must also be able to edit the public DNS zone. If you do not control cloudtrips.dev, substitute a subdomain of a domain you control throughout the trip.

Create the Web App

Search for App Services, select Create > Web App, and enter:

Subscription: CloudTrips TEST
Resource group: rg-cloudtrips-customdomain-test-weu
Name: app-cloudtrips-domain-dmytro-test-weu
Publish: Code
Runtime stack: Node 24 LTS
Operating System: Linux
Region: West Europe
Linux Plan: Create new
Plan name: asp-cloudtrips-domain-test-weu
Pricing plan: Basic B1
Zone redundancy: Disabled

The web-app name must be globally unique. If it is unavailable, add a short suffix and use the resulting name consistently in the CNAME and commands. Select Review + create > Create.

Basic B1 is the lowest dedicated tier supported for this managed-certificate lab and is billable until deleted.

Create Web App page showing the standalone Node 24 Linux app on a Basic B1 plan

Deploy the Test Page

Create a local folder containing package.json:

{
  "name": "cloudtrips-custom-domain",
  "version": "1.0.0",
  "scripts": {
    "start": "node server.js"
  }
}

Add server.js:

const http = require('http');
const port = process.env.PORT || 8080;

http.createServer((request, response) => {
  response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  response.end('<h1>CloudTrips Custom Domain</h1>');
}).listen(port);

From that folder, package and deploy the files:

zip cloudtrips-domain.zip package.json server.js

az webapp deploy \
  --resource-group rg-cloudtrips-customdomain-test-weu \
  --name app-cloudtrips-domain-dmytro-test-weu \
  --src-path cloudtrips-domain.zip \
  --type zip

Confirm the default hostname works before changing DNS:

curl --fail \
  https://app-cloudtrips-domain-dmytro-test-weu-csf8bpczcqdmfngc.westeurope-01.azurewebsites.net

Azure generated this hostname for the app. Do not construct it from the app name: newer App Service hostnames can contain a unique suffix and regional label. Always copy Default domain from Overview or retrieve defaultHostName with the command below.

Understand the Two DNS Records

The authoritative DNS provider is the service that stores the official public DNS records for a domain. Internet DNS resolvers ask that provider for the answer. For cloudtrips.dev, the authoritative provider is Vercel DNS, so the following records must be created in Vercel—not in the App Service resource:

CNAME: Routes appservice.cloudtrips.dev to the web app
TXT:   Proves to Azure that you control appservice.cloudtrips.dev
Browser requests appservice.cloudtrips.dev

DNS asks Vercel for the official record

Vercel returns the Azure App Service hostname

The TXT record does not carry application traffic. It prevents another Azure customer from claiming your hostname merely by knowing its name.

Start the Custom Domain Mapping

Open app-cloudtrips-domain-dmytro-test-weu, select Settings > Custom domains, and choose Add custom domain:

Domain provider: All other domain services
TLS/SSL certificate: Add certificate later
Domain: appservice.cloudtrips.dev

Do not select Validate yet. Keep the pane open and note the DNS records it expects. Mapping the hostname before creating its certificate separates DNS validation from certificate issuance and makes failures easier to identify.

Add custom domain pane configured for appservice.cloudtrips.dev with Add certificate later selected

Create the Records in Vercel DNS

Retrieve the app hostname and its unique ownership-verification value:

az webapp show \
  --resource-group rg-cloudtrips-customdomain-test-weu \
  --name app-cloudtrips-domain-dmytro-test-weu \
  --query "{cname:defaultHostName,verificationId:customDomainVerificationId}" \
  --output yaml

Open the Vercel team that manages cloudtrips.dev, open the domain’s DNS records, and create:

Type: CNAME
Name: appservice
Value: app-cloudtrips-domain-dmytro-test-weu-csf8bpczcqdmfngc.westeurope-01.azurewebsites.net
TTL: Provider default

Type: TXT
Name: asuid.appservice
Value: Use the customDomainVerificationId returned by Azure
TTL: Provider default

Copy the verification ID exactly without quotes or spaces. Use the defaultHostName returned by Azure as the CNAME value; do not substitute a shorter hostname based on the app name. The CNAME must point directly to the app’s azurewebsites.net hostname so managed-certificate issuance and renewal can succeed.

Vercel DNS showing the appservice CNAME and asuid.appservice TXT records

Verify the public records from your Mac:

dig appservice.cloudtrips.dev CNAME +short
dig asuid.appservice.cloudtrips.dev TXT +short

The first command should return the web app hostname; the second should return the Azure verification ID. DNS propagation can take several minutes.

Validate and Add the Domain

Return to the Add custom domain pane and select Validate. Continue only after Azure shows green checks for the required records, then select Add.

The hostname should now appear in Custom domains with No binding. This is expected: DNS and domain ownership work, but HTTPS has no certificate yet.

App Service Custom domains page showing appservice.cloudtrips.dev mapped with no TLS binding

Allow DigiCert in CAA

Before requesting the certificate, check whether the parent domain already has CAA records:

dig cloudtrips.dev CAA +short

CAA means Certification Authority Authorization. These DNS records restrict which certificate authorities may issue certificates for the domain. If no CAA records exist, no issuer allowlist is applied. If at least one exists, the certificate authority used by App Service must be permitted explicitly.

cloudtrips.dev already allowed Let’s Encrypt, Google Trust Services, and Sectigo, but not DigiCert. App Service Managed Certificates use DigiCert, so Vercel DNS needs this additional record:

Name: @
Type: CAA
Value: 0 issue "digicert.com"
TTL: 60
Priority: Leave empty

Keep the existing CAA records. Here, 0 is the CAA flag, issue authorizes a certificate authority to issue ordinary certificates, and digicert.com is the authorized authority. A missing DigiCert authorization can make Azure show a misleading ResourceNotFound error while creating the managed certificate.

Vercel DNS showing the CAA record that allows DigiCert for cloudtrips.dev

Wait for DNS propagation and confirm that the public response includes DigiCert:

dig cloudtrips.dev CAA +short

Create the Managed Certificate

Open Settings > Certificates. On Managed certificates, select Add certificate:

Custom domain: appservice.cloudtrips.dev

Select Validate, then Add. Domain mapping must exist before App Service can issue this certificate. The App Service managed certificate is free, renews automatically while its requirements remain satisfied, and cannot be exported for use outside App Service.

Certificate issuance is asynchronous and can take several minutes. Wait until the certificate appears in Managed certificates before creating the binding. If the combined certificate-and-binding action remains busy, do not submit it repeatedly; refresh the certificate page and check the activity log.

Managed certificates page showing the issued certificate for appservice.cloudtrips.dev

Add the SNI SSL Binding

Return to Custom domains. Next to appservice.cloudtrips.dev, select Add binding:

Certificate: Select the managed certificate for appservice.cloudtrips.dev
TLS/SSL type: SNI SSL

SNI means Server Name Indication. During the TLS connection, the browser sends the requested hostname so App Service can present the correct certificate. Modern clients support SNI.

SNI SSL:      Client sends the hostname; multiple HTTPS sites share one IP
IP-based SSL: Hostname receives a dedicated inbound IP address

IP-based SSL is mainly for legacy clients without SNI support or workloads that require a dedicated IP. It can cost more and requires Standard tier or higher, whereas this B1 app supports SNI SSL. Select Add, then wait until the domain shows Secured.

Custom domains page showing appservice.cloudtrips.dev secured with an SNI SSL binding

Verify HTTPS

Request the application through its custom name:

curl --fail --show-error \
  https://appservice.cloudtrips.dev

Expected content includes:

CloudTrips Custom Domain

Inspect the certificate presented for the hostname:

openssl s_client \
  -connect appservice.cloudtrips.dev:443 \
  -servername appservice.cloudtrips.dev \
  </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

Do not use curl -k; it disables the certificate validation this test is intended to prove.

Browser showing the CloudTrips web app over HTTPS at appservice.cloudtrips.dev

Clean Up Safely

First delete the appservice CNAME and asuid.appservice TXT records from Vercel. Removing DNS before deleting the Azure app prevents a dangling CNAME that could create a subdomain-takeover risk.

Then delete the isolated Azure resource group to stop B1 charges:

az group delete --name rg-cloudtrips-customdomain-test-weu --yes

Confirm the cleanup:

az group exists --name rg-cloudtrips-customdomain-test-weu
dig appservice.cloudtrips.dev CNAME +short

The first command should return false; after DNS caches expire, the second should return no CNAME.