Azure Storage Custom Domains Reference¶
Comparative positioning note
This document is written from the perspective of Microsoft Azure, Cloud Scale Analytics, and CSA Loom. Any description of third-party or competing products, services, pricing, or capabilities is derived from publicly available documentation and sources believed accurate at the time of writing, and is provided for general comparison only. We do not claim expertise in, or authority over, any non-Microsoft product or service; the respective vendor's official documentation is the authoritative source for their offerings, which may change over time. Nothing here is intended to disparage any vendor — where a competing product has genuine advantages, we aim to note them honestly. Verify all third-party details against the vendor's current official documentation before making decisions.
Home > Reference > Storage > Custom Domains
Comprehensive reference guide for configuring custom domains for Azure Storage accounts, including CDN integration, HTTPS support, and best practices for cloud-scale analytics workloads.
Table of Contents¶
- Overview
- Custom Domain Configuration
- CDN Integration
- HTTPS and SSL Certificates
- DNS Configuration
- Domain Validation
- Advanced Scenarios
- Performance Optimization
- Troubleshooting
Overview¶
What is Custom Domain Support?¶
Custom domains allow you to access Azure Storage resources using your own domain name instead of the default Azure Storage endpoint.
Supported Services¶
| Service | Default Endpoint | Custom Domain Support | HTTPS Support |
|---|---|---|---|
| Blob Storage | {account}.blob.core.windows.net | Yes (direct or CDN) | Yes (CDN only) |
| Static Website | {account}.z13.web.core.windows.net | Yes (CDN recommended) | Yes (CDN only) |
| Azure Data Lake Gen2 | {account}.dfs.core.windows.net | Limited (CDN required) | Yes (CDN only) |
| File Storage | {account}.file.core.windows.net | No | N/A |
| Table Storage | {account}.table.core.windows.net | No | N/A |
| Queue Storage | {account}.queue.core.windows.net | No | N/A |
Custom Domain Architecture¶
┌──────────────────────────────────────────────────────────┐
│ Custom Domain Architecture Options │
├──────────────────────────────────────────────────────────┤
│ │
│ Option 1: Direct Mapping (HTTP only) │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │ User │────────>│ DNS (CNAME) │ │
│ └──────────────┘ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Azure Storage │ │
│ │ (HTTP only) │ │
│ └──────────────────┘ │
│ │
│ Option 2: CDN Integration (HTTP + HTTPS) │
│ ┌──────────────┐ ┌─────────────────┐ │
│ │ User │────────>│ DNS (CNAME) │ │
│ └──────────────┘ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Azure CDN │ │
│ │ (HTTPS support) │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Azure Storage │ │
│ └──────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
Custom Domain Configuration¶
Prerequisites¶
- Azure Storage account (General Purpose v2 or Blob Storage)
- Domain name registered with domain registrar
- Access to domain's DNS settings
- Azure CDN profile (required for HTTPS)
Direct CNAME Mapping (HTTP Only)¶
Configuration Steps¶
- Verify domain ownership
- Create CNAME record
- Register custom domain with storage account
- Validate configuration
Azure CLI - Direct Mapping¶
# Step 1: Create storage account
az storage account create \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--location eastus \
--sku Standard_GRS \
--kind StorageV2 \
--https-only false # Required for direct custom domain
# Step 2: Get storage account endpoint
BLOB_ENDPOINT=$(az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query primaryEndpoints.blob -o tsv)
echo "Create CNAME record: data.contoso.com -> ${BLOB_ENDPOINT}"
# Step 3: Register custom domain (after CNAME is created)
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--custom-domain "data.contoso.com"
# Verify custom domain
az storage account show \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--query customDomain
PowerShell - Direct Mapping¶
# Create storage account
New-AzStorageAccount `
-ResourceGroupName "rg-analytics-prod" `
-Name "stadatalake001" `
-Location "eastus" `
-SkuName "Standard_GRS" `
-Kind "StorageV2"
# Get blob endpoint
$storageAccount = Get-AzStorageAccount `
-ResourceGroupName "rg-analytics-prod" `
-Name "stadatalake001"
$blobEndpoint = $storageAccount.PrimaryEndpoints.Blob
Write-Host "Create CNAME: data.contoso.com -> $blobEndpoint"
# Set custom domain
Set-AzStorageAccount `
-ResourceGroupName "rg-analytics-prod" `
-Name "stadatalake001" `
-CustomDomainName "data.contoso.com" `
-UseSubDomain $false
Indirect CNAME Mapping (Zero Downtime)¶
For production environments, use indirect mapping to avoid downtime during validation.
# Step 1: Create asverify CNAME record
# CNAME: asverify.data.contoso.com -> asverify.stadatalake001.blob.core.windows.net
# Step 2: Register custom domain with UseSubDomain
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--custom-domain "data.contoso.com" \
--use-subdomain true
# Step 3: Create final CNAME record
# CNAME: data.contoso.com -> stadatalake001.blob.core.windows.net
# Step 4: Update to direct mapping
az storage account update \
--name stadatalake001 \
--resource-group rg-analytics-prod \
--custom-domain "data.contoso.com" \
--use-subdomain false
CDN Integration¶
Why Use Azure CDN?¶
| Feature | Direct Mapping | With CDN |
|---|---|---|
| HTTPS Support | ❌ No | ✅ Yes |
| Custom SSL Certificates | ❌ No | ✅ Yes |
| Global Performance | Limited | ✅ Yes |
| Caching | No | ✅ Yes |
| DDoS Protection | Basic | ✅ Enhanced |
| Cost | Lower | Higher |
CDN Configuration Steps¶
Azure CLI - CDN Setup¶
# Step 1: Create CDN profile
az cdn profile create \
--name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--sku Standard_Microsoft \
--location Global
# Step 2: Create CDN endpoint
az cdn endpoint create \
--name data-contoso \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--origin stadatalake001.blob.core.windows.net \
--origin-host-header stadatalake001.blob.core.windows.net \
--enable-compression true \
--content-types-to-compress "application/json" "text/css" "text/javascript" \
--query-string-caching-behavior BypassCaching
# Step 3: Add custom domain to CDN endpoint
az cdn custom-domain create \
--endpoint-name data-contoso \
--hostname data.contoso.com \
--name data-contoso-com \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod
# Step 4: Enable HTTPS on custom domain
az cdn custom-domain enable-https \
--endpoint-name data-contoso \
--name data-contoso-com \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--min-tls-version "1.2"
Bicep - CDN Configuration¶
// Storage account
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: 'stadatalake001'
location: location
sku: {
name: 'Standard_GRS'
}
kind: 'StorageV2'
properties: {
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: true
}
}
// CDN profile
resource cdnProfile 'Microsoft.Cdn/profiles@2023-05-01' = {
name: 'cdn-analytics-prod'
location: 'Global'
sku: {
name: 'Standard_Microsoft'
}
}
// CDN endpoint
resource cdnEndpoint 'Microsoft.Cdn/profiles/endpoints@2023-05-01' = {
parent: cdnProfile
name: 'data-contoso'
location: 'Global'
properties: {
originHostHeader: '${storageAccount.name}.blob.${environment().suffixes.storage}'
isHttpAllowed: true
isHttpsAllowed: true
queryStringCachingBehavior: 'BypassCaching'
isCompressionEnabled: true
contentTypesToCompress: [
'application/json'
'text/css'
'text/javascript'
'text/html'
]
origins: [
{
name: 'origin1'
properties: {
hostName: '${storageAccount.name}.blob.${environment().suffixes.storage}'
httpPort: 80
httpsPort: 443
originHostHeader: '${storageAccount.name}.blob.${environment().suffixes.storage}'
}
}
]
}
}
// Custom domain
resource customDomain 'Microsoft.Cdn/profiles/endpoints/customDomains@2023-05-01' = {
parent: cdnEndpoint
name: 'data-contoso-com'
properties: {
hostName: 'data.contoso.com'
}
}
HTTPS and SSL Certificates¶
CDN-Managed Certificates¶
Azure CDN provides free, automatically managed SSL certificates.
# Enable HTTPS with CDN-managed certificate
az cdn custom-domain enable-https \
--endpoint-name data-contoso \
--name data-contoso-com \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--min-tls-version "1.2"
# Check HTTPS provisioning status
az cdn custom-domain show \
--endpoint-name data-contoso \
--name data-contoso-com \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--query customHttpsProvisioningState
Custom SSL Certificates (Key Vault)¶
# Store certificate in Key Vault
az keyvault certificate import \
--vault-name kv-analytics-prod \
--name ssl-data-contoso \
--file certificate.pfx \
--password "cert-password"
# Enable HTTPS with custom certificate
az cdn custom-domain enable-https \
--endpoint-name data-contoso \
--name data-contoso-com \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--user-cert-group-name rg-analytics-prod \
--user-cert-vault-name kv-analytics-prod \
--user-cert-secret-name ssl-data-contoso \
--user-cert-secret-version "latest" \
--user-cert-protocol-type "sni" \
--min-tls-version "1.2"
Certificate Requirements¶
| Requirement | Details |
|---|---|
| Format | PFX or PEM |
| Key Type | RSA (minimum 2048-bit) |
| Certificate Type | Domain Validation (DV) or higher |
| Valid Period | Active and not expired |
| Chain | Complete certificate chain included |
| Private Key | Must be included and not password protected (for PEM) |
DNS Configuration¶
CNAME Record Configuration¶
For Direct Mapping¶
Record Type: CNAME
Name: data (or subdomain of your choice)
Value: stadatalake001.blob.core.windows.net
TTL: 3600 (1 hour)
For CDN Mapping¶
Record Type: CNAME
Name: data (or subdomain of your choice)
Value: data-contoso.azureedge.net
TTL: 3600 (1 hour)
For Zero-Downtime Validation¶
Step 1: Validation CNAME
Record Type: CNAME
Name: asverify.data
Value: asverify.stadatalake001.blob.core.windows.net
TTL: 3600
Step 2: Final CNAME (after validation)
Record Type: CNAME
Name: data
Value: stadatalake001.blob.core.windows.net
TTL: 3600
DNS Provider Examples¶
Azure DNS¶
# Create DNS zone
az network dns zone create \
--resource-group rg-analytics-prod \
--name contoso.com
# Create CNAME record
az network dns record-set cname create \
--resource-group rg-analytics-prod \
--zone-name contoso.com \
--name data \
--ttl 3600
az network dns record-set cname set-record \
--resource-group rg-analytics-prod \
--zone-name contoso.com \
--record-set-name data \
--cname data-contoso.azureedge.net
Route 53 (AWS)¶
{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "data.contoso.com",
"Type": "CNAME",
"TTL": 3600,
"ResourceRecords": [{
"Value": "data-contoso.azureedge.net"
}]
}
}]
}
Cloudflare¶
# Using Cloudflare API
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "data",
"content": "data-contoso.azureedge.net",
"ttl": 3600,
"proxied": false
}'
Domain Validation¶
Validation Methods¶
| Method | Downtime | Complexity | Use Case |
|---|---|---|---|
| Direct CNAME | Yes (minutes) | Low | Development, testing |
| asverify Method | No | Medium | Production (recommended) |
| CDN Custom Domain | Minimal | Low | Production with HTTPS |
Validation Scripts¶
PowerShell Validation¶
# Validate CNAME record
function Test-CNameRecord {
param(
[string]$CustomDomain,
[string]$ExpectedTarget
)
try {
$cnameRecord = Resolve-DnsName -Name $CustomDomain -Type CNAME -ErrorAction Stop
if ($cnameRecord.NameHost -eq $ExpectedTarget) {
Write-Host "✅ CNAME record configured correctly" -ForegroundColor Green
Write-Host " $CustomDomain -> $($cnameRecord.NameHost)"
return $true
} else {
Write-Host "❌ CNAME record mismatch" -ForegroundColor Red
Write-Host " Expected: $ExpectedTarget"
Write-Host " Actual: $($cnameRecord.NameHost)"
return $false
}
}
catch {
Write-Host "❌ CNAME record not found: $CustomDomain" -ForegroundColor Red
return $false
}
}
# Test custom domain connectivity
function Test-CustomDomainConnectivity {
param(
[string]$CustomDomain,
[string]$TestPath = ""
)
$url = "https://$CustomDomain/$TestPath"
try {
$response = Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Stop
Write-Host "✅ Custom domain accessible via HTTPS" -ForegroundColor Green
Write-Host " Status Code: $($response.StatusCode)"
return $true
}
catch {
Write-Host "❌ Custom domain not accessible: $_" -ForegroundColor Red
return $false
}
}
# Run validation
Test-CNameRecord -CustomDomain "data.contoso.com" -ExpectedTarget "data-contoso.azureedge.net"
Test-CustomDomainConnectivity -CustomDomain "data.contoso.com" -TestPath "test.txt"
Python Validation¶
import dns.resolver
import requests
import sys
def validate_cname(custom_domain: str, expected_target: str) -> bool:
"""Validate CNAME record configuration."""
try:
answers = dns.resolver.resolve(custom_domain, 'CNAME')
actual_target = str(answers[0].target).rstrip('.')
if actual_target == expected_target.rstrip('.'):
print(f"✅ CNAME record configured correctly")
print(f" {custom_domain} -> {actual_target}")
return True
else:
print(f"❌ CNAME record mismatch")
print(f" Expected: {expected_target}")
print(f" Actual: {actual_target}")
return False
except Exception as e:
print(f"❌ CNAME record not found: {e}")
return False
def validate_https(custom_domain: str, test_path: str = "") -> bool:
"""Validate HTTPS accessibility."""
url = f"https://{custom_domain}/{test_path}"
try:
response = requests.get(url, timeout=10)
print(f"✅ Custom domain accessible via HTTPS")
print(f" Status Code: {response.status_code}")
return response.status_code < 400
except Exception as e:
print(f"❌ Custom domain not accessible: {e}")
return False
# Run validation
if __name__ == "__main__":
domain = "data.contoso.com"
target = "data-contoso.azureedge.net"
cname_valid = validate_cname(domain, target)
https_valid = validate_https(domain, "test.txt")
sys.exit(0 if (cname_valid and https_valid) else 1)
Advanced Scenarios¶
Multi-Environment Configuration¶
# custom-domains.yaml
environments:
dev:
storage_account: "stadevdata001"
custom_domain: "data-dev.contoso.com"
cdn_endpoint: "data-dev-contoso"
ssl_certificate: "CDN-managed"
staging:
storage_account: "stastagingdata001"
custom_domain: "data-staging.contoso.com"
cdn_endpoint: "data-staging-contoso"
ssl_certificate: "CDN-managed"
prod:
storage_account: "stadatalake001"
custom_domain: "data.contoso.com"
cdn_endpoint: "data-contoso"
ssl_certificate: "Custom (Key Vault)"
geo_redundancy: true
cdn_rules:
- cache_duration: "7d"
- compression: true
- https_redirect: true
Subdomain Configuration¶
# Configure multiple subdomains for different services
# api.data.contoso.com -> API endpoints
# files.data.contoso.com -> File storage
# cdn.data.contoso.com -> CDN content
# Create multiple CDN endpoints
for subdomain in api files cdn; do
az cdn endpoint create \
--name ${subdomain}-data-contoso \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--origin stadatalake001.blob.core.windows.net \
--origin-path "/${subdomain}"
az cdn custom-domain create \
--endpoint-name ${subdomain}-data-contoso \
--hostname ${subdomain}.data.contoso.com \
--name ${subdomain}-data-contoso-com \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod
done
Performance Optimization¶
CDN Caching Rules¶
# Configure caching rules
az cdn endpoint rule add \
--name data-contoso \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--order 1 \
--rule-name "CacheStaticContent" \
--match-variable RequestUri \
--operator Contains \
--match-values ".jpg" ".png" ".css" ".js" \
--action-name CacheExpiration \
--cache-behavior OverrideAlways \
--cache-duration "7.00:00:00"
# Configure compression
az cdn endpoint update \
--name data-contoso \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--enable-compression true \
--content-types-to-compress \
"application/json" \
"application/javascript" \
"text/css" \
"text/html" \
"text/plain"
Performance Metrics¶
| Metric | Without CDN | With CDN | Improvement |
|---|---|---|---|
| Global Latency | 200-500ms | 20-100ms | 60-90% |
| Cache Hit Rate | 0% | 70-95% | Significant |
| Bandwidth Costs | High | Lower | 30-60% |
| Origin Load | 100% | 5-30% | 70-95% |
Troubleshooting¶
Common Issues¶
| Issue | Cause | Solution |
|---|---|---|
| Domain not resolving | CNAME not propagated | Wait for DNS propagation (up to 48h) |
| HTTPS not working | Certificate not provisioned | Verify CDN custom domain HTTPS status |
| 404 errors | Incorrect origin path | Check CDN endpoint origin configuration |
| Certificate mismatch | Wrong certificate | Verify custom certificate configuration |
| Slow performance | No CDN caching | Configure appropriate caching rules |
Diagnostic Commands¶
# Check DNS propagation
nslookup data.contoso.com
dig data.contoso.com CNAME
# Test HTTPS certificate
openssl s_client -connect data.contoso.com:443 -servername data.contoso.com
# Check CDN endpoint status
az cdn endpoint show \
--name data-contoso \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--query provisioningState
# Purge CDN cache
az cdn endpoint purge \
--name data-contoso \
--profile-name cdn-analytics-prod \
--resource-group rg-analytics-prod \
--content-paths '/*'
Best Practices¶
Configuration Checklist¶
- Use CDN for production custom domains requiring HTTPS
- Implement asverify method for zero-downtime validation
- Configure minimum TLS 1.2 for all HTTPS connections
- Enable CDN compression for static content
- Set appropriate caching rules for different content types
- Monitor CDN metrics and optimize rules
- Use managed certificates for automatic renewal
- Configure geo-redundancy for critical workloads
- Implement proper DNS TTL values
- Document all custom domain configurations
Related Resources¶
- Azure Storage Custom Domains
- Azure CDN Documentation
- Storage Custom Domain with HTTPS
- DNS Configuration Best Practices
- Storage Geo-Replication
Note: Custom domain configurations may take up to 48 hours to fully propagate globally. Always test thoroughly in non-production environments before implementing in production.