# CDN (classic)

Source: /azure/services/cdn/

## Introduction

Azure Content Delivery Network (CDN) caches content from an origin, such as a storage account or a web app, at edge locations close to users.
A classic CDN profile (for example the `Standard_Microsoft` SKU) holds endpoints. Each endpoint has a `<name>.azureedge.net` host name, one or more origins, optional origin groups and custom domains, and caching, compression and delivery rules. For more information, see [What is a content delivery network on Azure?](https://learn.microsoft.com/en-us/azure/cdn/cdn-overview).

Azure no longer allows new classic CDN profiles, and recommends [Azure Front Door](/azure/services/front-door/) instead. LocalStack for Azure supports classic CDN so that you can keep developing and testing existing infrastructure that uses it locally, including the migration of a classic profile to Front Door.
The supported APIs are available on our [API Coverage section](#api-coverage), which provides information on the extent of classic CDN's integration with LocalStack.

## Getting started

This guide is designed for users new to classic Azure CDN and assumes basic knowledge of the Azure CLI and our `lstk az` proxy.

Like Azure, LocalStack refuses to create new classic CDN profiles and endpoints by default: the requests fail with a `400` error that says classic creation is no longer supported.
Start LocalStack with `CDN_CLASSIC_ALLOW_CREATE=1` to allow it.
Without the flag, reading, deleting and patching existing classic resources (as `az cdn profile update` and `az cdn endpoint update` do) still works, but a `PUT` over an existing classic profile or endpoint, which ARM templates, Bicep and Terraform send when they redeploy, is refused as well.

Launch LocalStack using your preferred method, with `CDN_CLASSIC_ALLOW_CREATE=1` set. For more information, see [Introduction to LocalStack for Azure](/azure/getting-started/). Once the container is running, enable Azure CLI interception by running:

```bash
lstk az start-interception
```

This command points the `az` CLI away from the public Azure management REST API and toward the LocalStack for Azure emulator API.
To revert this configuration, run:

```bash
lstk az stop-interception
```

This reconfigures the `az` CLI to send commands to the official Azure management REST API.

:::note
The `az cdn` commands are provided by the `cdn` extension in recent versions of the Azure CLI, which installs itself the first time you run one of them.
:::

### Create a resource group

Create a resource group to hold all resources created in this guide:

```bash
az group create \
  --name rg-cdn-demo \
  --location westeurope
```

### Create a CDN profile

Create a classic profile with the `Standard_Microsoft` SKU:

```bash
az cdn profile create \
  --name cdn-profile-demo \
  --resource-group rg-cdn-demo \
  --sku Standard_Microsoft
```

```bash title="Output"
{
  "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-cdn-demo/providers/Microsoft.Cdn/profiles/cdn-profile-demo",
  "kind": "cdn",
  "location": "Global",
  "name": "cdn-profile-demo",
  "originResponseTimeoutSeconds": 30,
  "provisioningState": "Succeeded",
  "resourceGroup": "rg-cdn-demo",
  "resourceState": "Active",
  "sku": {
    "name": "Standard_Microsoft"
  },
  "tags": {},
  "type": "Microsoft.Cdn/profiles",
  ...
}
```

### Create an endpoint

Create an endpoint that serves content from `www.contoso.com`, with compression enabled:

```bash
az cdn endpoint create \
  --name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo \
  --origin www.contoso.com \
  --origin-host-header www.contoso.com \
  --enable-compression true
```

```bash title="Output"
{
  "contentTypesToCompress": [
    "text/plain",
    "text/html",
    "text/css",
    "text/javascript",
    "application/x-javascript",
    "application/javascript",
    "application/json",
    "application/xml"
  ],
  "customDomains": [],
  "geoFilters": [],
  "hostName": "cdn-endpoint-demo.azureedge.net",
  "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-cdn-demo/providers/Microsoft.Cdn/profiles/cdn-profile-demo/endpoints/cdn-endpoint-demo",
  "isCompressionEnabled": true,
  "isHttpAllowed": true,
  "isHttpsAllowed": true,
  "location": "Global",
  "name": "cdn-endpoint-demo",
  "originGroups": [],
  "originHostHeader": "www.contoso.com",
  "origins": [
    {
      "hostName": "www.contoso.com",
      "httpPort": 80,
      "httpsPort": 443,
      "name": "www-contoso-com"
    }
  ],
  "provisioningState": "Succeeded",
  "queryStringCachingBehavior": "NotSet",
  "resourceGroup": "rg-cdn-demo",
  "resourceState": "Running",
  "tags": {},
  "type": "Microsoft.Cdn/profiles/endpoints",
  "urlSigningKeys": []
}
```

The origins you pass inline are created as origin resources of the endpoint:

```bash
az cdn origin list \
  --endpoint-name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo \
  --output table
```

```bash title="Output"
Enabled    HostName         HttpPort    HttpsPort    Name             ProvisioningState    ResourceGroup    ResourceState
---------  ---------------  ----------  -----------  ---------------  -------------------  ---------------  ---------------
True       www.contoso.com  80          443          www-contoso-com  Succeeded            rg-cdn-demo      Active
```

### Update the endpoint

Change the endpoint's query string caching behavior:

```bash
az cdn endpoint update \
  --name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo \
  --query-string-caching-behavior BypassCaching
```

```bash title="Output"
{
  "hostName": "cdn-endpoint-demo.azureedge.net",
  "name": "cdn-endpoint-demo",
  "provisioningState": "Succeeded",
  "queryStringCachingBehavior": "BypassCaching",
  "resourceState": "Running",
  "type": "Microsoft.Cdn/profiles/endpoints",
  ...
}
```

### Add a custom domain

Add a custom domain to the endpoint:

```bash
az cdn custom-domain create \
  --name www-example \
  --endpoint-name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo \
  --hostname www.example.com
```

```bash title="Output"
{
  "customHttpsProvisioningState": "Disabled",
  "hostName": "www.example.com",
  "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/rg-cdn-demo/providers/Microsoft.Cdn/profiles/cdn-profile-demo/endpoints/cdn-endpoint-demo/customdomains/www-example",
  "name": "www-example",
  "provisioningState": "Succeeded",
  "resourceGroup": "rg-cdn-demo",
  "resourceState": "Active",
  "type": "Microsoft.Cdn/profiles/endpoints/customdomains",
  ...
}
```

### Purge content

Purge cached content by path. Each path must start with `/`:

```bash
az cdn endpoint purge \
  --name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo \
  --content-paths '/images/*'
```

A path without a leading `/`, such as `--content-paths images`, is rejected:

```bash title="Output"
(BadRequest) The content path 'images' must start with '/'.
```

### Stop and start the endpoint

Stop the endpoint and check its state:

```bash
az cdn endpoint stop \
  --name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo

az cdn endpoint show \
  --name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo \
  --query resourceState \
  --output tsv
```

```bash title="Output"
Stopped
```

Start it again:

```bash
az cdn endpoint start \
  --name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo
```

### Check name availability

Endpoint host names are unique across all subscriptions. Check whether a name is still available:

```bash
az cdn name-exists \
  --name cdn-endpoint-demo
```

```bash title="Output"
{
  "message": "Name not available",
  "nameAvailable": false,
  "reason": "Name is already in use"
}
```

### Delete the endpoint and the profile

Delete the endpoint and the profile, then list the profiles in the resource group to confirm they were removed:

```bash
az cdn endpoint delete \
  --name cdn-endpoint-demo \
  --profile-name cdn-profile-demo \
  --resource-group rg-cdn-demo

az cdn profile delete \
  --name cdn-profile-demo \
  --resource-group rg-cdn-demo

az cdn profile list \
  --resource-group rg-cdn-demo
```

```bash title="Output"
[]
```

## Features

The classic CDN emulator supports the following features:

- **Profiles and endpoints**: Create, get, update, list, and delete classic profiles and endpoints. Endpoints get a `<name>.azureedge.net` host name.
- **Origins, origin groups and custom domains**: Manage the origins, origin groups and custom domains of an endpoint. Origins and origin groups passed inline when you create an endpoint become resources of their own.
- **Endpoint operations**: Start and stop endpoints, purge and load content with path validation, and validate custom domains.
- **Custom domain HTTPS**: Enable and disable HTTPS on a custom domain.
- **Classic CDN WAF policies**: Create, get, update, list, and delete `cdnWebApplicationFirewallPolicies`, list the managed rule sets, and see which endpoints link to a policy. A policy that an endpoint still uses cannot be deleted.
- **Quotas**: A profile holds up to 25 endpoints and an endpoint up to 20 custom domains; usage is reported by the resource usage operations.
- **Name availability**: Endpoint names are checked against all subscriptions, as on Azure.
- **Migration to Front Door**: Check whether a classic profile can be migrated, and migrate it to Azure Front Door Standard/Premium. Each classic endpoint becomes a Front Door endpoint (named `<name>-migrated` unless you map it), with an origin group holding its origins and a default route for `/*`. Custom domains and delivery rules are not carried over.
- **Edge nodes and optimization types**: List the edge nodes and the optimization types a profile supports.

## Configuration

| Variable | Default | Effect |
|---|---|---|
| `CDN_CLASSIC_ALLOW_CREATE` | unset | Allow creating classic CDN profiles and endpoints, which Azure has refused since 2025-08-15. |
| `CDN_VALIDATE_PROBE_FETCHES` | unset | Make `ValidateProbe` fetch the URL. Off by default: the emulator answers `EndpointProbeCannotBeRetrieved`, which is what Azure answers for a URL only reachable from your machine. |

## Limitations

- **No content delivery**: Classic endpoints do not serve traffic. Requests to `<name>.azureedge.net` are not routed to the origin, so caching, compression, delivery rules and geo-filtering are stored and returned but have no effect. To test request routing and caching locally, use [Azure Front Door](/azure/services/front-door/).
- **Purge and load content have no effect**: Paths are validated, but there is no cache to purge or edge to preload.
- **Custom domain validation**: A host name validates only when it is already a custom domain of the endpoint. DNS records are not checked.
- **Custom domain HTTPS**: Enabling HTTPS completes straight away and no certificate is issued.
- **WAF policies are not enforced**: Classic CDN WAF policies are stored, but no requests are evaluated against them.
- **Single sign-on**: `GenerateSsoUri` is not supported for classic profiles.

## API Coverage


### CDN (classic) API coverage

Source service: `cdn`. 52 of 52 tracked operations are implemented.

Service documentation: /azure/services/cdn/

| Operation | Status |
| --- | --- |
| CustomDomains.Create | Implemented |
| CustomDomains.Delete | Implemented |
| CustomDomains.DisableCustomHttps | Implemented |
| CustomDomains.EnableCustomHttps | Implemented |
| CustomDomains.Get | Implemented |
| CustomDomains.ListByEndpoint | Implemented |
| EdgeNodes.List | Implemented |
| Endpoints.Create | Implemented |
| Endpoints.Delete | Implemented |
| Endpoints.Get | Implemented |
| Endpoints.ListByProfile | Implemented |
| Endpoints.ListResourceUsage | Implemented |
| Endpoints.LoadContent | Implemented |
| Endpoints.PurgeContent | Implemented |
| Endpoints.Start | Implemented |
| Endpoints.Stop | Implemented |
| Endpoints.Update | Implemented |
| Endpoints.ValidateCustomDomain | Implemented |
| ManagedRuleSets.List | Implemented |
| Microsoft.Cdn.CheckNameAvailability | Implemented |
| Microsoft.Cdn.CheckNameAvailabilityWithSubscription | Implemented |
| Microsoft.Cdn.ValidateProbe | Implemented |
| Operations.List | Implemented |
| OriginGroups.Create | Implemented |
| OriginGroups.Delete | Implemented |
| OriginGroups.Get | Implemented |
| OriginGroups.ListByEndpoint | Implemented |
| OriginGroups.Update | Implemented |
| Origins.Create | Implemented |
| Origins.Delete | Implemented |
| Origins.Get | Implemented |
| Origins.ListByEndpoint | Implemented |
| Origins.Update | Implemented |
| Policies.CreateOrUpdate | Implemented |
| Policies.Delete | Implemented |
| Policies.Get | Implemented |
| Policies.List | Implemented |
| Policies.Update | Implemented |
| Profiles.CdnCanMigrateToAfd | Implemented |
| Profiles.CdnMigrateToAfd | Implemented |
| Profiles.Create | Implemented |
| Profiles.Delete | Implemented |
| Profiles.GenerateSsoUri | Implemented |
| Profiles.Get | Implemented |
| Profiles.List | Implemented |
| Profiles.ListByResourceGroup | Implemented |
| Profiles.ListResourceUsage | Implemented |
| Profiles.ListSupportedOptimizationTypes | Implemented |
| Profiles.MigrationAbort | Implemented |
| Profiles.MigrationCommit | Implemented |
| Profiles.Update | Implemented |
| ResourceUsage.List | Implemented |
