← Back to DevBytes

Troubleshooting CloudFormation: Common Issues and Solutions

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:

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:

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

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:

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:

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:

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.

— Ad —

Google AdSense will appear here after approval

← Back to all articles