I was reviewing CloudFormation drift reports to catch manual changes when I noticed hundreds of resources flagged as modified—but I hadn’t touched them. Tags were added automatically by AWS services, S3 bucket policies changed due to Block Public Access settings, and Auto Scaling groups had modified instance counts. CloudFormation was reporting expected operational changes as drift. In this post, I’ll walk through exactly what causes this and how to fix it.
The Problem
CloudFormation drift detection reports resources as MODIFIED even though no manual changes were made. Running drift detection produces noisy results, making it impossible to find actual drift.
CloudFormation drift shows:
Resource has drifted. Expected [Expected Value], Actual [Actual Value]
Examples of false positive drift:
| Resource Type | False Positive | Root Cause |
|---|---|---|
| S3 Bucket | Bucket policy changed | Block Public Access was toggled, automatically updated the policy |
| Auto Scaling Group | Instance count differs | Scaling action added/removed instances per scaling policy |
| RDS Cluster | Tags added/removed | AWS services automatically added aws:rds: tags |
| VPC Flow Logs | Field values differ | CloudFormation didn’t set certain optional fields; AWS filled defaults |
Why Does This Happen?
CloudFormation drift detection compares the expected template state against the actual live resource state. Divergences are reported as drift. However, AWS services often modify resources during normal operation without CloudFormation’s involvement.
- AWS automatically adds tags — CloudFormation tags your resources with stack metadata. AWS services add operational tags (
aws:cloudformation:stack-name,aws:rds:db-instance-class). These appear as drift even though they’re expected. - Computed/default properties — CloudFormation didn’t explicitly set certain properties in the template, but AWS auto-populated them with defaults. When drift compares, the defaults show as “actual” values.
- Auto Scaling operations — Auto Scaling Group desired capacity changes due to scaling policies. Drift detects the changed instance count.
- Service-linked role policies auto-updated — AWS updates service-linked role policies automatically. The new inline policy version shows as drift.
- S3 Block Public Access — Toggling Block Public Access modifies the S3 bucket policy automatically, triggering drift.
- RDS/Aurora cluster operations — Database operations (failover, snapshot restoration) modify resource state that CloudFormation didn’t define.
The Fix
Option 1: Ignore Known False Positives
Run drift detection and filter by resource type or logical ID to focus on meaningful drift:
# Detect drift
aws cloudformation detect-stack-drift \
--stack-name my-stack
# View drifted resources
aws cloudformation describe-stack-resource-drifts \
--stack-name my-stack \
--stack-resource-drift-status-filters MODIFIED \
--query "StackResourceDrifts[].{Type:ResourceType,LogicalId:LogicalResourceId,Expected:PropertyDifferences[0].ExpectedValue,Actual:PropertyDifferences[0].ActualValue}"
# Filter by resource type to see only certain resources
aws cloudformation describe-stack-resource-drifts \
--stack-name my-stack \
--query "StackResourceDrifts[?ResourceType=='AWS::EC2::SecurityGroup' && DriftStatus=='MODIFIED']" \
--output table
Option 2: Update Stack to Incorporate Actual Values (Synchronize)
If the actual state is acceptable, update the stack to match it. Import the resource with its current state:
# Create a change set for import
aws cloudformation create-change-set \
--change-set-name import-changeset \
--stack-name my-stack \
--change-set-type IMPORT \
--resources-to-import file://resources-to-import.json \
--template-body file://updated-template.yaml
# View the change set
aws cloudformation describe-change-set \
--change-set-name import-changeset \
--stack-name my-stack
# Execute the change set to synchronize
aws cloudformation execute-change-set \
--change-set-name import-changeset \
--stack-name my-stack
Format for resources-to-import.json:
[
{
"LogicalResourceId": "MySecurityGroup",
"PhysicalResourceId": "sg-12345678"
}
]
Option 3: Update Template to Accept Expected Values
For resources like Auto Scaling Groups where drift is expected, explicitly set drift-acceptable values in the template:
Resources:
MyAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref MyLaunchTemplate
Version: !GetAtt MyLaunchTemplate.LatestVersionNumber
MinSize: '1'
MaxSize: '10'
DesiredCapacity: '5' # This will drift as scaling policies modify it
VPCZoneIdentifier:
- subnet-12345678
For this resource, drift from scaling is expected. Document this in comments:
# Note: DesiredCapacity will drift as Auto Scaling policies adjust instance count
# This is expected behavior. Check drift manually to catch policy misconfigurations.
Option 4: Use Change Sets Instead of Drift for Audit
Instead of relying on drift detection, use CloudFormation Change Sets to audit proposed changes before applying them:
# Create change set to see what will change
aws cloudformation create-change-set \
--change-set-name audit-changes \
--stack-name my-stack \
--change-set-type UPDATE \
--template-body file://updated-template.yaml
# Review changes without applying
aws cloudformation describe-change-set \
--change-set-name audit-changes \
--stack-name my-stack \
--query "Changes[].[Type,ResourceChange.Action,ResourceChange.LogicalResourceId]" \
--output table
# Delete if you don't want to apply
aws cloudformation delete-change-set \
--change-set-name audit-changes \
--stack-name my-stack
How to Run This
- Run drift detection:
aws cloudformation detect-stack-drift --stack-name my-stack - Analyze drifted resources:
aws cloudformation describe-stack-resource-drifts --stack-name my-stack --stack-resource-drift-status-filters MODIFIED - Categorize drift: identify which is expected (scaling, tags) vs actual drift (manual changes)
- For actual drift, fix the resource or update the stack
- For expected drift, document it and plan manual review cadence
Is This Safe?
Yes. Drift detection is read-only. You’re just viewing state; no changes are applied unless you explicitly use a change set or update the stack.
Key Takeaway
CloudFormation drift detection creates false positives from auto-scaling, service-managed tags, and AWS auto-updates. Focus drift detection on specific resource types, document expected drift, and use change sets for audit trails instead of relying solely on drift reports.
Have questions or ran into a different CloudFormation issue? Connect with me on LinkedIn or X.