Back to blog
AWSBest PracticesCloud SecurityCompute & ContainersOperations & Compliance

EC2 Classic Instance Detected: Migrating Off Retired AWS Infrastructure

EC2-Classic is retired and unsupported. Learn how to detect Classic instances, migrate them into a VPC with CLI steps, and prevent regressions with policy-as-code.

TL;DR

This check flags any EC2 instance still running on the retired EC2-Classic networking platform. EC2-Classic was fully retired by AWS, so a flagged instance means stale infrastructure, weak network isolation, and no path to modern features. Fix it by migrating the workload into a VPC and decommissioning the Classic resource.

If Lensix surfaces an EC2 Classic Instance Detected finding, it has spotted a workload tied to a platform AWS retired years ago. EC2-Classic predates the Virtual Private Cloud (VPC) model that every modern AWS account uses by default. Anything still depending on it is a relic, and relics in production tend to break in expensive ways.

This post walks through what the check looks for, why a Classic instance is a real problem rather than a cosmetic one, and exactly how to migrate off it.


What this check detects

The ec2_classic check inspects your running EC2 instances and reports any that are not associated with a VPC. In the AWS API, this shows up as an instance with no VpcId and no SubnetId. Those fields are populated for every VPC instance, so their absence is a reliable signal that the instance is running in EC2-Classic.

You can spot the same thing yourself with the CLI:

aws ec2 describe-instances \
  --query 'Reservations[].Instances[?!not_null(VpcId)].[InstanceId,InstanceType,LaunchTime]' \
  --output table

Any instance returned here has no VPC association, which means it is Classic. An empty result is what you want.

Note: EC2-Classic was the original AWS networking model, where instances launched into a single flat network shared across all customers in a Region. VPC replaced it with isolated, customer-controlled virtual networks. Accounts created after December 2013 never had EC2-Classic at all.


Why it matters

AWS announced the retirement of EC2-Classic and completed it on August 15, 2022. In practice, a flagged instance usually means one of two things: a long-lived account that never finished its migration, or a forgotten workload that has been quietly running and is now in an unsupported state. Either way, the risks are concrete.

1. Weak network isolation

In EC2-Classic, instances live in a shared flat network. You cannot define subnets, route tables, network ACLs, or VPC security group rules the way you can in a VPC. Classic security groups are weaker, they do not support egress filtering, and you lose the layered controls that modern security architectures depend on. An attacker who lands on a Classic instance has far fewer barriers to lateral movement.

2. No modern features

A huge list of AWS capabilities simply does not work in EC2-Classic: newer instance families, enhanced networking, IPv6, VPC endpoints, PrivateLink, GuardDuty VPC flow log analysis, and more. You are locked out of performance, cost, and security improvements that the rest of your fleet enjoys.

Danger: EC2-Classic is retired and unsupported. AWS can no longer guarantee these resources will keep functioning, and there is no SLA covering them. A Classic instance that stops or fails may not be recoverable, and you may be unable to launch a replacement. Treat any finding as urgent.

3. Compliance and audit exposure

Running retired infrastructure is hard to defend in an audit. Frameworks like SOC 2, PCI DSS, and ISO 27001 all expect supported, patchable platforms with documented network controls. A Classic instance fails that bar on multiple counts and tends to generate findings during every assessment cycle.


How to fix it

The fix is migration into a VPC. There is no in-place toggle that converts a Classic instance to a VPC instance, so the work is to recreate the workload inside a VPC and cut over. Here is the practical sequence.

Step 1: Confirm what you are dealing with

aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[?VpcId==`null`].{ID:InstanceId,Type:InstanceType,AZ:Placement.AvailabilityZone,Name:Tags[?Key==`Name`]|[0].Value}' \
  --output table

Record each instance ID, its purpose, attached volumes, security groups, Elastic IPs, and DNS entries that point at it. Migration is mostly about not losing any of these dependencies.

Step 2: Create an AMI of the instance

An AMI is the cleanest way to carry the existing disk state into the new environment.

aws ec2 create-image \
  --instance-id i-0123456789abcdef0 \
  --name "classic-migration-webapp-$(date +%Y%m%d)" \
  --description "Pre-migration snapshot of Classic instance" \
  --no-reboot

Warning: Using --no-reboot avoids downtime but can produce a filesystem-inconsistent image for write-heavy workloads. For databases or anything with active writes, quiesce the application or omit the flag to allow a clean reboot during imaging.

Step 3: Make sure you have a target VPC and subnet

Most accounts have a default VPC. If you need a dedicated one:

# Find existing VPCs
aws ec2 describe-vpcs --query 'Vpcs[].{ID:VpcId,CIDR:CidrBlock,Default:IsDefault}' --output table

# Find subnets in your chosen VPC
aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=vpc-0abc123" \
  --query 'Subnets[].{ID:SubnetId,AZ:AvailabilityZone,CIDR:CidrBlock}' \
  --output table

Step 4: Recreate security groups in the VPC

Classic security groups cannot be reused in a VPC. Recreate the rules in a VPC security group, and take the opportunity to tighten them and add explicit egress rules.

aws ec2 create-security-group \
  --group-name webapp-vpc-sg \
  --description "Web app SG migrated from Classic" \
  --vpc-id vpc-0abc123

aws ec2 authorize-security-group-ingress \
  --group-id sg-0newsg123 \
  --protocol tcp --port 443 --cidr 0.0.0.0/0

Step 5: Launch the replacement in the VPC

aws ec2 run-instances \
  --image-id ami-0newimage123 \
  --instance-type m6i.large \
  --subnet-id subnet-0def456 \
  --security-group-ids sg-0newsg123 \
  --key-name my-keypair \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=webapp-vpc}]'

Note that migration is a good moment to move off old instance families. The example above jumps to m6i, which is cheaper and faster than the t1, m1, or m3 families common on Classic.

Step 6: Move Elastic IPs and cut over

Classic Elastic IPs must be moved to VPC scope before they can attach to a VPC instance.

# Move a Classic EIP into VPC scope
aws ec2 move-address-to-vpc --public-ip 203.0.113.25

# Associate it with the new instance
aws ec2 associate-address \
  --instance-id i-0newinstance \
  --allocation-id eipalloc-0xyz789

Validate the new instance, update DNS records to point at it, and run the application through your smoke tests before retiring the original.

Danger: Only terminate the Classic instance once you have confirmed the VPC replacement is fully working and you have an AMI backup. Terminated Classic instances cannot be relaunched, since EC2-Classic no longer accepts new launches.

aws ec2 terminate-instances --instance-ids i-0123456789abcdef0

Tip: If you have many Classic instances, AWS Application Migration Service (MGN) can automate the lift into a VPC with minimal application changes and a tighter cutover window than manual AMI work.


How to prevent it from happening again

Once migrated, you want to guarantee no Classic resource ever reappears. Since EC2-Classic launches are already blocked by AWS, prevention is mostly about detection and account hygiene.

Enable EC2-Classic platform restriction

For accounts that historically had EC2-Classic, you can confirm the platform is disabled so nothing can accidentally depend on it:

aws ec2 describe-account-attributes \
  --attribute-names supported-platforms \
  --query 'AccountAttributes[].AttributeValues[].AttributeValue'

A result of only VPC means Classic is fully disabled. If EC2 appears, the account still has Classic enabled and should be locked down.

Add a continuous check

Keep the Lensix ec2_classic check running on a schedule so any drift, such as a resource restored into a Classic-enabled sandbox account, gets caught immediately. Pair it with an AWS Config rule for defense in depth:

{
  "ConfigRuleName": "ec2-instances-in-vpc",
  "Source": {
    "Owner": "AWS",
    "SourceIdentifier": "EC2_INSTANCES_IN_VPC"
  },
  "Scope": {
    "ComplianceResourceTypes": ["AWS::EC2::Instance"]
  }
}

Gate infrastructure in CI/CD

All new instances should be defined as code and launched into an explicit subnet. A simple policy-as-code rule rejects any EC2 resource without a subnet reference. Here is an Open Policy Agent example for Terraform plans:

package terraform.ec2

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_instance"
  not resource.change.after.subnet_id
  msg := sprintf("EC2 instance '%s' must specify a subnet_id (no EC2-Classic)", [resource.address])
}

Wire that into your pipeline so a plan that omits a subnet fails before it ever reaches apply.


Best practices

  • Default to private subnets. Place instances in private subnets and reach the internet through a NAT gateway or VPC endpoints rather than assigning public IPs directly.
  • Use VPC security groups with explicit egress. One of the biggest gains over Classic is outbound filtering. Define egress rules deliberately instead of leaving the default allow-all in place.
  • Modernize instance families during migration. Newer generations like m6i, c7g, and r7g deliver better price-performance, so a migration is the right time to right-size.
  • Tag for ownership. Every migrated instance should carry owner, environment, and cost-center tags so stale resources are easy to find and decommission later.
  • Audit account attributes across the org. In multi-account setups, confirm every account reports only the VPC platform, not just the ones you migrated.

Note: If this check still fires after a confirmed migration, double-check that you are scanning the right Region. EC2-Classic was Region-scoped, and a forgotten instance often hides in a Region nobody actively uses.

An EC2-Classic finding is not something to schedule for next quarter. The platform is gone, the instance is unsupported, and the network controls around it are weaker than anything you would deploy today. Migrate it into a VPC, confirm Classic is disabled at the account level, and let continuous checks keep it that way.

Fix EC2-Classic Instances: Migrate to VPC | Lensix | Lensix