---
cuppy_hub: aws
cuppy_path: patterns/cloudfront-to-alb.md
cuppy_section: docs
cuppy_doc: 63BT-KWT6
cuppy_doc_uuid: bdd13959-95db-4b70-8221-119635588e18
cuppy_permalink: https://cuppy.io/aws/d/63BT-KWT6
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/patterns/cloudfront-to-alb
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: af889f1b48305deb1ad592ca5d7155a9a64f7c162615ce8d76af5e75c915d55c
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# CloudFront to a load balancer

**Prefer a VPC origin.** The ALB becomes *internal*, in private subnets, and CloudFront is the only way in. Use a public ALB only where VPC origins aren't supported. In that case, restrict its security group to CloudFront's origin-facing prefix list.

## Option 1: VPC origin (private ALB)

### What crosses the handoff

| Side | Provides / expects |
|---|---|
| **CloudFront** | A VPC origin resource (`CreateVpcOrigin`) naming the ALB's ARN. CloudFront creates a service-managed network interface in your private subnet and a security group named `CloudFront-VPCOrigins-Service-SG`. Don't edit that group, and don't name your own groups that way. The VPC origin takes up to 15 minutes to reach `Deployed`. |
| **VPC / ALB** | An internal ALB that is Active, in a VPC **with an internet gateway attached** (it marks the VPC as internet-receiving; you don't route to it). A private subnet with a free IPv4 address (and IPv6 for dual-stack). The ALB's security group admits the listener port from `CloudFront-VPCOrigins-Service-SG` (tighter) or from the prefix list `com.amazonaws.global.cloudfront.origin-facing` (plus `com.amazonaws.global.ipv6.cloudfront.origin-facing` for dual-stack). The **outbound** NACL allows ephemeral ports 1024–65535. Inbound NACL rules aren't evaluated for this traffic. |

### Known-good

1. Internal ALB in two private subnets, with a security group admitting 443 from the service-managed group.
2. VPC origin created (IPv4, or dual-stack since 2026-10-15) and `Deployed`.
3. Distribution origin is the VPC origin. Use HTTPS to the ALB with a certificate whose name matches the origin domain (see below).
4. Viewer certificate in ACM **us-east-1**; ALB certificate in the ALB's Region.
5. Existing public setups migrate through a **staging distribution**: test the VPC origin, promote, then make the subnets private.

WebSockets through VPC origins are supported (2026-05-01; no extra cost). Not supported: gRPC, Lambda@Edge origin request/response triggers, Gateway Load Balancers, NLBs with TLS listeners (NLBs also need a security group). VPC origins exist in most commercial Regions, with some AZ exceptions. Check the list on the VPC origins page before you design.

## Option 2: public ALB, locked down

| Side | Provides / expects |
|---|---|
| **CloudFront** | A custom origin whose domain is the ALB's DNS name or, better, a name of yours that is on the ALB's certificate. |
| **ALB** | Internet-facing, in public subnets. Its security group admits 443 **only from the prefix list** `com.amazonaws.global.cloudfront.origin-facing`. That prefix list counts against the rules-per-group quota by its *weight*, which can be large (`reference/vpc/security-groups-and-nacls.md`). |

The prefix list stops direct access from outside CloudFront's ranges. It doesn't stop *other* CloudFront distributions from using your ALB as an origin. For that, add a check at the ALB, such as a secret header or WAF rule (UNVERIFIED: a common practice, not in the collected sources), or use Option 1.

## Origin TLS, for both options

CloudFront verifies the origin certificate. One of its names must match the **origin domain name** (or the `Host` header CloudFront forwards), and the chain must be complete and from a CA on Mozilla's list. Otherwise you get 502 with `X-Cache: Error from cloudfront`. The ALB's default `…elb.amazonaws.com` name can't be on any public certificate, so an HTTPS origin needs a custom name on the ALB's certificate.

## Known-bad

| Configuration | Symptom |
|---|---|
| Origin domain is the ALB DNS name, HTTPS to the origin | 502: no certificate can match `…amazonaws.com` |
| ALB certificate missing intermediates, or expired | 502 |
| Internal ALB as an ordinary custom origin (no VPC origin) | 504: CloudFront can't reach private addresses |
| VPC origin; ALB security group doesn't admit the service group or prefix list | 504 |
| VPC origin; outbound NACL blocks ephemeral ports | 504 |
| VPC without an internet gateway | VPC origin creation fails |
| Dual-stack VPC origin; security group admits only the IPv4 prefix list | IPv6 requests fail (UNVERIFIED symptom) |
| VPC origin's ALB target group relies on WebSocket health checks | ALB health checks don't support WebSockets: give the target group an HTTP health path |
| Public ALB open to `0.0.0.0/0` | Works, but CloudFront (and WAF at the edge) can be bypassed |
| ALB returns 502/504 itself (targets unhealthy, keep-alive too short) | CloudFront relays it: `guides/elb/fix-alb-5xx.md` |

## Which side is wrong?

- **502 with `X-Cache: Error from cloudfront`** → TLS between CloudFront and the ALB (names, chain), or DNS (`NonS3OriginDnsError` for a public custom origin).
- **504** → reachability from CloudFront: security group, NACL, VPC origin state, or an internal ALB without a VPC origin.
- **5xx that also appears when you call the ALB directly** → the ALB or its targets.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
