Introduction to CloudFormation Troubleshooting
AWS CloudFormation is a powerful Infrastructure as Code (IaC) service that allows developers to provision and manage AWS resources declaratively using templates written in JSON or YAML. While CloudFormation automates infrastructure deployment, it is not immune to failures. Stacks can fail for a variety of reasons — from syntax errors and IAM permission issues to resource conflicts and dependency problems. Understanding how to diagnose and resolve these issues is an essential skill for any cloud engineer.
This tutorial walks through the most common CloudFormation issues, explains why they occur, and provides practical solutions with code examples. By the end, you will have a structured approach to debugging CloudFormation deployments and a set of best practices to prevent issues before they happen.
Why Troubleshooting Matters
When a CloudFormation stack fails, it does not just stop — it often rolls back, deleting any resources it had already created. This can waste time, obscure the root cause, and leave your environment in an inconsistent state. Effective troubleshooting minimizes downtime, reduces infrastructure costs, and builds confidence in your deployment pipeline. In production environments, a stuck or failed stack can block application releases, so rapid diagnosis is critical.
Understanding CloudFormation Failure Modes
CloudFormation failures generally fall into several categories:
- Template validation errors — syntax problems, invalid property values, or unsupported resource types.
- Resource creation failures — the underlying AWS API rejects the request due to limits, naming conflicts, or misconfiguration.
- Permission errors — the IAM role or user executing the stack lacks required permissions.
- Dependency and ordering issues — resources reference each other incorrectly or depend on values not yet available.
- Update and rollback failures — updates cannot be applied, or rollbacks get stuck due to resource state.
Each category requires a different diagnostic approach, which we will explore below.
Issue 1: Template Syntax and Validation Errors
The most basic failures occur before any resources are created. These are caught during template validation and are usually the easiest to fix. Common causes include malformed YAML/JSON, incorrect indentation, referencing parameters that do not exist, or using unsupported intrinsic functions.
Detecting Syntax Errors
Always validate your template locally before deploying. The AWS CLI provides a validate-template command that checks structural correctness:
aws cloudformation validate-template \
--template-body file://template.yaml
For YAML templates, indentation is a frequent source of errors. Consider this broken example:
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-example-bucket
The Type property is not indented under MyBucket, which causes a parse failure. The corrected version:
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-example-bucket
Using cfn-lint for Deeper Validation
The cfn-lint tool goes beyond basic syntax checking and validates resource properties against the AWS CloudFormation Resource Specification. Install it with pip:
pip install cfn-lint
cfn-lint template.yaml
This catches issues like invalid property names, missing required properties, and incorrect value types — all before you ever attempt a deployment.
Issue 2: IAM Permission Denied Errors
CloudFormation acts on behalf of the IAM user or role that initiates the stack. If that identity lacks permission to create a specific resource, the stack will fail with an AccessDenied or Unauthorized error. This is one of the most common causes of failed deployments in locked-down enterprise environments.
Diagnosing Permission Issues
Check the stack events to find the specific error. Use the following command:
aws cloudformation describe-stack-events \
--stack-name my-stack \
--query 'StackEvents[?ResourceStatus==`CREATE_FAILED`]' \
--output table
A typical permission error event looks like this:
{
"ResourceStatus": "CREATE_FAILED",
"ResourceStatusReason": "API: ec2:RunInstances User: arn:aws:iam::123456789012:user/deployer is not authorized to perform: ec2:RunInstances"
}
Resolving Permission Issues
The solution is to grant the deploying identity the necessary permissions. Best practice is to use a dedicated deployment role with a least-privilege policy. Here is an example of passing a service role when creating a stack:
aws cloudformation create-stack \
--stack-name my-stack \
--template-body file://template.yaml \
--capabilities CAPABILITY_NAMED_IAM \
--role-arn arn:aws:iam::123456789012:role/CloudFormationDeployRole
If your template creates IAM resources (roles, policies, users), you must include the CAPABILITY_IAM or CAPABILITY_NAMED_IAM capability flag. Without it, CloudFormation refuses to proceed.
Issue 3: Resource Already Exists Conflicts
Many AWS resources require globally or regionally unique names. S3 bucket names must be globally unique, DynamoDB table names must be unique within a region, and IAM role names must be unique within an account. If you try to create a resource with a name that already exists, the stack will fail.
Example: S3 Bucket Name Conflict
{
"ResourceStatus": "CREATE_FAILED",
"ResourceStatusReason": "my-example-bucket already exists in stack arn:aws:cloudformation:us-east-1:123456789012:stack/other-stack/..."
}
Solutions
There are several strategies to avoid naming conflicts:
- Let CloudFormation generate names by omitting the
BucketNameproperty and usingReforFn::GetAttto retrieve the generated name. - Append a unique suffix such as a stack name or a short hash.
- Use
DeletionPolicy: Retainto keep resources when a stack is deleted, then import them into a new stack if needed.
Here is an example that appends the stack name to ensure uniqueness:
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "my-app-bucket-${AWS::StackName}"
Issue 4: Circular Dependencies
Circular dependencies occur when two resources reference each other in a way that CloudFormation cannot resolve an order for creation. For example, if Resource A depends on an attribute of Resource B, and Resource B depends on an attribute of Resource A, CloudFormation cannot determine which to create first.
Identifying Circular Dependencies
CloudFormation reports this during stack creation with a message like:
Template error: Circular dependency encountered: [ResourceA, ResourceB]
Resolving Circular Dependencies
The most common fix is to break the cycle using Fn::GetAtt with a DependsOn override, or by restructuring your template. A frequent pattern involves security groups that reference each other. Consider this problematic configuration:
Resources:
FrontendSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Frontend SG
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref BackendSG
BackendSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Backend SG
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref FrontendSG
This creates a circular dependency. The solution is to create both security groups first without the cross-references, then add the ingress rules as separate AWS::EC2::SecurityGroupIngress resources:
Resources:
FrontendSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Frontend SG
BackendSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Backend SG
FrontendToBackend:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref BackendSG
IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref FrontendSG
BackendToFrontend:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref FrontendSG
IpProtocol: tcp
FromPort: 443
ToPort: 443
SourceSecurityGroupId: !Ref BackendSG
This breaks the cycle because the ingress resources depend on the security groups, but the security groups no longer depend on each other.
Issue 5: Stack Stuck in ROLLBACK_IN_PROGRESS
When a resource creation fails, CloudFormation begins rolling back all previously created resources in the stack. If a resource cannot be deleted during rollback — for example, an S3 bucket that contains objects — the stack becomes stuck in ROLLBACK_IN_PROGRESS and eventually ROLLBACK_FAILED.
Common Cause: Non-Empty S3 Bucket
S3 buckets cannot be deleted if they contain objects. If your template creates a bucket and the stack fails after the bucket is created, the rollback will fail when trying to delete it.
Solution
First, manually empty the bucket:
aws s3 rm s3://my-example-bucket --recursive
Then continue the rollback:
aws cloudformation continue-update-rollback \
--stack-name my-stack
To prevent this in the future, add a custom resource or use the DeletionPolicy: Retain attribute, or implement a Lambda-backed custom resource that empties the bucket before deletion. Here is a simple approach using DeletionPolicy:
Resources:
MyBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName: my-example-bucket
With Retain, the bucket persists after stack deletion, preventing the rollback from getting stuck. You can clean it up manually later.
Issue 6: Update Failures and Resource Replacement
Some property changes require CloudFormation to replace a resource entirely rather than update it in place. For example, changing the DBInstanceClass of an RDS instance or the BucketName of an S3 bucket triggers a replacement. If the replacement fails, the stack can end up in an inconsistent state.
Detecting Replacements Before Applying
Always use a changeset to preview what will happen before applying an update:
aws cloudformation create-change-set \
--stack-name my-stack \
--change-set-name my-change-set \
--change-set-type UPDATE \
--template-body file://updated-template.yaml
aws cloudformation describe-change-set \
--stack-name my-stack \
--change-set-name my-change-set
The response includes an Action field for each resource. A value of Replace means the resource will be deleted and recreated. Review these carefully before executing the changeset:
aws cloudformation execute-change-set \
--stack-name my-stack \
--change-set-name my-change-set
Handling Replacement Failures
If a replacement fails, CloudFormation attempts to roll back to the previous state. If the rollback also fails, you may need to manually intervene. Use continue-update-rollback with the --resources-to-skip parameter to skip a problematic resource:
aws cloudformation continue-update-rollback \
--stack-name my-stack \
--resources-to-skip MyDBInstance
This tells CloudFormation to skip the failed resource and continue rolling back the rest. You will need to manually reconcile the skipped resource afterward.
Issue 7: Custom Resource Timeouts and Failures
Custom resources backed by Lambda functions are powerful but introduce additional failure modes. If the Lambda function does not respond within 60 minutes, or if it sends a response to the wrong S3 pre-signed URL, the custom resource will time out and the stack will fail.
Common Custom Resource Mistakes
- Forgetting to send a response to the pre-signed S3 URL provided in the event.
- Using an incorrect
Statusvalue (must beSUCCESSorFAILED). - Not including the
PhysicalResourceIdin the response. - Lambda function timing out before completing its work.
Correct Custom Resource Response Pattern
Here is a minimal Lambda function that correctly handles a custom resource request:
import json
import urllib3
http = urllib3.PoolManager()
def lambda_handler(event, context):
response_url = event['ResponseURL']
response_body = {
'Status': 'SUCCESS',
'PhysicalResourceId': event.get('PhysicalResourceId', event['LogicalResourceId']),
'StackId': event['StackId'],
'RequestId': event['RequestId'],
'LogicalResourceId': event['LogicalResourceId'],
'Data': {
'Result': 'Custom resource created successfully'
}
}
try:
# Perform your custom logic here
if event['RequestType'] == 'Delete':
# Handle cleanup
pass
encoded_body = json.dumps(response_body).encode('utf-8')
http.request('PUT', response_url, body=encoded_body)
except Exception as e:
response_body['Status'] = 'FAILED'
response_body['Reason'] = str(e)
encoded_body = json.dumps(response_body).encode('utf-8')
http.request('PUT', response_url, body=encoded_body)
return {'StatusCode': 200}
The critical part is that the function always sends a response to the ResponseURL, even when it fails. Without this response, CloudFormation will wait until timeout.
Issue 8: Wait Condition and CreationPolicy Timeouts
When bootstrapping EC2 instances with user data or when using Auto Scaling groups, CloudFormation often uses CreationPolicy or WaitCondition resources to signal that an instance is ready. If the signal never arrives, the stack times out and fails.
Example CreationPolicy
Resources:
MyInstance:
Type: AWS::EC2::Instance
CreationPolicy:
ResourceSignal:
Timeout: PT15M
Count: 1
Properties:
ImageId: ami-0abcdef1234567890
InstanceType: t2.micro
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum install -y aws-cli
/opt/aws/bin/cfn-signal -e 0 \
--stack ${AWS::StackName} \
--resource MyInstance \
--region ${AWS::Region}
Troubleshooting Signal Failures
If the stack fails with Resource signal not found, check the following:
- Verify the EC2 instance launched successfully by checking its system log in the AWS console.
- Ensure the
cfn-signalhelper script is installed on the instance. - Confirm the IAM instance profile has
cloudformation:SignalResourcepermission. - Check that the user data script executed without errors.
- Verify the
--resourcename matches the logical resource ID exactly.
To retrieve the instance system log:
aws ec2 get-console-output --instance-id i-0abcdef1234567890
Issue 9: Nested Stack Failures
Nested stacks allow you to break large templates into smaller, reusable components. However, when a nested stack fails, the error messages in the parent stack are often vague, showing only that the nested stack failed without details.
Diagnosing Nested Stack Failures
You need to drill into the nested stack's events. First, find the nested stack's physical ID:
aws cloudformation describe-stack-resources \
--stack-name parent-stack \
--logical-resource-id MyNestedStack
The PhysicalResourceId will be the nested stack's name. Then query that stack's events:
aws cloudformation describe-stack-events \
--stack-name nested-stack-name \
--query 'StackEvents[?ResourceStatus==`CREATE_FAILED`]' \
--output table
Best Practice for Nested Stacks
Always test nested stacks independently before integrating them into a parent stack. Use aws cloudformation package and deploy commands to manage nested stack templates stored in S3, ensuring the parent always references the correct version:
Resources:
MyNestedStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://my-bucket.s3.amazonaws.com/nested-template.yaml
Parameters:
Environment: production
VpcId: !Ref VpcId
Issue 10: Drift Detection and Configuration Mismatches
Drift occurs when resources are modified outside of CloudFormation — for example, through the AWS console or CLI. Drifted resources can cause stack updates to fail because CloudFormation's expected state no longer matches reality.
Running Drift Detection
aws cloudformation detect-stack-drift \
--stack-name my-stack
This returns a StackDriftDetectionId. Check the status:
aws cloudformation describe-stack-drift-detection-status \
--stack-drift-detection-id abc12345-6789-def0-1234-567890abcdef
Once detection completes, view the drift details:
aws cloudformation describe-stack-resource-drifts \
--stack-name my-stack \
--stack-resource-drift-status-filters MODIFIED
Resolving Drift
You have two options:
- Import the changes — update your template to match the current actual state of the resource, then run an update with no changes to reconcile.
- Revert the changes — manually change the resource back to match the template, then run drift detection again to confirm it is clean.
Best Practices for Preventing CloudFormation Issues
1. Validate Early and Often
Integrate cfn-lint and validate-template into your CI/CD pipeline. Catch errors before they reach AWS. A simple pipeline step:
steps:
- name: Lint CloudFormation
run: |
pip install cfn-lint
cfn-lint templates/*.yaml
2. Use ChangeSets for All Updates
Never apply updates blindly. Always create and review a changeset first. This gives you visibility into replacements, deletions, and additions before they happen.
3. Implement Deletion Policies Thoughtfully
Use DeletionPolicy: Retain for critical resources like databases and S3 buckets with important data. Use UpdateReplacePolicy: Retain to prevent accidental data loss during replacements.
Resources:
MyDatabase:
Type: AWS::RDS::DBInstance
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t3.micro
Engine: mysql
MasterUsername: admin
MasterUserPassword: !Ref DBPassword
4. Use Outputs for Cross-Stack References
Instead of hardcoding values or creating tight coupling between stacks, use exports and imports:
# Network stack
Outputs:
VpcId:
Value: !Ref MyVpc
Export:
Name: my-app-vpc-id
# Application stack
Resources:
MyInstance:
Type: AWS::EC2::Instance
Properties:
SubnetId: !ImportValue my-app-vpc-id
5. Enable Termination Protection on Production Stacks
aws cloudformation update-termination-protection \
--stack-name production-stack \
--enable-termination-protection
This prevents accidental deletion of critical infrastructure.
6. Tag Everything
Tags help with cost allocation, access control, and resource identification during troubleshooting:
Resources:
MyBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: my-example-bucket
Tags:
- Key: Environment
Value: Production
- Key: Project
Value: MyApp
- Key: ManagedBy
Value: CloudFormation
7. Use CloudFormation Drift Detection Periodically
Schedule regular drift detection checks, especially in environments where multiple teams have AWS access. Consider automating this with EventBridge and Lambda.
Debugging Workflow Summary
When a CloudFormation stack fails, follow this structured debugging workflow:
- Step 1: Check the stack status with
describe-stacks. - Step 2: Review stack events with
describe-stack-events, filtering forFAILEDstatuses. - Step 3: Read the
ResourceStatusReasonfield for the specific error message. - Step 4: Identify the failing resource type and look up common causes for that resource.
- Step 5: Fix the template or the underlying resource issue.
- Step 6: If the stack is stuck in rollback, use
continue-update-rollbackwith--resources-to-skipif needed. - Step 7: Redeploy and monitor the events in real time with
--followor the AWS console.
Here is a handy command to watch stack events in real time:
aws cloudformation describe-stack-events \
--stack-name my-stack \
--query 'StackEvents[*].[Timestamp,LogicalResourceId,ResourceStatus,ResourceStatusReason]' \
--output table
Conclusion
Troubleshooting CloudFormation is a systematic process that becomes easier with experience. The key is to understand that CloudFormation failures always leave a trail — in stack events, in resource status reasons, and in the underlying AWS service logs. By validating templates early, using changesets, implementing proper deletion policies, and following a structured debugging workflow, you can resolve most issues quickly and prevent many from occurring in the first place. Remember that every failed deployment is an opportunity to improve your templates and your deployment pipeline. Invest in tooling like cfn-lint, automate drift detection, and always test in a non-production environment before applying changes to production stacks. With these practices in place, CloudFormation becomes a reliable and predictable foundation for managing your AWS infrastructure at scale.