Skip to content

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 MapPublicIpOnLaunch is true, and Terraform won't manage them unless you explicitly import them. Import with terraform import aws_default_subnet and set map_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 omitting map_public_ip_on_launch passes this control. But an omitted attribute is invisible in a diff. Set it explicitly so that if someone changes it to true, 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 = false on the subnet, an aws_instance or launch template can set associate_public_ip_address = true on 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_launch from true to false only affects instances launched after the change. Running instances keep their auto-assigned public IPs until stopped and started, not just rebooted. Run aws ec2 describe-instances with a filter for association.public-ip to 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.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: vpc_subnet_auto_assign_public_ip_disabled

  • AWS Config Managed Rule: SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED

  • Checkov Check: CKV_AWS_130

  • Powerpipe Control: aws_compliance.control.vpc_subnet_auto_assign_public_ip_disabled

  • Prowler Check: vpc_subnet_no_public_ip_by_default

  • AWS Security Hub Control: EC2.15

  • KICS Query: 52f04a44-6bfa-4c41-b1d3-4ae99a2de05c

Last reviewed: 2026-03-08