Skip to content
Codeloom
AWS

AWS VPC Networking: Subnets, NAT, and Security Groups

Learn AWS VPC networking fundamentals including subnets, NAT gateways, route tables, security groups, and NACLs to build secure cloud architectures.

·9 min read · By Codeloom
Intermediate 18 min read

What you'll learn

  • Design VPC architectures with public and private subnets
  • Configure NAT Gateways for private subnet internet access
  • Set up Security Groups and NACLs for layered security
  • Build route tables that control traffic flow

Prerequisites

  • AWS account with admin access
  • Basic understanding of IP addressing and CIDR notation

Amazon Virtual Private Cloud (VPC) is the networking foundation of every AWS deployment. Whether you are running a single EC2 instance or a fleet of containers behind a load balancer, the VPC defines how traffic flows, who can reach what, and how your resources connect to the internet and each other.

This guide walks through every essential VPC component, explains how they fit together, and provides working configurations you can deploy today.

What Is a VPC and Why It Matters

A VPC is a logically isolated section of the AWS cloud where you launch resources in a virtual network you define. You control the IP address range, create subnets, configure route tables, and set up gateways.

Every AWS account comes with a default VPC in each region, but production workloads should always use a custom VPC. The default VPC has permissive settings that make it easy to get started but difficult to secure at scale.

Key benefits of a custom VPC:

  • Full control over IP addressing so you can plan for growth and avoid conflicts with on-premises networks
  • Network segmentation to isolate workloads by tier, environment, or compliance boundary
  • Fine-grained security through security groups, NACLs, and private subnets
  • Connectivity options including VPN, Direct Connect, VPC peering, and Transit Gateway

Planning Your CIDR Block

The CIDR block defines the IP address range for your entire VPC. Choose carefully because you cannot change the primary CIDR after creation, though you can add secondary CIDRs later.

Common CIDR ranges for production VPCs:

# Small VPC - 256 addresses
CIDR: 10.0.0.0/24

# Medium VPC - 4,096 addresses (recommended for most workloads)
CIDR: 10.0.0.0/20

# Large VPC - 65,536 addresses
CIDR: 10.0.0.0/16

A /16 VPC gives you 65,536 addresses, which is usually more than enough. The bigger concern is avoiding overlap with other VPCs you might peer with or on-premises networks you might connect to via VPN.

Subnets: Public vs Private

Subnets divide your VPC into smaller segments, each mapped to a single Availability Zone (AZ). The distinction between public and private subnets is not a property of the subnet itself but rather the route table attached to it.

  • Public subnet: Route table includes a route to an Internet Gateway (IGW)
  • Private subnet: No direct route to the internet; traffic goes through a NAT Gateway or stays internal

A typical three-tier architecture uses six subnets across two AZs:

# Subnet layout for a production VPC (10.0.0.0/16)
Public Subnets:
  - 10.0.1.0/24  (us-east-1a) - Load balancers, bastion hosts
  - 10.0.2.0/24  (us-east-1b) - Load balancers, bastion hosts

Private App Subnets:
  - 10.0.10.0/24 (us-east-1a) - Application servers, containers
  - 10.0.11.0/24 (us-east-1b) - Application servers, containers

Private Data Subnets:
  - 10.0.20.0/24 (us-east-1a) - RDS, ElastiCache, other databases
  - 10.0.21.0/24 (us-east-1b) - RDS, ElastiCache, other databases

Here is a CloudFormation snippet that creates this layout:

Resources:
  ProductionVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: production-vpc

  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref ProductionVPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: public-subnet-a

  PrivateAppSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref ProductionVPC
      CidrBlock: 10.0.10.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: private-app-subnet-a

  PrivateDataSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref ProductionVPC
      CidrBlock: 10.0.20.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: private-data-subnet-a

Internet Gateway and Route Tables

An Internet Gateway (IGW) allows resources in public subnets to communicate with the internet. You attach one IGW per VPC and then create a route in the public subnet’s route table pointing 0.0.0.0/0 to the IGW.

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref ProductionVPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref ProductionVPC

  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetARouteAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTable

Each subnet can be associated with exactly one route table. If you do not explicitly associate a subnet with a route table, it uses the VPC’s main route table. This is a common source of misconfigurations, so always create explicit associations.

NAT Gateways for Private Subnet Internet Access

Resources in private subnets often need outbound internet access to download packages, call external APIs, or reach AWS service endpoints. A NAT Gateway provides this access without exposing the resources to inbound internet traffic.

NAT Gateways live in a public subnet and require an Elastic IP address:

  NatElasticIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatElasticIP.AllocationId
      SubnetId: !Ref PublicSubnetA

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref ProductionVPC

  PrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  PrivateAppSubnetARouteAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateAppSubnetA
      RouteTableId: !Ref PrivateRouteTable

NAT Gateways cost roughly $0.045/hour plus data processing charges. For high-availability, deploy one NAT Gateway per AZ. For dev/test environments, a single NAT Gateway or even a NAT instance can save costs.

An alternative to NAT Gateways is VPC Endpoints, which allow private subnets to reach AWS services like S3 and DynamoDB without going through the internet at all. Gateway endpoints for S3 and DynamoDB are free.

Security Groups: Stateful Instance-Level Firewalls

Security groups act as virtual firewalls at the instance level. They are stateful, meaning if you allow inbound traffic on a port, the response traffic is automatically allowed outbound regardless of outbound rules.

Key characteristics:

  • Allow rules only - you cannot create deny rules
  • Stateful - return traffic is automatically allowed
  • Instance level - attached to ENIs (network interfaces)
  • Evaluated as a group - all rules are evaluated before deciding to allow traffic

Here is a practical security group configuration for a three-tier application:

  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP/HTTPS from internet
      VpcId: !Ref ProductionVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0

  AppSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow traffic from ALB only
      VpcId: !Ref ProductionVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          SourceSecurityGroupId: !Ref ALBSecurityGroup

  DatabaseSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow traffic from app tier only
      VpcId: !Ref ProductionVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 5432
          ToPort: 5432
          SourceSecurityGroupId: !Ref AppSecurityGroup

Notice how each security group references the previous tier’s group ID rather than IP addresses. This is a best practice because it automatically adapts as instances scale up and down.

Network ACLs: Stateless Subnet-Level Firewalls

Network Access Control Lists (NACLs) operate at the subnet level and are stateless. Unlike security groups, NACLs support both allow and deny rules and are evaluated in order by rule number.

NACLs serve as a second layer of defense. The default NACL allows all inbound and outbound traffic. For production, consider tightening these rules:

  PrivateDataNACL:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref ProductionVPC

  # Allow inbound PostgreSQL from app subnets
  InboundPostgres:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref PrivateDataNACL
      RuleNumber: 100
      Protocol: 6
      RuleAction: allow
      CidrBlock: 10.0.10.0/23
      PortRange:
        From: 5432
        To: 5432

  # Allow return traffic (ephemeral ports)
  InboundEphemeral:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref PrivateDataNACL
      RuleNumber: 200
      Protocol: 6
      RuleAction: allow
      CidrBlock: 10.0.0.0/16
      PortRange:
        From: 1024
        To: 65535

  # Deny everything else inbound
  InboundDenyAll:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      NetworkAclId: !Ref PrivateDataNACL
      RuleNumber: 999
      Protocol: -1
      RuleAction: deny
      CidrBlock: 0.0.0.0/0

Because NACLs are stateless, you must explicitly allow return traffic on ephemeral ports (1024-65535). This is the most common mistake when working with NACLs.

VPC Flow Logs for Visibility

VPC Flow Logs capture information about IP traffic going to and from network interfaces in your VPC. They are essential for troubleshooting connectivity issues and security monitoring.

  VPCFlowLog:
    Type: AWS::EC2::FlowLog
    Properties:
      ResourceId: !Ref ProductionVPC
      ResourceType: VPC
      TrafficType: ALL
      LogDestinationType: cloud-watch-logs
      LogGroupName: /aws/vpc/flow-logs
      DeliverLogsPermissionArn: !GetAtt FlowLogRole.Arn

Flow log records show source IP, destination IP, ports, protocol, action (ACCEPT/REJECT), and byte counts. Send them to CloudWatch Logs for real-time monitoring or S3 for long-term analysis with Athena.

VPC Endpoints: Keeping Traffic Private

VPC Endpoints let your private subnets access AWS services without sending traffic over the internet. There are two types:

  • Gateway Endpoints (free): S3 and DynamoDB only. Added as a route table entry.
  • Interface Endpoints (paid): Most other AWS services. Creates an ENI in your subnet with a private IP.
  S3GatewayEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcId: !Ref ProductionVPC
      RouteTableIds:
        - !Ref PrivateRouteTable
      VpcEndpointType: Gateway

  SecretsManagerEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub com.amazonaws.${AWS::Region}.secretsmanager
      VpcId: !Ref ProductionVPC
      SubnetIds:
        - !Ref PrivateAppSubnetA
        - !Ref PrivateAppSubnetB
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref EndpointSecurityGroup

Gateway endpoints for S3 are free and should always be configured. They reduce NAT Gateway data processing costs and improve security by keeping S3 traffic within the AWS network.

Common VPC Design Mistakes

Undersized CIDR blocks. Start with a /16 unless you have a reason not to. Running out of IP addresses in a VPC is painful to fix.

Single AZ deployments. Always spread subnets across at least two AZs for high availability. An AZ outage should not take down your entire application.

Overly permissive security groups. Never use 0.0.0.0/0 on application or database security groups. Reference other security groups by ID instead.

No VPC endpoints. Without endpoints, traffic to S3 and DynamoDB from private subnets goes through the NAT Gateway, adding cost and latency.

Ignoring flow logs. Enable flow logs from day one. You will need them when troubleshooting connectivity or investigating security incidents.

Wrapping Up

AWS VPC networking is the foundation that every other service builds on. A well-designed VPC uses multiple AZs, separates public and private tiers, employs security groups and NACLs for defense in depth, and uses VPC endpoints to keep traffic private and reduce costs.

Start with the three-tier architecture described here and adjust based on your workload. The time you invest in VPC design pays dividends in security, reliability, and operational simplicity as your infrastructure grows.