Skip to content

CloudFront 504 Gateway Timeout for EC2 VPC Origin (IPv6 + Private IPv4)

0

Description:

We are attempting to remove Public IPv4, NAT, and ALB overhead costs from our infrastructure, one use-case at a time. The current use-case is web-facing stateful services (not fit for Lambda) that do not require high availability (so for example, load-balancing or blue/green deployments are excluded) and also are not containerized (so ECS/ECR are excluded from the solution). The short-term objective for technology demonstration purposes, is to use CloudFormation to stand up a stand-alone EC2 instance serving web traffic to the Internet.

We have created this simple template with an Ngix default page to demonstrate the issue:

We are experiencing a persistent "504 Gateway Timeout" error from a CloudFront distribution configured with an EC2 VPC Origin. The origin points to an EC2 instance (i-03fb7666cc52c9636) running Amazon Linux 2023, which is intended to be IPv6-only with a private IPv4 address.

Current Setup:

  • EC2 Instance:
    • Launched in a public subnet with a single IPv6 address and AssociatePublicIpAddress: false (no public IPv4).
    • UserData script successfully installs and starts Nginx, serving a default "Hello World" page on port 80.
    • System logs confirm Nginx is running and IPv6 outbound connectivity is working.
  • Security Group (RendezvousEc2SecurityGroup):
    • Ingress rule for TCP port 80 from ::/0 (public IPv6).
    • Diagnostic change applied: Added a temporary ingress rule for TCP port 80 from 10.0.0.0/8 (private IPv4 range) to allow CloudFront's VPC origin to connect. This did not resolve the 504 error.
  • CloudFront Distribution (RendezvousCloudFrontDistribution):
    • Configured with IPV6Enabled: true.
    • Origin uses VpcOriginConfig referencing RendezvousVpcOrigin.
    • DomainName for the origin is set to !GetAtt RendezvousEc2Instance.PrivateDnsName.
    • HTTPPort is 80, and OriginProtocolPolicy is http-only.

Problem: Despite Nginx running on the EC2 instance and security groups allowing traffic on port 80 (both IPv6 public and a broad IPv4 private range for diagnosis), CloudFront consistently returns a 504 Gateway Timeout. This indicates CloudFront is unable to establish a connection with the EC2 instance.

Request: What could be causing this?

Here are some relevant bits of the template:

  RendezvousEc2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub "${EnvironmentName}-RendezvousEc2LaunchTemplate"
      LaunchTemplateData:
        ImageId: !Ref AmiId # Dynamically provided AMI ID
        InstanceType: !Ref InstanceType
        IamInstanceProfile:
          Arn: !GetAtt RendezvousEc2InstanceProfile.Arn
        KeyName: !Ref KeyName # WHY: Enables SSH access to the EC2 instance using the specified key pair.
        UserData: !Base64
          Fn::Sub:
            - |
              #!/bin/bash -xe
              set -e
              echo "Starting UserData script at $(date)"
              exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
              echo "Starting UserData script at $(date)"

              # Install necessary packages for Amazon Linux 2023
              # WHY: dnf is the package manager for AL2023. We need aws-cli and nginx.
              # MISTAKES TO AVOID:
              # 1. Missing aws-cli: The signaling script will fail without it.
              # 2. Using yum/amazon-linux-extras: These are for AL2, not AL2023.
              dnf update -y
              dnf install -y nginx bind-utils curl aws-cli --allowerasing

              echo "Nginx, curl, and aws-cli installed."

              echo "Starting IPv6 connectivity diagnostics at $(date)"
              echo "Configuring IPv6 DNS resolver..."
              echo "nameserver 2001:4860:4860::8888" > /etc/resolv.conf
              echo "Checking /etc/resolv.conf after modification:"
              cat /etc/resolv.conf
              echo "Testing general outbound IPv6 connectivity to ipv6.google.com:"
              curl -6 -v https://ipv6.google.com
              echo "Finished IPv6 connectivity diagnostics at $(date)"

              # Configure Nginx for "Hello World"
              echo "Configuring Nginx..."
              echo "<h1>Hello from Rendezvous EC2 (IPv6-only)</h1>" > /usr/share/nginx/html/index.html
              systemctl start nginx
              systemctl enable nginx
              echo "Nginx started and enabled."

              # Signal CloudFormation that the instance is up
              # WHY: The cfn-signal utility cannot be used on an IPv6-only instance because
              # the CloudFormation API endpoint is IPv4. Instead, we invoke a Lambda function
              # which can then signal on our behalf.
              # MISTAKES TO AVOID:
              # 1. Missing IAM Permissions: Ensure the EC2 instance role has 'lambda:InvokeFunctionUrl' permission.
              # 2. Incorrect Payload: The payload must match what the Lambda function expects.
              # 3. Script Errors: Any error before this point will prevent the signal from being sent,
              #    causing the CloudFormation stack to time out and roll back, which is the correct behavior.
              echo "Signaling CloudFormation via Lambda Function URL..."

              # MISTAKES TO AVOID:
              # 1. Fragile Metadata Service: Relying on the metadata service can be fragile.
              #    Using a known tag and passing the region directly is more robust.
              # 2. Incorrect Function URL: Ensure the Function URL is correctly retrieved and used.
              # 3. Unescaped CloudFormation Variables: CloudFormation's !Sub function will try to
              #    resolve bash variables (e.g., $SIGNAL_PAYLOAD) as CloudFormation
              #    parameters or pseudo-parameters, leading to deployment failures.
              #    By directly embedding !Sub values and simplifying dynamic parts, we avoid this.
                  SIGNAL_PAYLOAD='{ "LogicalResourceId": "RendezvousEc2Instance", "Status": "SUCCESS" }'
                  LAMBDA_FUNCTION_URL="${LambdaFunctionUrl}"

                  curl -6 -X POST -H "Content-Type: application/json" \
                    -d "$SIGNAL_PAYLOAD" \
                    "$LAMBDA_FUNCTION_URL"

                  echo "UserData script finished at $(date). Lambda Function URL invoked."
            - LambdaFunctionUrl: !GetAtt RendezvousCfnSignalLambdaFunctionUrl.FunctionUrl
        InstanceMarketOptions:
          MarketType: spot
        TagSpecifications: # Tags for the instance launched by the Launch Template
          - ResourceType: instance
            Tags:
              - Key: Name
                Value: !Sub "${EnvironmentName}-RendezvousEc2Instance"

  # EC2 Spot Instance
  # WHY: This resource now references the Launch Template for its configuration,
  # correctly separating the instance definition from the market options.
  # It is configured with IPv6 and private IPv4, explicitly disabling public IPv4
  # to avoid associated charges.
  # MISTAKES TO AVOID:
  # 1. Missing Launch Template Reference: Ensure the LaunchTemplate property
  #    is correctly configured to point to the created Launch Template.
  # 2. Redundant Properties: Avoid duplicating properties already defined in
  #    the Launch Template (e.g., ImageId, InstanceType, UserData).
  RendezvousEc2Instance:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref RendezvousEc2LaunchTemplate
        Version: !GetAtt RendezvousEc2LaunchTemplate.LatestVersionNumber
      NetworkInterfaces:
        - DeviceIndex: 0
          GroupSet:
            - !GetAtt RendezvousEc2SecurityGroup.GroupId
          SubnetId: !ImportValue
            Fn::Sub: "CorePublicSubnet1Id-${EnvironmentName}" # Using a public subnet for IPv6 internet access
          Ipv6AddressCount: 1 # Request a single IPv6 address
          # MISTAKES TO AVOID:
          # 1. Assigning Public IPv4: This instance is intended to be IPv6-only.
          #    Removing AssociatePublicIpAddress ensures no public IPv4 is assigned.
          AssociatePublicIpAddress: false # WHY: Explicitly set to false for IPv6-only instance.
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-RendezvousEc2Instance"
    CreationPolicy: # WHY: CloudFormation waits for a signal from the custom resource before marking the instance creation as complete.
      ResourceSignal:
        Timeout: PT10M # MISTAKES TO AVOID: Ensure this timeout is sufficient for the instance to boot and the custom resource to signal.
        Count: 1 # Expect one signal from the custom resource
asked 7 months ago251 views
3 Answers
0
Accepted Answer

Finally got it working using the VPC Origin path for the CloudFront traffic, with the IPv6 used for outbound without a NAT. Key fixes:

  • InstanceId in the VPCOrigin
  • PrivateDnsName in the DistributionConfig
  • Allow 0.0.0.0/0
  • Use correct port (nginx default is 80)
  • Use an IPv6 call to Lambda for cfn.signal
answered 7 months ago
0

Things to check,

Place your EC2 instance in a public subnet with:

  • An IPv6 address assigned (either automatically from the VPC CIDR or manually).
  • IPv6 connectivity enabled for the subnet.
  • Attach a security group that allows inbound traffic from CloudFront’s edge locations over IPv6 on the port your application listens on (e.g., TCP 80 or 443).

Networking

  • The subnet must have a route to the internet over IPv6:
  • Add a default route (::/0) in the subnet’s route table pointing to an Internet Gateway (IGW)
  • Ensure the Internet Gateway has IPv6 enabled.

CloudFront

  • CloudFront does not let you directly target an IP address (IPv4 or IPv6). It requires a DNS hostname.
  • Assign an AAAA DNS record (for IPv6) pointing to your EC2’s IPv6 address:
EXPERT
answered 7 months ago
EXPERT
reviewed 7 months ago
  • Made some progress based on your last two points, thank you! After adding an AAAA DNS record on a spare domain I had laying around, the error changed to: ERROR Failed to contact the origin.

  • I see I had some incorrect port numbers too.

-1

The 504 Gateway Timeout error you're experiencing with CloudFront and your IPv6-only EC2 instance is likely related to how CloudFront's VPC origin connects to your EC2 instance. There are several potential causes for this issue:

  1. NAT Gateway and Subnet Configuration: When CloudFront attempts to connect to your EC2 instance through its VPC origin, it's likely trying to use IPv4 internally. If your EC2 instance is in a subnet with a NAT gateway attached, this could be blocking inbound connections from CloudFront. NAT gateways allow outbound connections but block inbound connections over the internet by default.

  2. CloudFront to Origin Communication: CloudFront has a default timeout of around 60-90 seconds for requests to origins. If your origin doesn't respond within this timeframe, CloudFront will return a 504 Gateway Timeout error.

  3. IPv6-only Configuration: While your EC2 instance is configured for IPv6, CloudFront's VPC origin functionality might be attempting to communicate with the instance using its private IPv4 address. Even though you've added an ingress rule for TCP port 80 from the private IPv4 range, there might be network path issues.

  4. Security Group Configuration: Although you've added ingress rules for both IPv6 and private IPv4, there might be other network ACLs or routing issues preventing the connection.

To resolve this issue, consider the following solutions:

  1. Check Subnet Configuration: Ensure your EC2 instance is in a subnet that has an Internet Gateway attached rather than a NAT Gateway. NAT Gateways block inbound connections, which could be causing the 504 errors.

  2. Review Network Path: Verify that there's a valid network path from CloudFront's VPC origin to your EC2 instance. This includes checking route tables, network ACLs, and security groups.

  3. Consider Dual-Stack Approach: For this specific use case, you might need to use a dual-stack approach (both IPv6 and public IPv4) temporarily until CloudFront's VPC origin fully supports IPv6-only origins.

  4. Test Direct Connectivity: Try to connect directly to the EC2 instance using its IPv6 address to confirm that Nginx is accessible via IPv6.

  5. CloudFront Origin Protocol: Ensure that the origin protocol policy in CloudFront is correctly set to match how your Nginx server is configured.

The most likely issue is related to the subnet configuration and how CloudFront's VPC origin is attempting to establish a connection to your EC2 instance. If your EC2 instance is in a subnet with a NAT gateway, try moving it to a subnet with an Internet Gateway instead.
Sources
Cloudfront 504 gateway Timeout | AWS re:Post
Cloudfront timeout to Application Load Balancer | AWS re:Post
HTTP 504 status code (Gateway Timeout) - Amazon CloudFront
DNS64 and NAT64 - Amazon Virtual Private Cloud

answered 7 months ago
    1. checked, 2. checked, 3. it's already dual-stack, 4. yes the direct connection to IPv6 nginx endpoint from an outside network is successful, 5. yes: OriginProtocolPolicy: http-only

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.