Tarek Cheikh
Founder & AWS Security Expert
Your Amazon Virtual Private Cloud (VPC) is the network foundation of every AWS workload. Every EC2 instance, RDS database, Lambda function, and container runs inside a VPC. If the network layer is misconfigured, attackers can move laterally, exfiltrate data, or reach resources that should never be publicly accessible -- regardless of how well you have configured IAM or encryption.
In 2025, multiple incidents demonstrated the risk of poorly secured VPCs. A misconfigured security group exposing port 22 to the internet led to a full EC2 compromise and lateral movement across an organization's production environment. Separately, attackers leveraged overly permissive egress rules to exfiltrate sensitive data via DNS tunneling before GuardDuty alerted the team. AWS responded by launching VPC Block Public Access (November 2024) and VPC Encryption Controls (November 2025) -- two features that make it dramatically easier to enforce network-level security at scale.
This guide covers 12 battle-tested VPC security best practices, each with real AWS CLI commands, audit procedures, and the latest 2024-2026 updates from AWS.
Security groups are stateful firewalls attached to ENIs (Elastic Network Interfaces). They are the primary network access control for EC2 instances, RDS databases, ECS tasks, Lambda functions, and most other VPC resources. A single overly permissive rule can expose critical infrastructure to the entire internet.
# Create a security group with a meaningful description
aws ec2 create-security-group --group-name web-server-sg --description "Web servers - HTTPS only from ALB" --vpc-id vpc-0123456789abcdef0
# Add a rule referencing the ALB security group (not a CIDR)
aws ec2 authorize-security-group-ingress --group-id sg-0123456789abcdef0 --protocol tcp --port 443 --source-group sg-0fedcba9876543210 --description "HTTPS from ALB security group - JIRA-1234"
# Audit: find security groups with 0.0.0.0/0 ingress
aws ec2 describe-security-groups --query "SecurityGroups[?IpPermissions[?contains(IpRanges[].CidrIp, '0.0.0.0/0')]].[GroupId,GroupName]" --output table
Security Hub Controls: [EC2.18] Security groups should only allow unrestricted incoming traffic for authorized ports. [EC2.19] Security groups should not allow unrestricted access to high-risk ports. [EC2.2] VPC default security group should not allow inbound or outbound traffic.
CIS Benchmark: Control 5.2 (no ingress from 0.0.0.0/0 to port 22), Control 5.3 (no ingress from 0.0.0.0/0 to port 3389).
Every VPC comes with a default security group that allows all inbound traffic from members of the same group and all outbound traffic. If resources are accidentally launched without specifying a security group, they inherit these permissive defaults. CIS Benchmark Control 5.4 requires that the default security group restricts all traffic.
# Get the default security group for each VPC
aws ec2 describe-security-groups --filters "Name=group-name,Values=default" --query "SecurityGroups[].[VpcId,GroupId]" --output table
# Remove all inbound rules from default security group
aws ec2 revoke-security-group-ingress --group-id sg-DEFAULT --ip-permissions "$(aws ec2 describe-security-groups --group-ids sg-DEFAULT --query 'SecurityGroups[0].IpPermissions' --output json)"
# Remove all outbound rules from default security group
aws ec2 revoke-security-group-egress --group-id sg-DEFAULT --ip-permissions "$(aws ec2 describe-security-groups --group-ids sg-DEFAULT --query 'SecurityGroups[0].IpPermissionsEgress' --output json)"
# Verify it is empty
aws ec2 describe-security-groups --group-ids sg-DEFAULT --query "SecurityGroups[0].[IpPermissions,IpPermissionsEgress]"
AWS Config Rule: vpc-default-security-group-closed -- automatically flags any default security group with active rules.
CIS Benchmark: Control 5.4 (default security group restricts all traffic).
VPC Flow Logs capture metadata about IP traffic flowing through your network interfaces. Without flow logs, you have zero visibility into network-level activity -- you cannot detect port scans, lateral movement, or data exfiltration. CIS Benchmark Control 3.7 requires flow logging on all VPCs.
# Enable VPC Flow Logs to S3 (recommended for cost and analysis)
aws ec2 create-flow-logs --resource-type VPC --resource-ids vpc-0123456789abcdef0 --traffic-type ALL --log-destination-type s3 --log-destination arn:aws:s3:::my-flowlogs-bucket/vpc-logs/ --max-aggregation-interval 60 --log-format '${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status} ${vpc-id} ${subnet-id} ${az-id} ${sublocation-type} ${sublocation-id} ${pkt-srcaddr} ${pkt-dstaddr} ${region} ${pkt-src-aws-service} ${pkt-dst-aws-service} ${flow-direction} ${traffic-path} ${tcp-flags}'
# Enable VPC Flow Logs to CloudWatch for real-time alerting
aws ec2 create-flow-logs --resource-type VPC --resource-ids vpc-0123456789abcdef0 --traffic-type REJECT --log-destination-type cloud-watch-logs --log-group-name /vpc/flowlogs --deliver-logs-permission-arn arn:aws:iam::123456789012:role/VPCFlowLogsRole
# Verify flow logs are enabled for all VPCs
aws ec2 describe-flow-logs --query "FlowLogs[].[FlowLogId,ResourceId,LogDestinationType,TrafficType]" --output table
SELECT srcaddr, dstaddr, dstport, protocol, COUNT(*) AS hits
FROM vpc_flow_logs
WHERE action = 'REJECT'
AND start > to_unixtime(current_timestamp - interval '24' hour)
GROUP BY srcaddr, dstaddr, dstport, protocol
ORDER BY hits DESC
LIMIT 20;
Security Hub Control: [EC2.6] VPC flow logging should be enabled in all VPCs.
CIS Benchmark: Control 3.7 (VPC flow logging enabled in all VPCs).
Without VPC endpoints, traffic to AWS services (S3, DynamoDB, STS, CloudWatch, etc.) traverses the public internet via NAT Gateways or Internet Gateways. This creates unnecessary exposure and data transfer costs. VPC endpoints keep traffic on the AWS backbone network.
# Create an S3 Gateway endpoint (free)
aws ec2 create-vpc-endpoint --vpc-id vpc-0123456789abcdef0 --service-name com.amazonaws.eu-west-1.s3 --route-table-ids rtb-0123456789abcdef0
# Create an STS Interface endpoint (PrivateLink)
aws ec2 create-vpc-endpoint --vpc-id vpc-0123456789abcdef0 --vpc-endpoint-type Interface --service-name com.amazonaws.eu-west-1.sts --subnet-ids subnet-0123456789abcdef0 subnet-0fedcba9876543210 --security-group-ids sg-0123456789abcdef0 --private-dns-enabled
VPC endpoint policies restrict which principals and resources can be accessed through the endpoint. This is a critical component of the AWS data perimeter framework.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowOnlyMyOrg",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::my-org-*", "arn:aws:s3:::my-org-*/*"],
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-EXAMPLE"
}
}
}]
}
Priority endpoints to deploy: S3, DynamoDB, STS, KMS, CloudWatch Logs, ECR (dkr + api), Secrets Manager, SSM, and EC2 Messages.
Launched in November 2024, VPC Block Public Access (BPA) is a declarative, account-level control that authoritatively blocks internet traffic to and from your VPCs via Internet Gateways and Egress-only Internet Gateways. Unlike security groups and NACLs which require correct configuration on every resource, BPA is a single toggle that overrides all other network settings.
You can exclude specific VPCs or subnets that legitimately need internet access (e.g., public ALB subnets). This allows you to enforce BPA across your entire account while maintaining connectivity for front-end resources.
# Enable VPC Block Public Access at the account level (bidirectional)
aws ec2 modify-vpc-block-public-access-options --internet-gateway-block-mode block-bidirectional
# Enable ingress-only mode (allow NAT Gateway egress)
aws ec2 modify-vpc-block-public-access-options --internet-gateway-block-mode block-ingress
# Exclude a specific subnet for public ALB
aws ec2 create-vpc-block-public-access-exclusion --internet-gateway-exclusion-mode allow-bidirectional --subnet-id subnet-0123456789abcdef0
# Check current BPA status
aws ec2 describe-vpc-block-public-access-options
# List exclusions
aws ec2 describe-vpc-block-public-access-exclusions
Integration: BPA integrates with AWS Network Access Analyzer for impact analysis before enabling, and with VPC Flow Logs for ongoing visibility. There is no additional charge for using BPA.
Best practice: Enable BPA in ingress-only mode as a baseline for all accounts in your organization. Use SCP-based guardrails to prevent member accounts from disabling BPA.
Launched at re:Invent 2025, VPC Encryption Controls make it easy to audit and enforce encryption in transit within and across VPCs. This addresses a long-standing blind spot: while most organizations enforce encryption at the application layer, they have had no way to verify or enforce it at the network layer.
encryption-status field showing whether traffic is cleartext, encrypted via Nitro hardware, encrypted at the application layer, or both. Use this first to identify unencrypted flows.# Enable VPC Encryption Controls in monitor mode
aws ec2 create-vpc-encryption-control --vpc-id vpc-0123456789abcdef0 --mode monitor
# Query Flow Logs for unencrypted traffic
# The encryption-status field shows: cleartext | nitro | app-layer | both
# After reviewing, switch to enforce mode
aws ec2 modify-vpc-encryption-control --vpc-id vpc-0123456789abcdef0 --mode enforce
2025-2026 Update: VPC Encryption Controls were free during the introductory period (November 2025 - February 2026). Beginning March 2026, AWS charges a fixed hourly rate per VPC with encryption controls enabled.
Best practice: Start in monitor mode to identify plaintext traffic. Fix application-layer encryption gaps. Then switch to enforce mode to guarantee all in-transit traffic is encrypted.
Network Access Control Lists (NACLs) are stateless firewalls at the subnet level. Unlike security groups, NACLs evaluate both inbound and outbound rules independently and process rules in order by number. They provide a second layer of defense below security groups.
# Create a NACL for database subnets
aws ec2 create-network-acl --vpc-id vpc-0123456789abcdef0 --tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=db-subnet-nacl}]'
# Allow inbound PostgreSQL only from app subnets
aws ec2 create-network-acl-entry --network-acl-id acl-0123456789abcdef0 --rule-number 100 --protocol tcp --port-range From=5432,To=5432 --cidr-block 10.0.1.0/24 --rule-action allow --ingress
# Deny all other inbound traffic
aws ec2 create-network-acl-entry --network-acl-id acl-0123456789abcdef0 --rule-number 200 --protocol -1 --cidr-block 0.0.0.0/0 --rule-action deny --ingress
# Allow outbound ephemeral ports for return traffic (stateless!)
aws ec2 create-network-acl-entry --network-acl-id acl-0123456789abcdef0 --rule-number 100 --protocol tcp --port-range From=1024,To=65535 --cidr-block 10.0.1.0/24 --rule-action allow --egress
Caveat: NACLs are stateless. You must explicitly allow return traffic (ephemeral ports 1024-65535) on the opposite direction. Forgetting this is the most common NACL misconfiguration.
CIS Benchmark: Control 5.1 (ensure NACLs do not allow ingress from 0.0.0.0/0 to administration ports).
AWS Network Firewall is a managed, stateful firewall with IDS/IPS capabilities powered by Suricata rules. It provides deep packet inspection, domain-based filtering, and centralized egress control -- capabilities that security groups and NACLs cannot offer.
*.amazonaws.com, api.github.com). Block all other egress at the domain level.# Create a Network Firewall rule group for domain filtering
aws network-firewall create-rule-group --rule-group-name allow-approved-domains --type STATEFUL --capacity 100 --rule-group '{
"RulesSource": {
"RulesSourceList": {
"Targets": [".amazonaws.com", "api.github.com", ".datadog.com"],
"TargetTypes": ["TLS_SNI", "HTTP_HOST"],
"GeneratedRulesType": "ALLOWLIST"
}
}
}'
# Create the firewall policy
aws network-firewall create-firewall-policy --firewall-policy-name egress-policy --firewall-policy '{
"StatelessDefaultActions": ["aws:forward_to_sfe"],
"StatelessFragmentDefaultActions": ["aws:forward_to_sfe"],
"StatefulRuleGroupReferences": [
{"ResourceArn": "arn:aws:network-firewall:eu-west-1:123456789012:stateful-rulegroup/allow-approved-domains"}
]
}'
# Create the firewall
aws network-firewall create-firewall --firewall-name central-egress-fw --vpc-id vpc-INSPECTION --subnet-mappings SubnetId=subnet-FW1 SubnetId=subnet-FW2 --firewall-policy-arn arn:aws:network-firewall:eu-west-1:123456789012:firewall-policy/egress-policy
Best practice: Start with an allow-list approach for egress domains. Log all denied traffic to S3 for analysis. Use AWS Firewall Manager to deploy firewall policies across all accounts in your organization.
VPC Peering and Transit Gateway enable communication between VPCs, but they introduce lateral movement risk if routing is overly permissive. A key property of VPC peering is that it is non-transitive -- if VPC A peers with VPC B and VPC B peers with VPC C, VPC A cannot reach VPC C through B. Transit Gateway, however, enables hub-and-spoke routing that can be transitive if route tables are not properly isolated.
# Create isolated route tables for Transit Gateway
aws ec2 create-transit-gateway-route-table --transit-gateway-id tgw-0123456789abcdef0 --tag-specifications 'ResourceType=transit-gateway-route-table,Tags=[{Key=Name,Value=production-rt}]'
aws ec2 create-transit-gateway-route-table --transit-gateway-id tgw-0123456789abcdef0 --tag-specifications 'ResourceType=transit-gateway-route-table,Tags=[{Key=Name,Value=development-rt}]'
# Associate production VPCs with the production route table only
aws ec2 associate-transit-gateway-route-table --transit-gateway-route-table-id tgw-rtb-PROD --transit-gateway-attachment-id tgw-attach-PROD-VPC
# Audit: check for overly broad peering routes
aws ec2 describe-route-tables --query "RouteTables[].Routes[?VpcPeeringConnectionId!=null].[DestinationCidrBlock,VpcPeeringConnectionId]" --output table
CIS Benchmark: Control 5.5 (routing tables for VPC peering are least access).
Amazon GuardDuty automatically ingests VPC Flow Logs, DNS logs, and CloudTrail events to detect threats. It identifies port scanning, brute-force attempts, cryptocurrency mining, DNS exfiltration, and communication with known malicious IPs -- all without deploying any agents.
# Enable GuardDuty in the current region
aws guardduty create-detector --enable --finding-publishing-frequency FIFTEEN_MINUTES
# Enable GuardDuty across all regions
for region in $(aws ec2 describe-regions --query "Regions[].RegionName" --output text); do
aws guardduty create-detector --enable --region "$region" 2>/dev/null
echo "Enabled GuardDuty in $region"
done
# Integrate with Route 53 DNS Firewall for automated blocking
# GuardDuty findings feed into DNS Firewall managed domain lists
aws route53resolver create-firewall-rule --firewall-rule-group-id rslvr-frg-EXAMPLE --firewall-domain-list-id rslvr-fdl-EXAMPLE --priority 100 --action BLOCK --block-response NXDOMAIN --name block-guardduty-threats
Best practice: Enable GuardDuty in every region, even regions you do not actively use. Attackers frequently spin up resources in unused regions. Use GuardDuty Extended Threat Detection (launched 2025) for multi-stage attack sequence detection.
The AWS data perimeter framework uses three dimensions to control access: trusted identities, trusted resources, and expected networks. VPCs are the foundation of the network perimeter dimension. By combining VPC endpoints with endpoint policies, SCPs, and resource control policies (RCPs), you can ensure that data only flows between authorized principals and resources.
aws:SourceVpc and aws:SourceVpce conditions.{
"Version": "2012-10-17",
"Statement": [{
"Sid": "EnforceNetworkPerimeter",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::sensitive-data-*", "arn:aws:s3:::sensitive-data-*/*"],
"Condition": {
"StringNotEqualsIfExists": {
"aws:SourceVpc": "vpc-0123456789abcdef0"
},
"BoolIfExists": {
"aws:ViaAWSService": "false"
}
}
}]
}
The aws:VpceOrgID condition key (2025 update) allows you to write network perimeter policies that apply to all VPC endpoints in your organization, without listing individual VPC endpoint IDs.
{
"Condition": {
"StringNotEqualsIfExists": {
"aws:SourceOrgID": "o-EXAMPLE"
}
}
}
Best practice: Implement the data perimeter incrementally. Start by deploying VPC endpoints for critical services (S3, STS, KMS). Add endpoint policies. Then layer SCPs and resource policies. Monitor with CloudTrail and Access Analyzer.
Manual audits do not scale. Use AWS Security Hub, AWS Config, and automated remediation to continuously monitor and enforce VPC security posture across your organization.
# Enable CIS Benchmark v5.0 in Security Hub
aws securityhub batch-enable-standards --standards-subscription-requests '[{
"StandardsArn": "arn:aws:securityhub:::standards/cis-aws-foundations-benchmark/v/5.0.0"
}]'
# Deploy AWS Config conformance pack for VPC controls
aws configservice put-conformance-pack --conformance-pack-name vpc-security-pack --template-body file://vpc-conformance-pack.yaml
# Automated remediation: auto-revoke 0.0.0.0/0 rules
# Create a Config remediation action
aws configservice put-remediation-configurations --remediation-configurations '[{
"ConfigRuleName": "restricted-ssh",
"TargetType": "SSM_DOCUMENT",
"TargetId": "AWS-DisablePublicAccessForSecurityGroup",
"Automatic": true,
"MaximumAutomaticAttempts": 3,
"RetryAttemptSeconds": 60,
"Parameters": {
"GroupId": {"ResourceValue": {"Value": "RESOURCE_ID"}}
}
}]'
# Query Security Hub for failed VPC controls
aws securityhub get-findings --filters '{
"ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}],
"ProductFields": [{"Key": "ControlId", "Value": "EC2.2", "Comparison": "EQUALS"}]
}' --query "Findings[].[Title,Resources[0].Id]" --output table
Use EventBridge rules to trigger Lambda functions on Security Hub findings. For example, automatically revoke any security group rule that allows 0.0.0.0/0 ingress on SSH or RDP, and notify the team via SNS.
# Create EventBridge rule for Security Hub findings
aws events put-rule --name auto-remediate-open-sg --event-pattern '{
"source": ["aws.securityhub"],
"detail-type": ["Security Hub Findings - Imported"],
"detail": {
"findings": {
"Compliance": {"Status": ["FAILED"]},
"ProductFields": {"ControlId": ["EC2.13", "EC2.14"]}
}
}
}'
Best practice: Start with detective controls (monitoring and alerting). Once you are confident in the detection logic, enable automated remediation for high-confidence, low-risk actions like revoking 0.0.0.0/0 SSH rules.
| Misconfiguration | Risk | Detection |
|---|---|---|
| Security group allows 0.0.0.0/0 on SSH (port 22) | Brute-force attacks, unauthorized access | Security Hub [EC2.13], CIS 5.2 |
| Default security group has active rules | Accidental exposure of new resources | Security Hub [EC2.2], CIS 5.4 |
| VPC Flow Logs disabled | No network visibility for incident response | Security Hub [EC2.6], CIS 3.7 |
| No VPC endpoints for S3/STS | Traffic traverses public internet unnecessarily | Network Access Analyzer |
| Overly permissive VPC peering routes | Lateral movement across environments | CIS 5.5, route table audit |
| No egress filtering | Data exfiltration via DNS, HTTPS to attacker domains | Network Firewall logs, GuardDuty DNS findings |
| # | Practice | Priority |
|---|---|---|
| 1 | Enforce least privilege on security groups | Critical |
| 2 | Lock down default security group | Critical |
| 3 | Enable VPC Flow Logs everywhere | Critical |
| 4 | Deploy VPC Endpoints for AWS services | High |
| 5 | Enable VPC Block Public Access | High |
| 6 | Enforce encryption in transit | High |
| 7 | Layer NACLs for subnet-level defense | Medium |
| 8 | Deploy AWS Network Firewall | High |
| 9 | Secure VPC Peering and Transit Gateway | High |
| 10 | Enable GuardDuty for VPC threat detection | Critical |
| 11 | Implement data perimeter framework | Medium |
| 12 | Automate continuous compliance | High |
This article is just the start. Get the full picture with our free whitepaper - 8 chapters covering IAM, S3, VPC, monitoring, agentic AI security, compliance, and a prioritized action plan with 50+ CLI commands.
Comprehensive guide to securing AWS EC2 instances. Covers IMDSv2 enforcement, security groups, EBS encryption, SSM Session Manager, private subnets, VPC Flow Logs, Amazon Inspector, and AMI hardening.
Comprehensive guide to securing AWS Identity and Access Management. Covers MFA enforcement, least privilege, IAM Identity Center, SCPs, Access Analyzer, and credential management.
Comprehensive guide to securing Amazon Elastic Container Service. Covers task role separation, non-root containers, ECR image scanning, secrets management, GuardDuty runtime monitoring, network isolation, ECScape mitigation, and container image signing.