The One-Line IaC Change That Could Delete Your Database
profile picture Krishna Muddi
11 min read Apr 6, 2026

The One-Line IaC Change That Could Delete Your Database

A guide to Fn::Cidr, subnet planning, and CIDR migration risks in AWS CloudFormation

Infrastructure-as-Code is powerful until a one-line networking change silently deletes your production database. Network configuration in AWS is one of those decisions that is easy to get wrong and very hard to fix later. Subnet CIDR blocks are immutable once created. You cannot resize them. You cannot shift them. The only way to change them is to delete and recreate, and that cascades to everything attached.

This guide covers two things: how to plan your CIDR blocks correctly from the start using CloudFormation's Fn::Cidr intrinsic function, and what to be aware of when changing them in an existing stack with live databases.

To make this practical, we will follow a real PR review scenario, in which a teammate replaced a hardcoded subnet CIDR with a dynamic Fn::Cidr expression. The intent was right, but it raised important questions: Does it produce the same IP range? And what would happen if this ran against a live Aurora cluster?

1. The Problem With Hardcoded CIDRs

The most common way to define a subnet CIDR in CloudFormation is to hardcode it:

    DatabaseSubnet:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref VPC
        CidrBlock: "172.32.0.0/26"   # hardcoded

This works fine for a single environment. But the moment you need to deploy the same template to staging, production, or a different region, every environment needs a different CIDR to avoid overlap, which means either duplicating templates or manually overriding parameters.

Hardcoded CIDRs also make it easy to introduce mistakes:

  • Two subnets accidentally assigned the same CIDR block
  • Subnet CIDRs that fall outside the VPC CIDR range
  • No room for future subnets because the VPC space was carved up too aggressively
  • Overlapping CIDRs when peering with another VPC or on-premises network

The fix is Fn::Cidr, a CloudFormation intrinsic function that dynamically generates subnet CIDRs from a parent VPC CIDR at deploy time.

2. Fn::Cidr - How It Actually Works

Fn::Cidr splits a parent CIDR block into a list of equally sized subnets. You pick one (or more) from that list using Fn::Select.

Syntax:

    # YAML full form
    Fn::Cidr:
      - ipBlock    # Parent VPC CIDR e.g. 172.32.0.0/24
      - count      # How many subnets to generate (1 - 256)
      - cidrBits   # Host bits per subnet (controls subnet size)

    # Short form
    !Cidr [ ipBlock, count, cidrBits ]

    # Picking the first subnet from the list
    CidrBlock: !Select [ 0, !Cidr [ !Ref VpcCidr, 4, 6 ] ]

Understanding cidrBits - The Most Misunderstood Parameter:

cidrBits is not the subnet prefix number. It is the number of host bits, the free bits at the end of the address. The formula is:

    subnet prefix = 32 - cidrBits

So, cidrBits: 6 means 6 host bits, giving a /26 subnet (32 - 6 = 26). Specifying a value of 8 for this parameter will create a CIDR with a mask of /24 (32 - 8 = 24).

cidrBits Formula Subnet Prefix Total IPs Usable IPs*
4 32 - 4 /28 16 11
5 32 - 5 /27 32 27
6 32 - 6 /26 64 59
7 32 - 7 /25 128 123
8 32 - 8 /24 256 251
12 32 - 12 /20 4096 4091

*AWS reserves the first 4 IP addresses and the last IP address in every subnet (Network address, VPC router, DNS, future use, and Network broadcast address). A /26 gives 64 - 5 = 59 usable IPs, not 62.

Worked Example: The PR Change

Given VpcCidr = 172.32.0.0/24 and the expression !Cidr ["172.32.0.0/24", 4, 6]:

  • cidrBits = 6 → subnet prefix = 32 - 6 = /26 → 64 IPs each
  • count = 4 → generates 4 subnets
  • 4 x 64 = 256 IPs total = exactly fills the /24 parent
Index CIDR IP Range Usable IPs
0 172.32.0.0/26 .0 - .63 59
1 172.32.0.64/26 .64 - .127 59
2 172.32.0.128/26 .128 - .191 59
3 172.32.0.192/26 .192 - .255 59

!Select [0, ...] picks index 0 –>172.32.0.0/26. The code is correct. The only issue in the PR was an imprecise comment using an X placeholder instead of the actual resolved value.

3. Planning CIDR for a New Stack

If you are starting fresh, getting the CIDR plan right upfront saves you from painful migrations later. Here is a reference structured approach.

Step 1: Size Your VPC First

Your VPC CIDR must be large enough to fit all subnets across all Availability Zones, plus room to grow. AWS supports VPC CIDRs from /16 (65,536 IPs) down to /28 (16 IPs).

Workload VPC CIDR Suggested Split Approx. Usable IPs
Dev / Sandbox /24 4 x /26 subnets ~236
Staging /22 8 x /25 subnets ~1,000
Production /20 16 x /24 subnets ~4,000
Enterprise /16 256 x /24 subnets ~65,000

Step 2: Leave Room for Growth

The AWS Well-Architected Reliability Pillar (REL02-BP03) explicitly warns against filling your entire VPC CIDR at deploy time. Subnet CIDRs cannot be changed after creation, only deleted and recreated, which triggers resource replacement and potential data loss.

  • Never carve your entire VPC CIDR into subnets at initial deploy
  • Leave at least 20-30% of the VPC address space unallocated
  • Plan for multi-AZ from the start: 3 AZs = at minimum 6 subnets (public + private per AZ)
  • EMR clusters, Spot Fleets, and Redshift clusters can consume IPs in bursts

Step 3: Use Fn::Cidr for Reusable Templates

With a parameter-driven VpcCidr and Fn::Cidr, the same template deploys correctly to any environment by changing one value:

    Parameters:
      VpcCidr:
        Type: String
        Default: "172.32.0.0/22"
        Description: "VPC CIDR block (must be unique per environment)"

    Resources:
      PublicSubnetA:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId: !Ref VPC
          CidrBlock: !Select [0, !Cidr [!Ref VpcCidr, 8, 6]] # /26
          AvailabilityZone: !Select [0, !GetAZs ""]

      PublicSubnetB:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId: !Ref VPC
          CidrBlock: !Select [1, !Cidr [!Ref VpcCidr, 8, 6]] # /26
          AvailabilityZone: !Select [1, !GetAZs ""]

      DatabaseSubnetA:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId: !Ref VPC
          CidrBlock: !Select [4, !Cidr [!Ref VpcCidr, 8, 6]] # /26
          AvailabilityZone: !Select [0, !GetAZs ""]

      DatabaseSubnetB:
        Type: AWS::EC2::Subnet
        Properties:
          VpcId: !Ref VPC
          CidrBlock: !Select [5, !Cidr [!Ref VpcCidr, 8, 6]] # /26
          AvailabilityZone: !Select [1, !GetAZs ""]

Tip: Use count: 8 even if you only need 4 subnets today. This reserves index slots consistently and prevents CIDR collisions when you add subnets later. Indices 0-3 for public, 4-7 for private/database is a clean convention.

4. Changing CIDRs in an Existing Stack (The Danger Zone)

Updating a subnet's CidrBlock in CloudFormation is not an in-place modification. It is a replacement operation. CloudFormation creates a new subnet and deletes the old one. Everything attached to that subnet is affected.

Replacement Cascade: What Gets Affected

Resource Impact
AWS::EC2::Subnet (CidrBlock) Subnet deleted and recreated
AWS::RDS::DBInstance DB deleted — automated snapshots also deleted
AWS::RDS::DBCluster (Aurora) Cluster deleted if subnet group changes
AWS::EC2::Instance Instance terminated, instance store data lost
AWS::EC2::NetworkInterface (ENI) Private IP changes, existing connections drop

Warning: If RDS or Aurora is replaced during a stack update, CloudFormation deletes ALL automated snapshots. Without a manual snapshot or UpdateReplacePolicy: Snapshot set on your DBCluster, your data is permanently lost.

Always Run a Change Set First

Before applying any networking change to an existing stack, run a CloudFormation Change Set to preview exactly which resources will be replaced. Below is an example using the AWS SAM commands.

    # Build and deploy with --no-execute-changeset to preview changes only
    sam build && sam deploy \
      --stack-name my-vpc-stack \
      --profile my-aws-profile \
      --no-execute-changeset \
      --region us-east-1 \
      --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
      --parameter-overrides VpcCidr=172.32.0.0/24

    # Review the change set in AWS Console or via CLI
    aws cloudformation describe-change-set \
      --stack-name my-vpc-stack \
      --change-set-name <change-set-name-from-sam-output>

In the output, look for any resource showing "Replacement": "True". If your DBCluster or DBInstance appears in that list, stop and do not apply the change set until you have added the following protection attributes to your template.

    DatabaseCluster:
      Type: AWS::RDS::DBCluster
      DeletionPolicy: Snapshot        # Snapshot before stack delete or        resource removal
      UpdateReplacePolicy: Snapshot   # Critical: snapshot before replacement during stack update
      Properties:
        DeletionProtection: true      # Hard stop at DB level — blocks deletion regardless of CF
        Engine: aurora-postgresql
        DBSubnetGroupName: !Ref DBSubnetGroup
        ...
    ```

DeletionPolicy and UpdateReplacePolicy are resource-level attributes, not Properties. They sit at the same indentation level as Type and Properties. DeletionProtection is the only one that lives inside Properties.

DeletionProtection: true is enforced at the RDS API level, not the CloudFormation level. When CloudFormation attempts to delete the cluster for any reason, the RDS API rejects the call outright, and CloudFormation rolls back with an error; the database is never touched. Unlike DeletionPolicy and UpdateReplacePolicy, which control what happens before a delete, DeletionProtection prevents the delete from completing entirely. For production clusters, this should be enabled for all database resources without exception.

5. Edge Cases and Gotchas

Parent CIDR Too Small for the Requested Subnets

If the parent VPC CIDR does not have enough space for the requested subnets, CloudFormation will error at deploy time. Always validate the math before deploying:

    # Parent: 172.32.0.0/28 = 16 IPs total
    # cidrBits: 6 = /26 = 64 IPs per subnet
    # count: 4 = 4 x 64 = 256 IPs required
    # 256 > 16  →  CloudFormation will error at deploy time

    # Rule: count x (2 ^ cidrBits) <= parent IP count

Hardcoded to Dynamic: Verify the Resolved Value Matches

When refactoring from a hardcoded CIDR to a dynamic Fn::Cidr expression, CloudFormation will not trigger a replacement only if the resolved value is identical. Verify the output locally before deploying to a live stack:

    # Hardcoded original
    CidrBlock: "172.32.0.0/26"

    # Dynamic replacement — must resolve to the same value
    CidrBlock: !Select [0, !Cidr ["172.32.0.0/24", 4, 6]]

    # Verify locally with Python or check the changeset before deployment
    python3 -c "
    import ipaddress
    subnets = list(ipaddress.IPv4Network('172.32.0.0/24').subnets(new_prefix=26))
    for i, s in enumerate(subnets): print(i, s)
    "
    # 0 172.32.0.0/26  ← matches the hardcoded value, safe to deploy

Wrong or Misleading Comments in IaC

The PR that started this discussion had a comment # 172.32.X.0/26 with an ambiguous X placeholder. In IaC, imprecise comments mislead future engineers who may not trace through the function to verify the resolved value.

    # Ambiguous — what is X?
    CidrBlock: !Select [0, !Cidr [!Ref VpcCidr, 4, 6]] # 172.32.X.0/26

    # Clear — states the actual resolved value for the default VpcCidr
    CidrBlock: !Select [0, !Cidr [!Ref VpcCidr, 4, 6]] # 172.32.0.0/26 (when VpcCidr=172.32.0.0/24)

Conclusion

Network planning is one of the most consequential infrastructure decisions you make in AWS. Get it right upfront, and it is invisible; everything just works. Get it wrong, and you are looking at manual subnet migrations, Aurora failovers, and potential data loss to fix it.

The combination of Fn::Cidr and parameter-driven VPC CIDRs gives you reusable, consistent, environment-agnostic CloudFormation templates. Pair that with proper growth planning, and you avoid the most common traps. Key takeaways:

  • cidrBits = host bits. Formula: 32 - cidrBits = subnet prefix. Not an addition to the parent prefix.
  • Always account for 5 reserved IPs per subnet (first 4 + last 1) when planning capacity.
  • Leave unused CIDR space in your VPC — subnet IPv4 CIDRs cannot be changed after creation.
  • Use count: 8, even when you only need 4 subnets today — reserve index slots for future growth.
  • Snapshot and UpdateReplacePolicy: Snapshot to DBCluster/DBInstance before any structural networking change. These are resource attributes, not Properties.
  • Verify AI-generated IaC against official AWS documentation. The cidrBits parameter is frequently misrepresented.

References

Application Modernization Icon

Innovate faster, and go farther with serverless-native application development. Explore limitless possibilities with AntStack's serverless solutions. Empowering your business to achieve your most audacious goals.

Talk to us

Your Digital Journey deserves a great story.

Build one with us.

Cookies Icon

These cookies are used to collect information about how you interact with this website and allow us to remember you. We use this information to improve and customize your browsing experience, as well as for analytics.

If you decline, your information won’t be tracked when you visit this website. A single cookie will be used in your browser to remember your preference.