VPC Origin Now Supports Dual Stack. I Tried to See If CloudFront Origin Requests Can Reach ALB via IPv6

VPC Origin Now Supports Dual Stack. I Tried to See If CloudFront Origin Requests Can Reach ALB via IPv6

Verified CloudFront VPC origin dual-stack support with IPv6 connectivity to internal ALB. VPC requires a standard IGW, but subnets can route IPv6 default traffic through an Egress-only IGW.
2026.10.16

This page has been translated by machine translation. View original

Introduction

On October 15, 2026, dual-stack support was added for CloudFront VPC origins.

https://aws.amazon.com/about-aws/whats-new/2026/10/cloudfront-vpc-origin-dualstack/

With this dual-stack support, IPv6 can now be used end-to-end from CloudFront all the way to ALBs, NLBs, and EC2 instances on private subnets. Previously, this segment of communication was IPv4 only.

In this article, we configured an internal ALB as a VPC origin in the Tokyo region and confirmed from the ALB access logs that CloudFront's IPv6 address is recorded as the connection source.

Architecture

We created two private subnets in a VPC with IPv6 enabled, and pointed the IPv6 default route (::/0) to an Egress-only IGW. Inside the subnets, we placed an internal ALB (dual-stack, returning a fixed 200 response on HTTP:80) and delivered the ALB access logs to CloudWatch Logs.

The ALB's SG allowed only TCP 80 from the IPv6 prefix list com.amazonaws.global.ipv6.cloudfront.origin-facing.

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-vpc-origins.html

Creating a VPC origin requires attaching a regular IGW to the VPC and reserving at least one available IPv4 and IPv6 address in the private subnets. These available addresses are used for service-managed ENIs created by CloudFront.

No route to the regular IGW is created, and the IPv6 default route points to the Egress-only IGW.

Confirming IPv6 in the ALB Access Logs

When a request was made to the CloudFront distribution's domain name, the ALB's fixed response returned 200.

$ curl -sS -i https://dxxxxxxxxxxxxx.cloudfront.net/
HTTP/2 200
x-cache: Miss from cloudfront

hello from internal dualstack ALB

The ALB access log (CloudWatch Logs) recorded this request with the following entry.

{
  "time": "2026-10-15T20:24:07.380120Z",
  "user_agent": "Amazon CloudFront",
  "client_port": "2001:db8:17bd:2f73:3ceb:b969:b646:3200:25216",
  "elb_status_code": "200",
  "ip_address": "192.168.28.87"
}

client_port is the source address and port. The address has been replaced with a documentation address for illustration purposes. In all 4 access log entries reviewed, the IP address portion of client_port (before replacement) was IPv6, user_agent was Amazon CloudFront, and elb_status_code was 200.

Summary

We confirmed that using a dual-stack VPC origin, CloudFront can connect via IPv6 to an internal ALB on a private subnet whose IPv6 default route points to an Egress-only IGW.

This is useful when building a network that uses end-to-end IPv6 communication, or when you want to consolidate external access through CloudFront. Since the origin can remain in a private subnet while being accessible only through CloudFront, it also reduces the risk of direct access due to security group misconfigurations.

CloudFormation Template

The template and stack creation command used for verification are as follows.

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC origin dualstack verification (IPv6 VPC, internal dualstack ALB, CloudFront VPC origin, and ALB access logs)

Parameters:
  ProjectName:
    Type: String
    Default: vpco-ds
  CloudFrontIpv6PrefixListId:
    Type: String
    Default: pl-0f28cd4a128e7b13a
    Description: com.amazonaws.global.ipv6.cloudfront.origin-facing
  CloudFrontIpv4PrefixListId:
    Type: String
    Default: ''
    Description: com.amazonaws.global.cloudfront.origin-facing (empty = do not allow IPv4)

Conditions:
  AllowIpv4: !Not [!Equals [!Ref CloudFrontIpv4PrefixListId, '']]

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 192.168.0.0/18
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Ref ProjectName

  IPv6CidrBlock:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      VpcId: !Ref VPC
      AmazonProvidedIpv6CidrBlock: true

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6CidrBlock
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 192.168.0.0/20
      AvailabilityZone: !Select [0, !GetAZs '']
      Ipv6CidrBlock: !Select [0, !Cidr [!Select [0, !GetAtt VPC.Ipv6CidrBlocks], 2, 64]]
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-1'

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6CidrBlock
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 192.168.16.0/20
      AvailabilityZone: !Select [1, !GetAZs '']
      Ipv6CidrBlock: !Select [1, !Cidr [!Select [0, !GetAtt VPC.Ipv6CidrBlocks], 2, 64]]
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-2'

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-igw'

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  EgressOnlyIGW:
    Type: AWS::EC2::EgressOnlyInternetGateway
    Properties:
      VpcId: !Ref VPC

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-private-rt'

  PrivateRouteIPv6:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationIpv6CidrBlock: '::/0'
      EgressOnlyInternetGatewayId: !Ref EgressOnlyIGW

  PrivateSubnet1Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnet2Association:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable

  AlbSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow CloudFront origin-facing prefix lists to the internal ALB
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourcePrefixListId: !Ref CloudFrontIpv6PrefixListId
          Description: CloudFront origin-facing (IPv6)
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-alb-sg'

  AlbSecurityGroupIngressIpv4:
    Type: AWS::EC2::SecurityGroupIngress
    Condition: AllowIpv4
    Properties:
      GroupId: !Ref AlbSecurityGroup
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourcePrefixListId: !Ref CloudFrontIpv4PrefixListId
      Description: CloudFront origin-facing (IPv4)

  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub '${ProjectName}-alb'
      Scheme: internal
      Type: application
      IpAddressType: dualstack
      Subnets:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      SecurityGroups:
        - !Ref AlbSecurityGroup

  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref Alb
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: fixed-response
          FixedResponseConfig:
            StatusCode: '200'
            ContentType: text/plain
            MessageBody: hello from internal dualstack ALB

  AccessLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/vendedlogs/${ProjectName}-alb-access'
      RetentionInDays: 1

  AccessLogSource:
    Type: AWS::Logs::DeliverySource
    Properties:
      Name: !Sub '${ProjectName}-alb-access'
      LogType: ALB_ACCESS_LOGS
      ResourceArn: !Ref Alb

  CwlDestination:
    Type: AWS::Logs::DeliveryDestination
    Properties:
      Name: !Sub '${ProjectName}-cwl'
      DeliveryDestinationType: CWL
      OutputFormat: json
      DestinationResourceArn: !GetAtt AccessLogGroup.Arn

  AccessLogDelivery:
    Type: AWS::Logs::Delivery
    Properties:
      DeliverySourceName: !Ref AccessLogSource
      DeliveryDestinationArn: !GetAtt CwlDestination.Arn

  VpcOrigin:
    Type: AWS::CloudFront::VpcOrigin
    DependsOn: InternetGatewayAttachment
    Properties:
      VpcOriginEndpointConfig:
        Name: !Sub '${ProjectName}-origin'
        Arn: !Ref Alb
        HTTPPort: 80
        HTTPSPort: 443
        OriginProtocolPolicy: http-only
        IpAddressType: dualstack

  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Comment: !Sub '${ProjectName} VPC origin dualstack verification'
        Enabled: true
        HttpVersion: http2and3
        IPV6Enabled: true
        Origins:
          - Id: VpcOriginAlb
            DomainName: !GetAtt Alb.DNSName
            VpcOriginConfig:
              VpcOriginId: !Ref VpcOrigin
        DefaultCacheBehavior:
          TargetOriginId: VpcOriginAlb
          ViewerProtocolPolicy: redirect-to-https
          CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # Managed-CachingDisabled
          Compress: true

Outputs:
  DistributionId:
    Value: !Ref Distribution
  DistributionDomainName:
    Value: !GetAtt Distribution.DomainName
  VpcOriginId:
    Value: !Ref VpcOrigin
  LogGroupName:
    Value: !Ref AccessLogGroup

:::

Save it as template.yaml and create the stack.

aws cloudformation create-stack --stack-name vpco-ds --template-body file://template.yaml
aws cloudformation wait stack-create-complete --stack-name vpco-ds

Share this article

AWSのお困り事はクラスメソッドへ