Sitemap
uninterrupted engineering

We are a specialized DevOps engineering team focused on e-commerce infrastructure. With deep expertise in MedusaJS and modern cloud technologies, we build, optimize, and manage high-performance online stores that scale.

Exposing Private Load Balancers with CloudFront VPC Origins

Keep your AWS load balancers private while using CloudFront. Learn how VPC Origins eliminate security risks with a complete Terraform guide.

5 min readSep 29, 2025

--

Press enter or click to view image in full size

Let’s explore CloudFront VPC Origins, an AWS feature that allows you to connect CloudFront directly to private resources in your VPC without exposing them to the Internet. In this article, we’ll see why this matters and how you can implement it using Terraform.

Introduction: Why Keep Your Load Balancers Private?

When building web applications on AWS, we’ve traditionally needed public load balancers so CloudFront could reach our origin servers. This presented several challenges:

  1. Your load balancer is exposed to the world — Even with tight security groups, a public load balancer increases your application’s attack surface
  2. Maintaining IP allow lists is a pain — The traditional approach requires whitelisting CloudFront’s IP ranges in your security groups, which change periodically and require updates
  3. Secret headers aren’t secure enough — Some try using secret headers as an alternative to IP whitelisting, but this “security by obscurity” approach can be discovered and exploited
  4. Direct access bypassing — Attackers might discover your load balancer’s public endpoint and bypass CloudFront entirely, circumventing any protections you’ve set up there

CloudFront VPC Origins provides a better solution. It creates a direct, secure connection between CloudFront and resources in your private subnets, keeping your load balancers completely hidden from the public internet while still being accessible through CloudFront.

The Architecture

Before diving into implementation details, let’s visualize how CloudFront VPC Origins creates a secure architecture. The diagram below illustrates the end-to-end flow from internet users to your private resources, showing how CloudFront VPC Origins bridges the gap between the public internet and your private VPC without exposing your infrastructure:

Press enter or click to view image in full size

Practical Implementation with Terraform

Let’s implement this using Terraform, based on our Medusa.js AWS module. We’ll go through this in a logical order based on dependencies.

1. Setting Up the Private Load Balancer

First, we create a load balancer in private subnets:

resource "aws_lb" "main" {
load_balancer_type = "application" # Default value
subnets = var.vpc.private_subnet_ids # The key part - using private subnets!
security_groups = [aws_security_group.lb.id]
name = "${local.prefix}-lb"
tags = local.tags
}

resource "aws_lb_target_group" "main" {
port = 9000 # Default container port for Medusa backend
protocol = "HTTP"
vpc_id = var.vpc.id
target_type = "ip"
name = "${local.prefix}-tg"
health_check {
protocol = "HTTP"
port = 9000
interval = 30
matcher = "200"
timeout = 3
path = "/health"
healthy_threshold = 3
unhealthy_threshold = 3
}

tags = local.tags
}

resource "aws_lb_listener" "main" {
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"

default_action {
type = "forward"
forward {
target_group {
arn = aws_lb_target_group.main.arn
}
}
}

lifecycle {
replace_triggered_by = [aws_lb_target_group.main]
}

tags = local.tags
}

2. Creating the Security Groups

Next, we set up security groups that only allow traffic from CloudFront VPC Origins:

# Using AWS-managed prefix list for CloudFront VPC Origins
data "aws_ec2_managed_prefix_list" "vpc_origin" {
name = "com.amazonaws.global.cloudfront.origin-facing"
}

resource "aws_security_group" "lb" {
name_prefix = "${local.prefix}-lb-"
description = "Allow inbound traffic from CloudFront VPC Origins"
vpc_id = var.vpc.id
tags = local.tags

lifecycle {
create_before_destroy = true
}
}

resource "aws_vpc_security_group_ingress_rule" "vpc_origin" {
security_group_id = aws_security_group.lb.id
prefix_list_id = data.aws_ec2_managed_prefix_list.vpc_origin.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
tags = local.tags
}

resource "aws_vpc_security_group_egress_rule" "lb" {
security_group_id = aws_security_group.lb.id
cidr_ipv4 = "0.0.0.0/0"
ip_protocol = "-1"
tags = local.tags
}

This is where the magic happens — instead of maintaining our own list of CloudFront IPs, we use AWS’s managed prefix list com.amazonaws.global.cloudfront.origin-facing. AWS keeps this updated automatically, so you don't have to worry about it.

3. Setting Up the VPC Origin Connection

Now, we create the CloudFront VPC Origin that connects to our private load balancer:

locals {
origin_id = "${local.prefix}-lb"
}

resource "aws_cloudfront_vpc_origin" "main" {
vpc_origin_endpoint_config {
name = local.origin_id
arn = aws_lb.main.arn
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"

origin_ssl_protocols {
quantity = 1
items = ["TLSv1.2"]
}
}

timeouts {
create = "30m" # Important: VPC Origins take time to provision
}

depends_on = [aws_lb_target_group.main, aws_security_group.lb]

tags = local.tags
}

Note: CloudFront VPC Origins can take a while to provision, and without extended timeout, Terraform might give up too early.

4. Configuring the CloudFront Distribution

Finally, we set up the CloudFront distribution to use our VPC Origin:

resource "aws_cloudfront_distribution" "main" {
enabled = true
comment = "My-Project-Dev-Backend" # Example of what title(local.prefix) might render

origin {
domain_name = aws_lb.main.dns_name
origin_id = local.origin_id

vpc_origin_config {
vpc_origin_id = aws_cloudfront_vpc_origin.main.id
}
}

# Default cache behavior
default_cache_behavior {
target_origin_id = local.origin_id
viewer_protocol_policy = "redirect-to-https"

# Cache settings for APIs - disabled to allow dynamic content
min_ttl = 0
default_ttl = 0
max_ttl = 0

forwarded_values {
query_string = true
headers = ["*"]

cookies {
forward = "all"
}
}

allowed_methods = ["GET", "HEAD", "POST", "PUT", "PATCH", "OPTIONS", "DELETE"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
}

# Certificate configuration
viewer_certificate {
cloudfront_default_certificate = true # Use CloudFront's default certificate
}

# Other settings
price_class = "PriceClass_100" # Use only North America and Europe edge locations

restrictions {
geo_restriction {
restriction_type = "none"
}
}

tags = {
Project = "my-project"
Environment = "dev"
Owner = "DevOps Team"
ManagedBy = "terraform"
Component = "Backend"
}
}

The vpc_origin_config block that references our VPC Origin is the key difference from a traditional CloudFront setup.

Conclusion: Solving Real Security Challenges

CloudFront VPC Origins represents a significant advancement for securing web applications on AWS, directly addressing the security challenges we outlined at the beginning:

Get Daniel Kraszewski’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember the problem of exposed load balancers? With VPC Origins, your load balancers now remain completely isolated in private subnets, dramatically reducing your attack surface. The headache of maintaining IP allowlists is eliminated through AWS’s managed prefix list com.amazonaws.global.cloudfront.origin-facing, which is automatically maintained for you.

Insecure secret header approaches are no longer needed, as the direct connection between CloudFront and your VPC resources provides a much stronger security model. And attackers trying to bypass CloudFront? With your load balancer in a private subnet, there’s simply no way for external traffic to reach it except through CloudFront.

There are a few practical considerations: CloudFront VPC Origins take time to provision, each region needs its own VPC Origin, there’s a cost for the connectivity, and setup is slightly more complex. However, for organizations handling sensitive data or with compliance requirements, these minor considerations are easily outweighed by the significant security benefits.

Originally published at https://u11d.com.

--

uninterrupted engineering
uninterrupted engineering

Published in uninterrupted engineering

We are a specialized DevOps engineering team focused on e-commerce infrastructure. With deep expertise in MedusaJS and modern cloud technologies, we build, optimize, and manage high-performance online stores that scale.