EC2 instances should be in a VPC
EC2 instances outside a VPC cannot use network ACLs and have no private IP address space isolation. EC2-Classic put instances on a flat, shared network where traffic filtering was limited to basic security group allow rules.
AWS retired EC2-Classic in August 2022, but older accounts may still carry residual resources or AMIs referencing Classic placement. Confirming VPC placement ensures every instance gets subnet-level routing controls, private DNS, and the ability to attach VPC endpoints for service-to-service communication without going over the public internet.
Retrofit consideration
All AWS accounts created after December 4, 2013 are VPC-only, and EC2-Classic was fully retired in August 2022. Retrofit work is minimal unless you are migrating very old workloads snapshotted from Classic environments, in which case verify network driver compatibility before relaunching.
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 "ec2_instance" {
source = "soc2.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "pcidss.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "hipaa.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "nist80053.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "cisv80ig1.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "nist800171.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "nydfs23.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "ffiec.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "rbicybersecurity.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "rbiitfnbfc.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "fedramplow.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "hipaasecurity2003.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "nist80053rev4.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
module "ec2_instance" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
If you use terraform-aws-modules/ec2-instance/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 "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = ">=6.0.0"
ami_ssm_parameter = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
instance_type = "t4g.nano"
subnet_id = "subnet-abc123"
}
Use AWS provider resources directly. See docs for the resources involved: aws_instance.
resource "aws_instance" "this" {
ami = "ami-abc12345"
instance_type = "t4g.nano"
vpc_security_group_ids = ["sg-abc12345"]
subnet_id = ["subnet-abc123", "subnet-def456"][0]
}
What this control checks
This control checks that each EC2 instance has a non-empty vpc_id / VpcId. In Terraform, an aws_instance can set subnet_id explicitly, or AWS places it into a default subnet in the default VPC for the selected Availability Zone when subnet_id is omitted. Both count as VPC placement. In practice, set subnet_id explicitly on aws_instance to avoid depending on default VPC existence. For aws_launch_template, cover subnet selection in the launch path via Auto Scaling group vpc_zone_identifier or equivalent. Instances where vpc_id is null or empty fail.
Common pitfalls
Default VPC deletion leaves no implicit placement target
Delete the default VPC and then launch an
aws_instancewithoutsubnet_id, and the launch call fails immediately. There is no fallback placement target. Setsubnet_idexplicitly on everyaws_instanceso the resource is never dependent on default VPC existence.Launch templates without subnet in network_interfaces
Set
subnet_idinside thenetwork_interfacesblock onaws_launch_template, or cover subnet selection in the Auto Scaling group'svpc_zone_identifier. If neither specifies a subnet, instances may fail to launch or land in an unintended default subnet with no indication of which path was taken.Legacy AMIs snapshotted from EC2-Classic
AMIs built from older Classic environments may carry kernel or network driver settings that cause problems when launched in a VPC. Before reusing old AMIs in VPC subnets, verify enhanced networking compatibility: the instance type needs ENA or
ixgbevfsupport, and the AMI needs the corresponding driver installed.Confusing VPC placement with network isolation
Being in a VPC does not mean the instance is isolated. An instance in a public subnet with
associate_public_ip_address = trueand a permissiveaws_security_groupis reachable from the internet. This control confirms VPC membership only. Restrictive security groups, private subnets, and NACLs are separate concerns.
Audit evidence
An auditor expects AWS Config evaluation results for the managed rule ec2-instances-in-vpc showing all EC2 instances COMPLIANT. Supporting evidence includes aws ec2 describe-instances output where every instance object has a non-empty VpcId field. EC2 console screenshots showing the VPC ID column populated for each instance are also acceptable.
For continuous assurance, Security Hub findings for this control should show PASSED across all evaluated regions. Historical compliance can be demonstrated through Config conformance pack reports exported to S3.
Framework-specific interpretation
SOC 2: CC6.1 and CC6.6 expect network segmentation and logical access controls around information assets. Security group rules and NACLs are only available to instances running in a VPC, so VPC membership is the prerequisite for satisfying those criteria.
PCI DSS v4.0: Requirement 1 in PCI DSS v4.0 wants network controls separating the cardholder data environment from untrusted networks. VPC placement is one part of meeting that requirement. You still need to configure the security groups and NACLs to actually restrict traffic; placement alone does not segment the CDE.
HIPAA Omnibus Rule 2013: 45 CFR 164.312(e)(1) calls for mechanisms to guard ePHI in transit. VPC placement routes inter-instance traffic over private, controlled paths and makes it practical to attach VPC endpoints that keep traffic off the public internet. Encryption in transit is still required separately and is not satisfied by VPC membership alone.
NIST SP 800-53 Rev 5: SC-7 (Boundary Protection) and AC-4 (Information Flow Enforcement) both expect managed interfaces where traffic policies can be enforced. A VPC provides those interfaces at the subnet level, with routing tables, NACLs, and security groups as the enforcement points for communications between instances and external networks.
FedRAMP Moderate Baseline Rev 4: SC-7 (Boundary Protection) requires a managed network interface at the system boundary for all federal information systems. VPC placement is that boundary for EC2, enabling ingress and egress filtering through security groups and network ACLs without the open, uncontrolled exposure of EC2-Classic.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
ec2_instance_in_vpcAWS Config Managed Rule:
INSTANCES_IN_VPCPowerpipe Control:
aws_compliance.control.ec2_instance_in_vpc
Last reviewed: 2026-03-09