VPC subnet auto assign public IP should be disabled
When a subnet auto-assigns public IPs, every EC2 instance launched into it gets an internet-routable address by default, regardless of whether the workload needs public exposure. This creates an implicit attack surface that grows with every new instance and is easy to miss during routine deployments.
Disabling auto-assign forces teams to explicitly request public IPs through Elastic IPs or per-instance overrides, making public exposure a deliberate decision rather than an inherited default. That single setting change eliminates an entire category of accidental exposure incidents where internal services, databases, or batch workers end up reachable from the internet.
Retrofit consideration
Toggling map_public_ip_on_launch on existing subnets does not remove public IPs already assigned to running instances. You must stop and restart (not reboot) each affected instance, or migrate to new instances, to drop the auto-assigned public IP. Provision NAT gateways or Elastic IPs first if any workloads depend on outbound connectivity.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "vpc" {
source = "hipaa.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "nist80053.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "cisv80ig1.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "nist800171.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "nydfs23.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "ffiec.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "rbicybersecurity.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "rbiitfnbfc.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "fedramplow.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
module "vpc" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
map_public_ip_on_launch = false
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
}
If you use terraform-aws-modules/vpc/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">=6.0.0"
azs = ["eu-west-1a", "eu-west-1b"]
cidr = "10.0.0.0/16"
name = "abc123"
private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
map_public_ip_on_launch = false
}
Use AWS provider resources directly. See docs for the resources involved: aws_subnet.
resource "aws_subnet" "this" {
cidr_block = "10.1.0.0/24"
vpc_id = "vpc-12345678"
map_public_ip_on_launch = false
}
What this control checks
The control checks that aws_subnet resources have map_public_ip_on_launch set to false or omitted (the attribute defaults to false). A subnet fails if the attribute is explicitly set to true. To remediate, set map_public_ip_on_launch = false on every aws_subnet resource. Workloads that need outbound internet access should use a NAT gateway attached to the route table. Instances that genuinely need a public IP should use aws_eip associated via aws_eip_association, which makes the exposure explicit and auditable.
Common pitfalls
Default VPC subnets have auto-assign enabled
The default VPC AWS creates in every region comes with subnets where
MapPublicIpOnLaunchistrue, and Terraform won't manage them unless you explicitly import them. Import withterraform import aws_default_subnetand setmap_public_ip_on_launch = false, or delete the default VPC entirely if it's not in use.Terraform default value is compliant but not explicit
The default is
false, so omittingmap_public_ip_on_launchpasses this control. But an omitted attribute is invisible in a diff. Set it explicitly so that if someone changes it totrue, the intent change shows up in the pull request and triggers review.Instance-level override bypasses subnet setting
Even with
map_public_ip_on_launch = falseon the subnet, anaws_instanceor launch template can setassociate_public_ip_address = trueon its network interface. This control covers the subnet default only. Per-instance overrides need their own review.Existing instances retain their public IPs after change
Get this wrong and your audit will surprise you: changing
map_public_ip_on_launchfromtruetofalseonly affects instances launched after the change. Running instances keep their auto-assigned public IPs until stopped and started, not just rebooted. Runaws ec2 describe-instanceswith a filter forassociation.public-ipto find any that are still exposed.
Audit evidence
Config rule evaluation results showing all subnets marked COMPLIANT, or an equivalent report from a cloud security posture management tool, are the primary artifacts. A VPC console screenshot showing 'Auto-assign public IPv4 address' as 'No' for each subnet works for initial review. For ongoing assurance, CloudTrail logs of ModifySubnetAttribute API calls show whether the setting has been re-enabled since the last assessment. Where exceptions exist (a bastion subnet, for example), include documented approval records and compensating controls such as security group restrictions.
Framework-specific interpretation
HIPAA Omnibus Rule 2013: 45 CFR 164.312(a)(1) requires access controls that limit ePHI system exposure. Disabling auto-assign public IP is a direct implementation of that requirement, reducing the chance that a database or application server handling patient data ends up internet-reachable because someone launched it in the wrong subnet.
NIST SP 800-53 Rev 5: SC-7 and AC-4 both care about intentional information flow. Auto-assign public IP breaks AC-4 enforcement because the exposure happens at launch, before any explicit authorization. Disabling it means public internet access is always a deliberate configuration choice, not an inherited subnet default.
FedRAMP Moderate Baseline Rev 4: SC-7 requires boundary protection, which means restricting which components can communicate directly with the internet. Auto-assigned public IPs create uncontrolled boundary points that multiply with every new instance. Disabling the setting keeps public exposure confined to explicitly designated components, which is what FedRAMP assessors look for when reviewing network architecture.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
vpc_subnet_auto_assign_public_ip_disabledAWS Config Managed Rule:
SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLEDCheckov Check:
CKV_AWS_130Powerpipe Control:
aws_compliance.control.vpc_subnet_auto_assign_public_ip_disabledProwler Check:
vpc_subnet_no_public_ip_by_defaultAWS Security Hub Control:
EC2.15KICS Query:
52f04a44-6bfa-4c41-b1d3-4ae99a2de05c
Last reviewed: 2026-03-08