> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/expo-open-ota/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/expo-open-ota/cdn/cloudfront.md).

# CloudFront

CloudFront delivers private update assets from an Amazon S3 origin.

This integration requires `STORAGE_MODE=s3`. Configure [S3 Storage](/expo-open-ota/storage/s3-storage.md) first.

{% hint style="info" %}
Keep the required values available before deployment. You can set them in a `.env` file.
{% endhint %}

{% stepper %}
{% step %}

### Generate a key pair

Run these commands in a terminal:

```bash
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
```

{% endstep %}

{% step %}

### Create a public key and key group

1. Open [CloudFront public keys](https://us-east-1.console.aws.amazon.com/cloudfront/v4/home#/publickey).
2. Select **Create public key**.
3. Name the key and upload `public_key.pem`.
4. Open [CloudFront key groups](https://us-east-1.console.aws.amazon.com/cloudfront/v4/home#/keygrouplist).
5. Select **Create key group**.
6. Name the group and select the public key.

Copy the public key ID from the public keys table:

```dotenv
CLOUDFRONT_KEY_PAIR_ID=your-public-key-id
```

{% endstep %}

{% step %}

### Create an origin access control

1. Open [CloudFront origin access controls](https://us-east-1.console.aws.amazon.com/cloudfront/v4/home#/originAccess).
2. Select **Create control setting**.
3. Name the control.
4. Set **Origin type** to **S3**.
5. Enable **Sign requests (recommended)**.
6. Leave **Do not override authorization header** disabled.
   {% endstep %}

{% step %}

### Create the distribution

Open [CloudFront distributions](https://us-east-1.console.aws.amazon.com/cloudfront/v4/home#/distributions), then select **Create distribution**.

#### Origin

* Select the bucket configured in [S3 Storage](/expo-open-ota/storage/s3-storage.md).
* Leave **Origin path** empty.
* Select **Origin access control settings (recommended)**.
* Select the origin access control you created.

{% hint style="warning" %}
Leave **Origin path** empty, even with `BUCKET_KEY_PREFIX`.

The server already signs paths with this prefix. Adding it again causes asset `404` responses.
{% endhint %}

#### Default cache behavior

Configure these settings:

* Enable **Compress objects automatically**.
* Set **Viewer protocol policy** to **HTTPS only**.
* Allow only `GET` and `HEAD` methods.
* Enable **Restrict viewer access**.
* Select **Trusted key groups**, then select your key group.
* Set the cache policy to **CachingOptimized**.

#### Other settings

Configure the remaining settings for your application. An alternate domain name is recommended.

Set the distribution domain name or alternate domain name:

```dotenv
CLOUDFRONT_DOMAIN=your-cloudfront-domain
```

{% endstep %}

{% step %}

### Allow CloudFront to read the bucket

1. Open [S3 buckets](https://us-east-1.console.aws.amazon.com/s3/home).
2. Select the bucket configured in [S3 Storage](/expo-open-ota/storage/s3-storage.md).
3. Open **Permissions**.
4. Edit the **Bucket policy**.
5. Add this policy:

```json
{
  "Version": "2008-10-17",
  "Id": "PolicyForCloudFrontPrivateContent",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipal",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::AWS_ACCOUNT_ID:distribution/YOUR_CLOUDFRONT_DISTRIBUTION_ID"
        }
      }
    }
  ]
}
```

Replace `YOUR_BUCKET_NAME`, `AWS_ACCOUNT_ID`, and `YOUR_CLOUDFRONT_DISTRIBUTION_ID`.
{% endstep %}
{% endstepper %}

### Environment variables

Set these values for every CloudFront deployment:

```dotenv
CLOUDFRONT_KEY_PAIR_ID=your-public-key-id
CLOUDFRONT_DOMAIN=your-cloudfront-domain
```

#### CloudFront private key

The CloudFront private key is server-wide. Use one key for the full deployment.

Its source follows `KEYS_STORAGE_TYPE`. When `KEYS_STORAGE_TYPE` is set — in either mode — the key is read only from the matching source: `aws-secrets-manager` reads `AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID`, `environment` reads `PRIVATE_CLOUDFRONT_KEY_B64`, and `local` reads `PRIVATE_CLOUDFRONT_KEY_PATH`. Any other configured source is ignored, with a startup warning when the selected source is empty. When `KEYS_STORAGE_TYPE` is unset, the server tries the sources in order: Secrets Manager → base64 → file path. See [Keys](/expo-open-ota/key-store/keys.md) for details.

{% tabs %}
{% tab title="AWS Secrets Manager" %}

```dotenv
AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID=name-of-the-cloudfront-private-key-secret
```

Used when `KEYS_STORAGE_TYPE=aws-secrets-manager`. When `KEYS_STORAGE_TYPE` is unset, it is the first source tried.
{% endtab %}

{% tab title="Environment variable" %}
Encode the private key:

```bash
base64 -i private_key.pem | tr -d '\n'
```

Then set the encoded value:

```dotenv
PRIVATE_CLOUDFRONT_KEY_B64=base64-encoded-cloudfront-private-key
```

Used when `KEYS_STORAGE_TYPE=environment`. When `KEYS_STORAGE_TYPE` is unset, it is tried after the Secrets Manager source.
{% endtab %}

{% tab title="Local file" %}

```dotenv
PRIVATE_CLOUDFRONT_KEY_PATH=/path/to/cloudfront-private-key.pem
```

Used when `KEYS_STORAGE_TYPE=local`. When `KEYS_STORAGE_TYPE` is unset, it is the last source tried. The file must exist on every replica.
{% endtab %}
{% endtabs %}

{% hint style="info" %}
Signed asset URLs expire after **10 minutes**.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/expo-open-ota/cdn/cloudfront.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
