# Amazon CloudFront

> Serve video files from a private S3 bucket through CloudFront.

Use Amazon CloudFront to deliver the video files Chunkify writes to your S3 bucket. This guide keeps the bucket private and gives CloudFront read access to your output prefix. Viewers use the CloudFront URL to access those outputs.

Start with an [Amazon S3 storage connection](/docs/video-streaming/storage/aws-s3). The examples use the output prefix `chunkify/` and the viewing website `https://app.example.com`. Replace them with your own values.

## Create a CloudFront distribution

In the [CloudFront console](https://console.aws.amazon.com/cloudfront/v4/home), select **Create distribution**. Name it, choose **Single website or app**, and select **Amazon S3** as the origin type.

Select your existing bucket with **Browse S3**. Use the recommended origin settings to configure origin access control (OAC), then finish creating the distribution. Review its origin settings:

| Setting | Value |
| --- | --- |
| Origin domain | Your bucket's S3 endpoint, not its static website endpoint |
| Origin path | Empty |
| Origin access | Origin access control (OAC) |
| OAC signing behavior | **Sign requests (recommended)** |

Keep S3 **Block Public Access** enabled. OAC lets CloudFront authenticate its reads without making the bucket public. See [AWS's distribution setup](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.SimpleDistribution.html).

Leave **Origin path** empty because Chunkify includes the output prefix in each playback URL. An origin path of `/chunkify` would add that prefix a second time.

Record the distribution ID and domain name assigned by CloudFront. The examples below use `your-distribution.cloudfront.net` as a placeholder. Replace it with your actual domain. A custom domain is optional.

## Allow CloudFront to read the outputs

In S3, open your bucket's **Permissions → Bucket policy**. Allow this distribution to read objects under your output prefix. Replace `YOUR_BUCKET`, `YOUR_AWS_ACCOUNT_ID`, and `YOUR_DISTRIBUTION_ID` below.

The creation wizard may already have added a statement allowing CloudFront to read the whole bucket. Replace that distribution's generated statement with the scoped statement below, preserving unrelated statements in the policy.

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontVideoOutputs",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR_BUCKET/chunkify/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::YOUR_AWS_ACCOUNT_ID:distribution/YOUR_DISTRIBUTION_ID"
        }
      }
    }
  ]
}
```

Adjust `chunkify/*` if your output prefix differs. CloudFront's bucket permission is separate from the IAM credentials Chunkify uses to write outputs. See [AWS's OAC policy examples](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html).

<note>
Anyone with a CloudFront URL can read objects under this prefix. Keep private source videos outside it. OAC protects the S3 origin; restricting individual viewers requires a separate playback access setup.

If your bucket uses SSE-KMS encryption, also [grant CloudFront access to the KMS key](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#oac-permission-to-access-s3).
</note>

## Allow playback from your website

This setup uses S3's CORS headers and passes them through CloudFront. In the bucket's **Permissions → Cross-origin resource sharing (CORS)** settings, merge this playback rule with your existing rules:

```json
[
  {
    "AllowedOrigins": ["https://app.example.com", "https://chunkify.dev"],
    "AllowedMethods": ["GET", "HEAD"],
    "AllowedHeaders": ["Range"],
    "ExposeHeaders": ["Accept-Ranges", "Content-Length", "Content-Range", "ETag"],
    "MaxAgeSeconds": 3600
  }
]
```

Replace the application origin and add any other websites that play your videos. `https://chunkify.dev` allows playback in the Chunkify dashboard. S3 handles preflight requests; do not add `OPTIONS` to this bucket rule.

Preserve existing upload rules when saving the complete policy. Browser uploads go directly to S3 and also need `PUT`, the `Content-Type` request header, and an exposed `ETag`. See [Configure CORS](/docs/video-streaming/storage/cors).

## Configure caching

CloudFront must keep playlists up to date while caching media files. Chunkify writes HLS playlists with `Cache-Control: no-cache`, so CloudFront can revalidate them before reuse as renditions and captions become available.

In CloudFront, open **Policies → Cache policies → Create cache policy**. Name the policy `ChunkifyMedia` and use these settings:

| Setting | Value |
| --- | --- |
| Minimum TTL | `0` seconds |
| Default TTL | `86400` seconds, used when an object has no cache headers |
| Maximum TTL | `31536000` seconds |
| Headers included in the cache key | Include `Origin` |
| Cookies | None |
| Query strings | None |

Minimum TTL must be `0` so CloudFront respects `no-cache`. The higher maximum allows completed media files to stay cached according to their own headers. See [AWS's cache duration guide](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html).

Open the distribution's **Behaviors** tab and edit the **Default (*)** behavior:

| Setting | Value |
| --- | --- |
| Origin | The S3 origin configured above |
| Viewer protocol policy | Redirect HTTP to HTTPS |
| Allowed HTTP methods | `GET`, `HEAD`, `OPTIONS` |
| Cached HTTP methods | `GET`, `HEAD`; leave `OPTIONS` caching off |
| Cache policy | `ChunkifyMedia` |
| Origin request policy | Managed policy **CORS-S3Origin** |
| Response headers policy | None; this setup uses S3's CORS headers |
| Restrict viewer access | No, for the public playback setup in this guide |

`CORS-S3Origin` forwards the headers S3 needs for CORS and preflight requests. Including `Origin` in the cache key keeps responses for different viewing websites separate. [AWS documents these CORS settings](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors).

These methods also support byte-range playback requests. CloudFront supports [range GETs](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RangeGETs.html); no separate origin or behavior is needed for `.m4s` or `.mp4` files.

Under **Error pages**, set **Error caching minimum TTL** to `1` second for `403` and `404`. Preserve the original error response. This limits cached errors if a request arrives before an object is available. See [AWS's error caching guide](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/custom-error-pages-expiration.html).

Save the changes and wait for the distribution to finish deploying before testing. If you changed policies on an existing distribution, [invalidate cached outputs](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html) so older responses do not hide the changes.

## Add the CDN domain to Chunkify

In your Project, open **Project settings → Storage**, edit the S3 connection, and set **CDN base URL** to the distribution's HTTPS origin:

```text
https://your-distribution.cloudfront.net
```

Enter only the HTTPS origin, without an output prefix, path, or query string, then select **Save changes**. The S3 connection can remain private; CloudFront reads its files through OAC.

For example, an object stored at `chunkify/asset_2G6MJiNz71bHQGNzGwKx5cJwPFS/manifest.m3u8` is served at:

```text
https://your-distribution.cloudfront.net/chunkify/asset_2G6MJiNz71bHQGNzGwKx5cJwPFS/manifest.m3u8
```

Chunkify returns this address as the video asset's `cdn_url`. Adding the base URL does not verify delivery. See [CDN delivery](/docs/video-streaming/storage/cdn-delivery) for how playback URLs are built.

## Verify playback

Create a test video asset using this storage connection. Once playback is available, use its `cdn_url` in a player on your website and check the browser's Network panel:

- The master playlist, rendition playlists, and media requests succeed, and the video plays and seeks.
- Responses allow your website's origin through `Access-Control-Allow-Origin`.
- The poster, timeline previews, and any captions load.
- Byte-range media requests return `206 Partial Content` when requested.
- After additional HLS renditions or caption tracks are ready, a new master-playlist request reflects the available outputs.
- Repeated media-segment requests can return `X-Cache: Hit from cloudfront`, indicating a [cache hit](https://repost.aws/knowledge-center/cloudfront-troubleshoot-latency).

To inspect a playlist's response headers, include your website's origin. Replace the example path with a real video asset's path:

```bash
curl --silent --show-error \
  --dump-header - \
  --output /dev/null \
  --header 'Origin: https://app.example.com' \
  'https://your-distribution.cloudfront.net/chunkify/asset_2G6MJiNz71bHQGNzGwKx5cJwPFS/manifest.m3u8'
```

Test playback from your website as well as the Chunkify dashboard. A successful request without an `Origin` header does not establish that browser CORS works.

## Troubleshoot delivery

| Symptom | Check |
| --- | --- |
| A file returns `403` | Confirm the object exists, then check the OAC, distribution ARN, and bucket policy's output prefix. |
| A file returns `404` | Confirm the exact object key in S3, keep the output prefix in the URL, and leave the origin path empty. If the file now exists, invalidate a previously cached error. |
| Browser reports a CORS error | Check the bucket's CORS policy, the `CORS-S3Origin` origin request policy, and `Origin` in the cache key. Invalidate old cached responses after correcting the settings. |
| Playback shows outdated renditions or captions | Check that Minimum TTL is `0`, remove conflicting cache overrides, and invalidate the cached playlist before retesting. |
