Skip to main content
emnode / learn
Compliance

Move resources into private networks (VPC isolation)

One capability across databases, search, serverless and EC2: put workloads inside private VPC subnets and reach AWS services privately, so nothing depends on a route to the public internet.

14 min·10 sections·AWS

Last reviewed

VPC isolation: the basics

What does "inside a private network" actually mean across AWS services?

Network isolation is one capability that AWS expresses in several different shapes. A Lambda function with no VPC config runs on an AWS-managed network with a default route to the internet (Lambda.3). An OpenSearch or Elasticsearch domain can be created with a public endpoint or with VPC placement, and the choice is permanent (Opensearch.2, ES.2). An RDS instance can sit in a subnet whose route table points at an internet gateway (RDS.18). A Redshift cluster can route its bulk COPY and UNLOAD traffic over the public AWS network instead of through your VPC (Redshift.7). And a fleet of EC2 instances can reach Systems Manager, EC2, ECR and other control planes either over the public internet or through interface VPC endpoints (EC2.10, EC2.17, EC2.55, EC2.56, EC2.57, EC2.58, EC2.60).

AWS Security Hub turns each of these into its own control, so one estate can fail a scatter of network-placement checks at once. They look like separate problems on the report, but they are one capability: keep workloads inside private subnets you govern, and give them a private path to the AWS services they call rather than a route out to the internet. The shared goal is that your security groups, network ACLs and VPC flow logs actually see and govern the traffic, instead of it slipping out over a default route nobody is watching.

Some of these are a single setting (Redshift enhanced VPC routing, an SSM endpoint trio), some are a one-off migration (a public OpenSearch domain has to be rebuilt inside a VPC and the data moved across), and some need supporting plumbing so the workload can still reach what it needs (a VPC-attached Lambda loses its default internet egress). The job is to inventory what runs on, or routes to, the public internet, decide which placements are deliberate, and move the rest into private networks with a private path to AWS services.

In this lesson you will learn how AWS expresses network placement across serverless, search, databases, data warehousing and EC2, how to find every workload that runs on or routes to the public internet, and how to move them into private subnets with a private path to AWS services without breaking the few that legitimately need public access. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The endpoint you cannot un-publish

Most network-placement controls remediate in place with a single setting flip, but one does not. An OpenSearch or Elasticsearch domain's network mode, public endpoint versus VPC, is welded on at creation: AWS's own guidance states that a domain created with a public endpoint cannot later be moved into a VPC. The only way out is to build a new domain inside private subnets and migrate the data across via snapshot or reindex. Every other isolation control in this group, Redshift enhanced VPC routing, an RDS subnet-group move, attaching a Lambda to a VPC, is reversible. The search domain is the one that teaches teams to choose private the first time.

Finding what runs on the public internet across an estate

Priya is the platform lead at a scale-up preparing for its first SOC 2 audit. Security Hub shows network-placement failures spread across Lambda, RDS, OpenSearch and Redshift in accounts that pre-date the team's current guardrails.

Rather than work the findings one by one, she starts with the resources that hold data and could be reached from the internet, separating deliberate public services from drift before changing anything.

Start with the highest-impact case: a database in a subnet whose route table points at an internet gateway is one security-group rule from public exposure.

$ aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=subnet-0a1b2c3d4e5f6a7b8 --query 'RouteTables[0].Routes[*].{Dest:DestinationCidrBlock,Target:GatewayId}' --output table
-------------------------------------
| Dest | Target |
-------------------------------------
| 10.0.0.0/16 | local |
| 0.0.0.0/0 | igw-0abc123def |
-------------------------------------
# A default route to an IGW makes the subnet public. The database in it fails RDS.18.

Walk the route table, not the resource flag. An IGW default route is what makes a subnet public, regardless of PubliclyAccessible.

How AWS decides a resource is privately networkeddeep dive

Most of these controls resolve to one of three mechanisms. The first is placement: does the resource have a VPC configuration at all? Lambda.3 checks for a VpcConfig with at least one subnet; Opensearch.2 and ES.2 check for VPCOptions; the EC2 endpoint controls (EC2.10 SSM, EC2.55 ECR, EC2.56 ECR DKR, EC2.57 SSM, EC2.58 SSM messages and EC2.60 EC2) check whether interface VPC endpoints exist so instances reach those control planes without an internet path. The second is routing: RDS.18 walks the subnet's route table for a 0.0.0.0/0 entry pointing at an internet gateway, and Redshift.7 checks the EnhancedVpcRouting boolean that forces COPY and UNLOAD through your VPC. The third is interface hygiene, where EC2.17 and EC2.180 cover redundant ENIs and source/destination checks that affect how an instance routes.

These are coarse gates, not deep inspections. Lambda.3 does not evaluate whether the subnets are genuinely private; a VPC OpenSearch domain in a misconfigured public subnet still passes Opensearch.2. So passing the control is necessary but not sufficient: sound subnet, route-table and NACL design is still on you. Security Hub evaluates these through AWS Config, typically on a several-hour cycle, so a fix does not flip the finding to PASSED instantly even though the configuration change itself takes effect immediately.

Once a workload is in a private subnet it loses any default internet route. To keep reaching AWS services it needs a private path: a gateway VPC endpoint for S3 or DynamoDB (free, route-table based), an interface VPC endpoint for other services (per-AZ hourly plus per-GB), or a NAT gateway for general egress (hourly plus per-GB). Provisioning that path before you move the workload is what separates a clean migration from a broken nightly job, and choosing endpoints over NAT for narrow egress is what keeps the bill flat.

What is the impact of leaving workloads on the public network?

The direct impact is exposure and lost governance. A database in a public subnet, a search domain on a public endpoint, or a function on the AWS-managed network sits outside the security groups, route tables and flow logs that govern the rest of your estate. Its traffic is invisible to the tooling your security team relies on, and a single misconfiguration of the access policy or security group in front of it converts "controlled" into "reachable from anywhere." Internet-facing data services are found quickly by automated scanners, which is exactly why exposed databases and search clusters have been behind some of the largest data exposures on record.

The second-order impact is blast radius. A workload with a route to the internet can reach command-and-control endpoints and exfiltrate data if it is ever compromised, and after an incident the first question, "what could this resource reach?", has the worst possible answer. Moving workloads into private subnets with a scoped egress path bounds that blast radius to the handful of services they actually call.

On the compliance side, every modern framework, SOC 2, ISO 27001, HIPAA, PCI DSS and FedRAMP, maps these controls to network-segmentation and boundary-protection requirements (NIST SC-7 chief among them). A passing set of VPC-placement controls across every account is concrete, screenshot-able evidence that data-bearing workloads sit behind a network boundary you can observe and govern.

How do you move workloads into private networks safely?

Work the capability as one loop rather than chasing individual findings. The order matters: understand each workload's egress and decide which placements are deliberate before you move anything, so you do not take a live service offline.

1. Inventory everything on, or routing to, the public internet

Across services, list the resources that are not privately networked: Lambda functions with no VpcConfig, OpenSearch and Elasticsearch domains with no VPCOptions, RDS instances in subnets with an IGW route, Redshift clusters with enhanced VPC routing off, and VPCs with managed instances but no SSM, ECR or EC2 interface endpoints. Treat this inventory as the source of truth, not the Security Hub finding count, because one workload can trigger several controls.

2. Separate deliberate public services from drift, and map each workload's egress

Decide which placements are on purpose. A genuinely public web tier belongs behind CloudFront or a load balancer, never directly exposed. Everything else is drift. For each workload you will move, map what it actually calls before you touch its networking, because a workload in a private subnet loses its default internet route and any unprovisioned dependency will break.

3. Provision the private path, then move the workload

Give each workload a private route to the AWS services it needs: a free gateway endpoint for S3 and DynamoDB, an interface VPC endpoint for narrow egress, or a NAT gateway for broad egress. Then make the placement change: attach the Lambda to multi-AZ private subnets, move the RDS instance to a private subnet group, flip Redshift enhanced VPC routing, or stand up a new VPC OpenSearch domain and migrate the data via snapshot. Do data-store migrations in a maintenance window and verify connectivity before declaring it done.

4. Enforce private-by-default so it cannot drift back

Pin the setting in your Terraform or CloudFormation modules so new resources are born private, and back it with AWS Config rules and Service Control Policies that block public creation, for example denying an OpenSearch domain without VPC options. Prevention is what turns this from a recurring remediation into a one-time fix.

# Move the highest-impact case first: an RDS instance in a public subnet group.
aws rds create-db-subnet-group \
  --db-subnet-group-name prod-db-subnets-private \
  --db-subnet-group-description "Private subnets only - no IGW route" \
  --subnet-ids subnet-0aa11bb22cc33dd44 subnet-0ee55ff66aa77bb88

aws rds modify-db-instance \
  --db-instance-identifier prod-payments-db \
  --db-subnet-group-name prod-db-subnets-private \
  --apply-immediately

# Provide a private path before moving compute, so it can still reach AWS services.
# A free S3 gateway endpoint, or a narrow interface endpoint instead of a NAT gateway.
aws ec2 create-vpc-endpoint --vpc-id vpc-0a1b2c3d \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ssm \
  --subnet-ids subnet-0aa11 subnet-0bb22 \
  --security-group-ids sg-0ccfn33 --private-dns-enabled

# Force Redshift bulk traffic through the VPC (confirm an S3 gateway endpoint exists first).
aws redshift modify-cluster \
  --cluster-identifier analytics-prod --enhanced-vpc-routing

Quick quiz

Question 1 of 5

Security Hub shows network-placement failures across Lambda, RDS, OpenSearch and Redshift. What is the most efficient way to think about them?

You can now treat network isolation as one capability rather than a scatter of findings: inventory everything on or routing to the public internet, separate the deliberate public services and route them through a controlled front door, provision the cheaper private egress path, and move the rest into private subnets, leaving the one OpenSearch case to a planned migration. The Controls this lesson covers section below links every control in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many AWS Security Hub controls. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.