Verified10 commandsAI-assisted

Networking: VPC, Route 53 & ELB

.md

Verified against aws-cli/2.33.6, flags verified via `aws <cmd> help`, 2026-08-21 · official docs

VPC and subnet setup, routing and internet access, security group rules, Route 53 DNS records, and Elastic Load Balancing (ALB/NLB via elbv2).

VPCs and subnets#

aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-vpc}]'
aws ec2 describe-vpcs --filters "Name=tag:Name,Values=my-vpc"
aws ec2 create-subnet --vpc-id vpc-0123456789abcdef0 --cidr-block 10.0.1.0/24 --availability-zone us-east-1a
aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-0123456789abcdef0"

Internet access — gateway, route table, association#

aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway --internet-gateway-id igw-0123456789abcdef0 --vpc-id vpc-0123456789abcdef0
aws ec2 create-route-table --vpc-id vpc-0123456789abcdef0
aws ec2 create-route --route-table-id rtb-0123456789abcdef0 --destination-cidr-block 0.0.0.0/0 --gateway-id igw-0123456789abcdef0
aws ec2 associate-route-table --route-table-id rtb-0123456789abcdef0 --subnet-id subnet-0123456789abcdef0

A subnet is only "public" because its route table has a 0.0.0.0/0 route pointing at an internet gateway — there's no separate "make this subnet public" flag. This four-command chain (gateway → attach → route → associate) is the whole mechanism.

VPC peering#

aws ec2 create-vpc-peering-connection --vpc-id vpc-0123456789abcdef0 --peer-vpc-id vpc-0fedcba9876543210
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id pcx-0123456789abcdef0    # run in the peer account/region
aws ec2 describe-vpc-peering-connections --filters "Name=status-code,Values=active"

Creating the connection only proposes it — it stays pending-acceptance until the owner of the peer VPC runs accept-vpc-peering-connection. Peering also doesn't add routes automatically: you still need create-route in both VPCs' route tables pointing at the pcx- ID, same pattern as the internet gateway routing above.

Transit gateway#

aws ec2 create-transit-gateway --description "hub-vpc-tgw"
aws ec2 create-transit-gateway-vpc-attachment --transit-gateway-id tgw-0123456789abcdef0 --vpc-id vpc-0123456789abcdef0 --subnet-ids subnet-0123456789abcdef0
aws ec2 describe-transit-gateways --filters "Name=state,Values=available"

A transit gateway is the hub-and-spoke alternative to VPC peering's mesh — worth it once you're peering more than a handful of VPCs, since peering connections don't transit (VPC A peered to B and B peered to C does not let A reach C, but attaching A, B, and C to the same transit gateway does).

aws ec2 create-vpc-endpoint --vpc-id vpc-0123456789abcdef0 --service-name com.amazonaws.us-east-1.s3 --route-table-ids rtb-0123456789abcdef0
aws ec2 create-vpc-endpoint --vpc-id vpc-0123456789abcdef0 --vpc-endpoint-type Interface --service-name com.amazonaws.us-east-1.ec2 --subnet-ids subnet-0123456789abcdef0 --security-group-ids sg-0123456789abcdef0
aws ec2 describe-vpc-endpoints --filters "Name=vpc-id,Values=vpc-0123456789abcdef0"

--vpc-endpoint-type defaults to Gateway (only S3 and DynamoDB support it — attaches to a route table, no extra cost) versus Interface (an ENI-backed endpoint for most other services — billed hourly, needs subnets and a security group). Either way traffic to that AWS service stays on the AWS network instead of routing out through a NAT gateway or the internet.

Security group rules#

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

aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp --port 22 --source-group sg-0123456789abcdef1   # allow from another security group instead of a CIDR

Route 53 — hosted zones#

aws route53 list-hosted-zones
aws route53 create-hosted-zone --name example.com --caller-reference "$(date +%s)"

--caller-reference must be a unique string per call (it's an idempotency token, not a DNS field) — a timestamp or UUID both work; a fixed literal will fail on a second run with the same name.

Route 53 — records#

aws route53 list-resource-record-sets --hosted-zone-id Z1234567890ABC
aws route53 change-resource-record-sets --hosted-zone-id Z1234567890ABC --change-batch '{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "app.example.com",
      "Type": "A",
      "TTL": 300,
      "ResourceRecords": [{"Value": "203.0.113.10"}]
    }
  }]
}'

change-resource-record-sets is the only way to create/update/delete records — there's no create-record shortcut. UPSERT creates the record if it doesn't exist or overwrites it if it does; use CREATE/DELETE when you want the API to reject a call that doesn't match the record's current existence state.

Load balancers (ALB/NLB) — creation and listeners#

aws elbv2 create-load-balancer --name my-alb --subnets subnet-0123456789abcdef0 subnet-0123456789abcdef1 --security-groups sg-0123456789abcdef0 --type application
aws elbv2 describe-load-balancers --names my-alb
aws elbv2 create-target-group --name my-targets --protocol HTTP --port 80 --vpc-id vpc-0123456789abcdef0 --health-check-path /healthz
aws elbv2 create-listener --load-balancer-arn <lb-arn> --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn=<target-group-arn>

Target groups — registering and checking health#

aws elbv2 register-targets --target-group-arn <target-group-arn> --targets Id=i-0123456789abcdef0
aws elbv2 describe-target-health --target-group-arn <target-group-arn>

describe-target-health is the fastest way to confirm whether an ALB actually considers your instances/tasks healthy — a TargetHealth.State of unhealthy here, not application logs, is usually the first place to look when a load balancer is returning 502s.