# Cloudflare

> Serve video files from R2 through a Cloudflare custom domain.

Serve the video files Chunkify writes to R2 through a custom domain such as `https://media.example.com`. Cloudflare delivers the files and caches media segments close to your viewers.

Start with a [Cloudflare R2 storage connection](/docs/video-streaming/storage/cloudflare-r2) and a domain in the same Cloudflare account as the bucket. Replace `media.example.com` and `https://app.example.com` below with your own values.

<tip>
If you connected R2 with **Set up with Cloudflare** in Chunkify, the guided setup already attaches your playback domain, adds segment caching, and configures browser access.

Continue with [Keep playlists fresh](#keep-playlists-fresh) to configure playlist caching, then [Verify playback](#verify-playback).
</tip>

## Connect a playback domain

In Cloudflare, open **R2 → Overview**, select your bucket, then open **Settings**.

1. Under **Custom Domains**, select **Add**.
2. Enter a playback hostname, such as `media.example.com`, and select **Continue**.
3. Review the DNS record, then select **Connect Domain**.
4. Wait for the domain status to become **Active** before testing HTTPS delivery.

Use the [R2 custom domain connection](https://developers.cloudflare.com/r2/buckets/public-buckets/#connect-a-bucket-to-a-custom-domain) rather than a CNAME to an `r2.dev` URL. Cloudflare reserves `r2.dev` for development traffic.

<note>
Connecting a custom domain makes the bucket's objects publicly readable at their URLs, including any source videos stored there. Keep private sources in a separate bucket if needed.
</note>

The domain root must map to the bucket root. Keep the output prefix in the URL. Do not add a rewrite that removes it or adds it a second time.

## Allow playback from your website

The R2 bucket's CORS policy also supplies the headers for its custom domain. In the bucket's **Settings → CORS policy**, merge this playback rule with any 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 with your website's origin. Keep `https://chunkify.dev` for playback in the Chunkify dashboard. The rule allows playback reads and byte-range requests.

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

After changing CORS, purge previously cached files so their old response headers do not persist. Cloudflare's [R2 CORS guide](https://developers.cloudflare.com/r2/buckets/cors/#use-cors-with-a-custom-domain) explains this behavior.

## Configure caching

Chunkify writes HLS playlists with `Cache-Control: no-cache` because they can change as renditions and captions become available. Completed media segments have a long cache lifetime.

Cloudflare caches `.mp4` files by default, including HLS initialization files. Its [default file extensions](https://developers.cloudflare.com/cache/concepts/default-cache-behavior/#default-cached-file-extensions) do not include `.m4s` segments or `.m3u8` playlists.

### Cache video segments

Select the domain's website in Cloudflare, then open **Caching → Cache Rules → Create rule**. Name the rule `Chunkify video segments` and choose **Custom filter expression**.

Use **Edit expression** to enter this filter:

```text
(http.host eq "media.example.com" and ends_with(http.request.uri.path, ".m4s"))
```

Set the following options, then deploy the rule:

| Setting | Value |
| --- | --- |
| Cache eligibility | Eligible for cache |
| Edge TTL | Follow the response's Cache-Control header; bypass cache if it is missing |
| Browser TTL | Respect origin |

Keep the origin's cache lifetimes. A fixed TTL override can retain responses longer than Chunkify intends. See [Cloudflare's cache rule settings](https://developers.cloudflare.com/cache/how-to/cache-rules/settings/).

### Keep playlists fresh

Create a second rule named `Chunkify HLS playlists` with this filter:

```text
(http.host eq "media.example.com" and ends_with(http.request.uri.path, ".m3u8"))
```

Set **Cache eligibility** to **Bypass cache**, then deploy. Avoid rules that force a browser cache lifetime for playlists so the browser honors Chunkify's `no-cache` header.

Place these rules after broader rules that affect the playback hostname. Cloudflare applies matching rules in order, and the [last value for a setting wins](https://developers.cloudflare.com/cache/how-to/cache-rules/order/).

## Add the CDN domain to Chunkify

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

```text
https://media.example.com
```

Enter only the HTTPS origin, without an output prefix, path, or query string, then select **Save changes**. The guided Cloudflare setup fills in this field for you.

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

```text
https://media.example.com/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 `CF-Cache-Status: HIT`, indicating a cache hit.

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://media.example.com/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 `404` | Confirm the exact object key in R2 and keep the output prefix in the URL. Test a real object URL; the domain root does not list files. If the file now exists, purge a previously cached `404`. |
| Browser reports a CORS error | Check the requesting website's origin in the bucket's CORS policy, then purge old cached responses. |
| Playback shows outdated renditions or captions | Check the playlist bypass rule and any later cache rules that override it. |
| Player receives an HTML challenge | Review Cloudflare security rules on the playback hostname. Media requests must receive the requested file. |
