How to Configure AWS VPC Default Route Table Routes in CloudFormation Templates


2 views

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