Elastic Load Balancer is Internet-Facing
Overview
This check identifies AWS Elastic Load Balancers that are configured as "internet-facing" rather than "internal." An internet-facing load balancer has a publicly resolvable DNS name and routes traffic from the internet to your backend resources. This check flags load balancers that may be unintentionally exposed to the public internet.
Risk
An unintended internet-facing load balancer exposes your backend services to the internet, creating several security risks:
- Reconnaissance: Attackers can discover and probe your services for vulnerabilities
- Credential stuffing: Public endpoints become targets for automated login attacks
- Application exploitation: Any flaws in your application become externally accessible
- Data exposure: Sensitive data may be inadvertently accessible from the internet
- DDoS attacks: Internet-facing resources are more susceptible to denial-of-service attacks
If your service is only meant for internal use (within your VPC or corporate network), it should use an internal load balancer instead.
Remediation Steps
Prerequisites
- AWS Console access with permissions to view and create load balancers
- Knowledge of whether your load balancer should be internal or public
- Access to the VPC and subnets where the load balancer will be deployed
Required IAM permissions
To work with Elastic Load Balancers, you need these permissions:
elasticloadbalancing:DescribeLoadBalancerselasticloadbalancing:CreateLoadBalancerelasticloadbalancing:DeleteLoadBalancerelasticloadbalancing:CreateTargetGroupelasticloadbalancing:CreateListenerec2:DescribeSubnetsec2:DescribeVpcsec2:DescribeSecurityGroups
AWS Console Method
Important: You cannot change an existing load balancer from internet-facing to internal. You must create a new internal load balancer and migrate your traffic.
Step 1: Identify your current load balancer configuration
- Open the EC2 console
- In the left navigation, click Load Balancers
- Find the load balancer flagged by Prowler
- Note the Scheme column - it will show "internet-facing"
- Record the current configuration: listeners, target groups, security groups, and subnets
Step 2: Create a new internal load balancer
- Click Create load balancer
- Choose the same type as your existing load balancer (Application, Network, or Gateway)
- Configure the basic settings:
- Name: Give it a descriptive name (e.g.,
my-app-internal-alb) - Scheme: Select Internal (this is the key setting)
- IP address type: IPv4 (or dual-stack if needed)
- Name: Give it a descriptive name (e.g.,
- Under Network mapping:
- Select the same VPC as your existing load balancer
- Select at least two private subnets (subnets without a route to an internet gateway)
- Configure security groups (for Application Load Balancers):
- Select appropriate security groups that allow traffic from your internal sources
- Configure listeners and routing:
- Set up the same listeners as your existing load balancer
- Point them to the same target groups (or create new ones)
- Review and click Create load balancer
Step 3: Update DNS and migrate traffic
- Update any DNS records or application configurations to point to the new internal load balancer's DNS name
- Test that traffic flows correctly through the new load balancer
- Once confirmed, delete the old internet-facing load balancer
AWS CLI (optional)
List your current load balancers and their schemes:
aws elbv2 describe-load-balancers \
--region us-east-1 \
--query 'LoadBalancers[*].[LoadBalancerName,Scheme,DNSName]' \
--output table
Create a new internal Application Load Balancer:
aws elbv2 create-load-balancer \
--name my-internal-alb \
--scheme internal \
--type application \
--subnets subnet-aaaa1111 subnet-bbbb2222 \
--security-groups sg-12345678 \
--region us-east-1
Replace:
my-internal-albwith your desired load balancer namesubnet-aaaa1111 subnet-bbbb2222with your private subnet IDs (at least two in different AZs)sg-12345678with your security group ID
Create a new internal Network Load Balancer:
aws elbv2 create-load-balancer \
--name my-internal-nlb \
--scheme internal \
--type network \
--subnets subnet-aaaa1111 subnet-bbbb2222 \
--region us-east-1
Verify the new load balancer is internal:
aws elbv2 describe-load-balancers \
--names my-internal-alb \
--region us-east-1 \
--query 'LoadBalancers[0].Scheme' \
--output text
Expected output: internal
Delete the old internet-facing load balancer (after migration):
aws elbv2 delete-load-balancer \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/old-internet-facing-alb/50dc6c495c0c9188 \
--region us-east-1
CloudFormation (optional)
AWSTemplateFormatVersion: '2010-09-09'
Description: Internal Application Load Balancer
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the load balancer will be deployed
PrivateSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Private subnets for the internal load balancer (at least two)
LoadBalancerName:
Type: String
Description: Name for the internal load balancer
Default: my-internal-alb
Resources:
InternalALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for internal ALB
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/8
Description: Allow HTTPS from internal networks
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 10.0.0.0/8
Description: Allow HTTP from internal networks
Tags:
- Key: Name
Value: !Sub '${LoadBalancerName}-sg'
InternalApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Ref LoadBalancerName
Scheme: internal
Type: application
IpAddressType: ipv4
Subnets: !Ref PrivateSubnetIds
SecurityGroups:
- !Ref InternalALBSecurityGroup
Tags:
- Key: Name
Value: !Ref LoadBalancerName
DefaultTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${LoadBalancerName}-tg'
Protocol: HTTP
Port: 80
VpcId: !Ref VpcId
TargetType: ip
HealthCheckEnabled: true
HealthCheckIntervalSeconds: 30
HealthCheckPath: /health
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
UnhealthyThresholdCount: 3
HTTPListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref InternalApplicationLoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn: !Ref DefaultTargetGroup
Outputs:
LoadBalancerArn:
Description: ARN of the internal load balancer
Value: !Ref InternalApplicationLoadBalancer
LoadBalancerDNSName:
Description: DNS name of the internal load balancer
Value: !GetAtt InternalApplicationLoadBalancer.DNSName
LoadBalancerScheme:
Description: Scheme of the load balancer (should be internal)
Value: !GetAtt InternalApplicationLoadBalancer.Scheme
Deploy with:
aws cloudformation deploy \
--template-file internal-alb.yaml \
--stack-name internal-alb-stack \
--parameter-overrides \
VpcId=vpc-12345678 \
PrivateSubnetIds=subnet-aaaa1111,subnet-bbbb2222 \
LoadBalancerName=my-internal-alb \
--region us-east-1
Terraform (optional)
# variables.tf
variable "vpc_id" {
description = "VPC ID where the load balancer will be deployed"
type = string
}
variable "private_subnet_ids" {
description = "List of private subnet IDs (at least two)"
type = list(string)
}
variable "load_balancer_name" {
description = "Name for the internal load balancer"
type = string
default = "my-internal-alb"
}
variable "internal_cidr_blocks" {
description = "CIDR blocks allowed to access the internal load balancer"
type = list(string)
default = ["10.0.0.0/8"]
}
# main.tf
resource "aws_security_group" "internal_alb" {
name = "${var.load_balancer_name}-sg"
description = "Security group for internal ALB"
vpc_id = var.vpc_id
ingress {
description = "Allow HTTPS from internal networks"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = var.internal_cidr_blocks
}
ingress {
description = "Allow HTTP from internal networks"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = var.internal_cidr_blocks
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.load_balancer_name}-sg"
}
}
resource "aws_lb" "internal" {
name = var.load_balancer_name
internal = true # This is the key setting
load_balancer_type = "application"
security_groups = [aws_security_group.internal_alb.id]
subnets = var.private_subnet_ids
enable_deletion_protection = false
tags = {
Name = var.load_balancer_name
Environment = "production"
}
}
resource "aws_lb_target_group" "default" {
name = "${var.load_balancer_name}-tg"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
tags = {
Name = "${var.load_balancer_name}-tg"
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.internal.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.default.arn
}
}
# outputs.tf
output "load_balancer_arn" {
description = "ARN of the internal load balancer"
value = aws_lb.internal.arn
}
output "load_balancer_dns_name" {
description = "DNS name of the internal load balancer"
value = aws_lb.internal.dns_name
}
output "load_balancer_scheme" {
description = "Scheme of the load balancer (should be internal)"
value = aws_lb.internal.internal ? "internal" : "internet-facing"
}
Deploy with:
terraform init
terraform apply \
-var="vpc_id=vpc-12345678" \
-var='private_subnet_ids=["subnet-aaaa1111","subnet-bbbb2222"]' \
-var="load_balancer_name=my-internal-alb"
Verification
After creating the internal load balancer:
- Go to the EC2 console
- Click Load Balancers in the left navigation
- Find your new load balancer
- Verify the Scheme column shows internal
- Note that the DNS name should start with
internal-(e.g.,internal-my-alb-123456789.us-east-1.elb.amazonaws.com)
CLI verification
aws elbv2 describe-load-balancers \
--names my-internal-alb \
--region us-east-1 \
--query 'LoadBalancers[0].{Name:LoadBalancerName,Scheme:Scheme,DNSName:DNSName}' \
--output table
Expected output should show Scheme: internal.
Re-run the Prowler check:
prowler aws --checks elb_internet_facing
Additional Resources
- How Elastic Load Balancing Works
- Create an Application Load Balancer
- Create a Network Load Balancer
- AWS WAF Web ACL Association
- CloudFormation LoadBalancer Resource
Notes
-
Cannot change scheme in-place: AWS does not allow changing an existing load balancer's scheme from internet-facing to internal (or vice versa). You must create a new load balancer with the desired scheme.
-
Private subnets required: Internal load balancers should be deployed in private subnets (subnets without a direct route to an internet gateway). Using public subnets defeats the purpose of an internal load balancer.
-
DNS resolution: Internal load balancer DNS names are only resolvable from within the VPC (or connected networks via VPN/Direct Connect/Transit Gateway).
-
When internet-facing is intentional: If your load balancer legitimately needs to be internet-facing (e.g., for a public website), you can suppress this finding. However, ensure you have proper security controls:
- Use AWS WAF to protect against web attacks
- Enforce TLS/HTTPS for encrypted connections
- Apply security groups with least-privilege rules
- Consider AWS Shield for DDoS protection
- Implement rate limiting where appropriate
-
Classic Load Balancers: If you're using Classic Load Balancers (ELB), the same principle applies - you must create a new load balancer with the internal scheme. Consider migrating to Application or Network Load Balancers for better features and security controls.
-
Migration downtime: Plan your migration carefully to minimize downtime. Consider using DNS-based cutover with low TTL values, or implement a gradual traffic shift if using Route 53 weighted routing.