Back to Blog
6 min read

Accelerating Content Delivery with Azure CDN

Azure Content Delivery Network (CDN) delivers content to users globally with low latency by caching content at strategically placed edge locations. It integrates seamlessly with Azure services and supports advanced features like custom domains, HTTPS, and rules engines.

CDN Offerings

ProviderFeaturesUse Case
Standard MicrosoftBasic caching, rules engineGeneral purpose
Standard VerizonAdvanced analyticsMedia streaming
Standard AkamaiLarge file optimizationSoftware distribution
Premium VerizonAdvanced rules, real-time analyticsEnterprise

Creating a CDN Profile and Endpoint

from azure.mgmt.cdn import CdnManagementClient
from azure.mgmt.cdn.models import (
    Profile,
    Sku,
    Endpoint,
    DeepCreatedOrigin,
    QueryStringCachingBehavior
)
from azure.identity import DefaultAzureCredential

class CDNManager:
    def __init__(self, subscription_id: str, resource_group: str):
        self.credential = DefaultAzureCredential()
        self.client = CdnManagementClient(self.credential, subscription_id)
        self.resource_group = resource_group

    def create_profile(self, profile_name: str, sku: str = "Standard_Microsoft") -> Profile:
        """Create a CDN profile."""

        profile = Profile(
            location="global",
            sku=Sku(name=sku)
        )

        return self.client.profiles.begin_create(
            self.resource_group,
            profile_name,
            profile
        ).result()

    def create_endpoint(self, profile_name: str, endpoint_name: str,
                       origin_hostname: str,
                       origin_path: str = None) -> Endpoint:
        """Create a CDN endpoint."""

        endpoint = Endpoint(
            location="global",
            origins=[
                DeepCreatedOrigin(
                    name="origin1",
                    host_name=origin_hostname,
                    http_port=80,
                    https_port=443,
                    origin_host_header=origin_hostname
                )
            ],
            origin_path=origin_path,
            is_http_allowed=True,
            is_https_allowed=True,
            query_string_caching_behavior=QueryStringCachingBehavior.IGNORE_QUERY_STRING,
            is_compression_enabled=True,
            content_types_to_compress=[
                "text/plain",
                "text/html",
                "text/css",
                "text/javascript",
                "application/javascript",
                "application/json",
                "application/xml"
            ]
        )

        return self.client.endpoints.begin_create(
            self.resource_group,
            profile_name,
            endpoint_name,
            endpoint
        ).result()

    def purge_content(self, profile_name: str, endpoint_name: str,
                     paths: list):
        """Purge cached content."""

        return self.client.endpoints.begin_purge_content(
            self.resource_group,
            profile_name,
            endpoint_name,
            {"content_paths": paths}
        ).result()

    def load_content(self, profile_name: str, endpoint_name: str,
                    paths: list):
        """Pre-load content into cache."""

        return self.client.endpoints.begin_load_content(
            self.resource_group,
            profile_name,
            endpoint_name,
            {"content_paths": paths}
        ).result()

# Usage
cdn_manager = CDNManager("subscription-id", "cdn-rg")

# Create profile
cdn_manager.create_profile("mycdn-profile")

# Create endpoint for blob storage
cdn_manager.create_endpoint(
    "mycdn-profile",
    "static-content",
    "mystorageaccount.blob.core.windows.net",
    "/public"
)

Configuring Custom Domains

from azure.mgmt.cdn.models import (
    CustomDomain,
    CustomDomainHttpsParameters,
    CdnManagedHttpsParameters,
    CertificateType,
    ProtocolType,
    MinimumTlsVersion
)

def add_custom_domain(cdn_manager: CDNManager, profile_name: str,
                     endpoint_name: str, custom_domain: str):
    """Add a custom domain to CDN endpoint."""

    # Custom domain name (without protocol)
    domain_name = custom_domain.replace(".", "-")

    custom_domain_obj = CustomDomain(
        host_name=custom_domain
    )

    # Create custom domain
    result = cdn_manager.client.custom_domains.begin_create(
        cdn_manager.resource_group,
        profile_name,
        endpoint_name,
        domain_name,
        custom_domain_obj
    ).result()

    return result

def enable_https(cdn_manager: CDNManager, profile_name: str,
                endpoint_name: str, domain_name: str):
    """Enable HTTPS with CDN-managed certificate."""

    https_params = CdnManagedHttpsParameters(
        certificate_source="Cdn",
        protocol_type=ProtocolType.SERVER_NAME_INDICATION,
        minimum_tls_version=MinimumTlsVersion.TLS12,
        certificate_source_parameters={
            "certificate_type": CertificateType.DEDICATED
        }
    )

    return cdn_manager.client.custom_domains.begin_enable_custom_https(
        cdn_manager.resource_group,
        profile_name,
        endpoint_name,
        domain_name,
        https_params
    ).result()

# Add custom domain
add_custom_domain(cdn_manager, "mycdn-profile", "static-content", "cdn.example.com")

# Enable HTTPS
enable_https(cdn_manager, "mycdn-profile", "static-content", "cdn-example-com")

Rules Engine Configuration

from azure.mgmt.cdn.models import (
    DeliveryRule,
    DeliveryRuleCondition,
    UrlFileExtensionMatchConditionParameters,
    UrlPathMatchConditionParameters,
    DeliveryRuleCacheExpirationAction,
    CacheExpirationActionParameters,
    HeaderAction,
    HeaderActionParameters,
    UrlRedirectAction,
    UrlRedirectActionParameters
)

def configure_caching_rules(cdn_manager: CDNManager, profile_name: str,
                           endpoint_name: str):
    """Configure caching rules for different content types."""

    rules = []

    # Rule 1: Cache static assets for 30 days
    rules.append(DeliveryRule(
        name="CacheStaticAssets",
        order=1,
        conditions=[
            {
                "name": "UrlFileExtension",
                "parameters": UrlFileExtensionMatchConditionParameters(
                    operator="Equal",
                    match_values=["css", "js", "png", "jpg", "jpeg", "gif", "svg", "woff", "woff2"]
                )
            }
        ],
        actions=[
            DeliveryRuleCacheExpirationAction(
                parameters=CacheExpirationActionParameters(
                    cache_behavior="Override",
                    cache_type="All",
                    cache_duration="30.00:00:00"
                )
            )
        ]
    ))

    # Rule 2: Don't cache API responses
    rules.append(DeliveryRule(
        name="NoCacheAPI",
        order=2,
        conditions=[
            {
                "name": "UrlPath",
                "parameters": UrlPathMatchConditionParameters(
                    operator="BeginsWith",
                    match_values=["/api/"]
                )
            }
        ],
        actions=[
            DeliveryRuleCacheExpirationAction(
                parameters=CacheExpirationActionParameters(
                    cache_behavior="BypassCache"
                )
            )
        ]
    ))

    # Rule 3: Add security headers
    rules.append(DeliveryRule(
        name="SecurityHeaders",
        order=3,
        conditions=[],
        actions=[
            HeaderAction(
                parameters=HeaderActionParameters(
                    header_action="Append",
                    header_name="X-Content-Type-Options",
                    value="nosniff"
                )
            ),
            HeaderAction(
                parameters=HeaderActionParameters(
                    header_action="Append",
                    header_name="X-Frame-Options",
                    value="DENY"
                )
            ),
            HeaderAction(
                parameters=HeaderActionParameters(
                    header_action="Append",
                    header_name="Strict-Transport-Security",
                    value="max-age=31536000; includeSubDomains"
                )
            )
        ]
    ))

    # Rule 4: Redirect HTTP to HTTPS
    rules.append(DeliveryRule(
        name="HttpsRedirect",
        order=4,
        conditions=[
            {
                "name": "RequestScheme",
                "parameters": {"operator": "Equal", "match_values": ["HTTP"]}
            }
        ],
        actions=[
            UrlRedirectAction(
                parameters=UrlRedirectActionParameters(
                    redirect_type="Found",
                    destination_protocol="Https"
                )
            )
        ]
    ))

    # Update endpoint with rules
    endpoint = cdn_manager.client.endpoints.get(
        cdn_manager.resource_group,
        profile_name,
        endpoint_name
    )

    endpoint.delivery_policy = {"rules": rules}

    return cdn_manager.client.endpoints.begin_update(
        cdn_manager.resource_group,
        profile_name,
        endpoint_name,
        endpoint
    ).result()

CDN Integration with Storage

from azure.storage.blob import BlobServiceClient, generate_blob_sas, BlobSasPermissions
from datetime import datetime, timedelta

class CDNStorageIntegration:
    def __init__(self, storage_connection: str, cdn_hostname: str):
        self.blob_service = BlobServiceClient.from_connection_string(storage_connection)
        self.cdn_hostname = cdn_hostname

    def get_cdn_url(self, container: str, blob_name: str) -> str:
        """Get CDN URL for a blob."""
        return f"https://{self.cdn_hostname}/{container}/{blob_name}"

    def upload_with_cache_headers(self, container: str, blob_name: str,
                                 data: bytes, content_type: str,
                                 cache_control: str = "public, max-age=86400"):
        """Upload blob with cache-control headers."""

        container_client = self.blob_service.get_container_client(container)
        blob_client = container_client.get_blob_client(blob_name)

        blob_client.upload_blob(
            data,
            content_settings={
                "content_type": content_type,
                "cache_control": cache_control
            },
            overwrite=True
        )

        return self.get_cdn_url(container, blob_name)

    def upload_versioned(self, container: str, blob_name: str,
                        data: bytes, version: str) -> str:
        """Upload with version in filename for cache busting."""

        # Add version to filename
        name_parts = blob_name.rsplit('.', 1)
        if len(name_parts) == 2:
            versioned_name = f"{name_parts[0]}.{version}.{name_parts[1]}"
        else:
            versioned_name = f"{blob_name}.{version}"

        return self.upload_with_cache_headers(
            container,
            versioned_name,
            data,
            "application/octet-stream",
            "public, max-age=31536000, immutable"  # 1 year, immutable
        )

# Usage
cdn_storage = CDNStorageIntegration(
    "your-storage-connection",
    "mycdn.azureedge.net"
)

# Upload static assets
with open("styles.css", "rb") as f:
    url = cdn_storage.upload_versioned(
        "static",
        "styles.css",
        f.read(),
        "v1.2.3"
    )
print(f"CDN URL: {url}")

Monitoring and Analytics

from azure.monitor.query import MetricsQueryClient, LogsQueryClient
from datetime import datetime, timedelta

def get_cdn_metrics(metrics_client: MetricsQueryClient,
                   endpoint_resource_id: str,
                   hours: int = 24) -> dict:
    """Get CDN endpoint metrics."""

    end_time = datetime.utcnow()
    start_time = end_time - timedelta(hours=hours)

    metrics = metrics_client.query_resource(
        endpoint_resource_id,
        metric_names=[
            "ByteHitRatio",
            "RequestCount",
            "ResponseSize",
            "OriginLatency"
        ],
        timespan=(start_time, end_time),
        granularity=timedelta(hours=1)
    )

    results = {}
    for metric in metrics.metrics:
        values = []
        for series in metric.timeseries:
            for data in series.data:
                values.append({
                    "timestamp": data.timestamp,
                    "average": data.average,
                    "total": data.total
                })
        results[metric.name] = values

    return results

def generate_cdn_report(metrics: dict) -> str:
    """Generate CDN performance report."""

    report = []
    report.append("=== CDN Performance Report ===\n")

    if "ByteHitRatio" in metrics:
        avg_hit_ratio = sum(v["average"] or 0 for v in metrics["ByteHitRatio"]) / max(len(metrics["ByteHitRatio"]), 1)
        report.append(f"Cache Hit Ratio: {avg_hit_ratio:.1f}%")

    if "RequestCount" in metrics:
        total_requests = sum(v["total"] or 0 for v in metrics["RequestCount"])
        report.append(f"Total Requests: {total_requests:,}")

    if "ResponseSize" in metrics:
        total_bytes = sum(v["total"] or 0 for v in metrics["ResponseSize"])
        report.append(f"Data Transferred: {total_bytes / (1024**3):.2f} GB")

    if "OriginLatency" in metrics:
        avg_latency = sum(v["average"] or 0 for v in metrics["OriginLatency"]) / max(len(metrics["OriginLatency"]), 1)
        report.append(f"Average Origin Latency: {avg_latency:.0f} ms")

    return "\n".join(report)

Best Practices

  1. Enable Compression: Compress text-based content
  2. Set Cache Headers: Use appropriate cache-control headers
  3. Version Assets: Include version in filenames for cache busting
  4. Use HTTPS: Always enable HTTPS with modern TLS
  5. Monitor Hit Ratio: Aim for >90% cache hit ratio
  6. Purge Strategically: Purge only what’s necessary

Azure CDN significantly improves application performance by reducing latency and offloading traffic from origin servers.

Michael John Pena

Michael John Pena

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.