When working with AWS CloudFormation, many engineers encounter a puzzling limitation - the VPC's default route table exists automatically when you create a VPC, but CloudFormation doesn't provide direct access to modify it through standard resources. This becomes problematic when you need to programmatically add routes to this fundamental networking component.
The AWS::EC2::RouteTable resource creates new route tables, while the default route table that comes with every VPC is managed differently in the AWS API. CloudFormation currently doesn't expose a direct way to reference this implicit resource.
Method 1: Using Custom Resources with AWS Lambda
Here's a complete CloudFormation template snippet that demonstrates how to modify the default route table using a custom Lambda function:
Resources:
ModifyDefaultRouteTable:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt RouteTableModifierLambda.Arn
VpcId: !Ref MyVPC
RouteConfigurations:
- DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
- DestinationCidrBlock: "192.168.1.0/24"
InstanceId: !Ref NATInstance
RouteTableModifierLambda:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import boto3
def handler(event, context):
ec2 = boto3.client('ec2')
# Implementation logic here
Method 2: AWS CLI in UserData
For simpler cases, you can modify the default route table through EC2 instance UserData:
"UserData": {
"Fn::Base64": {
"Fn::Join": ["", [
"#!/bin/bash\n",
"AWS_REGION=", {"Ref": "AWS::Region"}, "\n",
"VPC_ID=", {"Ref": "MyVPC"}, "\n",
"DEFAULT_RT=$(aws ec2 describe-route-tables --filters Name=vpc-id,Values=${VPC_ID} Name=association.main,Values=true --query 'RouteTables[0].RouteTableId' --output text --region ${AWS_REGION})\n",
"aws ec2 create-route --route-table-id ${DEFAULT_RT} --destination-cidr-block 10.0.0.0/16 --instance-id i-1234567890abcdef0 --region ${AWS_REGION}\n"
]]
}
}
When implementing these solutions, remember:
- The default route table's ID isn't predictable - you must query it
- Changes to the default route table affect all subnets that don't have explicit route table associations
- Custom resources add complexity to your stack's error handling
Instead of modifying the default route table, consider creating explicit route tables for all subnets. This approach provides better visibility and control in CloudFormation:
Resources:
CustomRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MyVPC
DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref CustomRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref CustomRouteTable
When working with AWS VPCs, many developers encounter a frustrating limitation: CloudFormation doesn't provide direct access to modify the default route table that's automatically created with each new VPC. This becomes problematic when you need to add custom routes (like VPN connections or peering routes) to your VPC's main routing table during infrastructure deployment.
For teams implementing Infrastructure-as-Code (IaC) practices, this gap creates inconsistencies between environments. While you can easily modify custom route tables in CloudFormation, the default route table requires either manual intervention or workarounds, breaking the automation principle.
Here's how to properly reference and modify the default route table:
Resources:
DefaultRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MyVPC
Tags:
- Key: Name
Value: DefaultRouteTable
AddDefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref DefaultRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
Some teams prefer creating their own "default" route table explicitly:
Resources:
CustomDefaultRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MyVPC
Tags:
- Key: DefaultRouteTable
Value: "true"
MainSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref CustomDefaultRouteTable
1. The default route table doesn't appear in CloudFormation stack resources
2. Changes to the actual default route table won't be tracked in CloudFormation
3. For production environments, consider creating explicit route tables instead
4. Remember that route table modifications may cause brief network interruptions
For most production scenarios, we recommend:
- Creating explicit route tables for all routing needs
- Associating subnets with these explicit tables
- Treating the actual default route table as read-only
- Implementing this pattern consistently across all environments