CloudFormation Stack Failures, Rollbacks and Drift
-
Phase 0: Before you start
-
Phase 1: The foundation
-
Phase 2: Operations and security
-
Phase 3: SQL Server on AWS
-
Phase 4: Troubleshooting step by step
-
Phase 5: Final project and career
Infrastructure as code is great until a stack is stuck and the deploy pipeline is blocked. Most CloudFormation rollback failed problems, and their cousins ROLLBACK_COMPLETE and drift, come down to two skills: finding the first real failure in the stack events, and knowing which stuck state needs which command.
You’ll deploy a small stack, preview a change, watch a create fail and roll back, then break a stack into UPDATE_ROLLBACK_FAILED the way it happens in real teams, by changing a resource outside CloudFormation. You’ll unblock it, then find and fix drift.
| Time | You need | By the end you can |
|---|---|---|
| 60 minutes | Lessons 34 and 56 done (CloudFormation basics, Get-RecentChanges.ps1), signed in as saat-admin. Cost: nothing that lasts; the stacks only create a bucket, a parameter, a security group and roles, all deleted at the end. | Read stack events to find the root failure, preview updates with change sets, recover from ROLLBACK_COMPLETE and UPDATE_ROLLBACK_FAILED, skip resources safely, and detect and fix drift. |
What it is, in one line
CloudFormation rollback failed means a failed stack update couldn’t be undone, usually because a resource was changed or deleted outside CloudFormation, leaving the stack in UPDATE_ROLLBACK_FAILED until you continue the rollback.
In Simple Words
Rearranging furniture with a list
You rearrange a room following a written plan, and keep a note of where everything was. If one heavy cupboard won’t move, you put everything back where it was. That’s a rollback.
But if someone threw away the old chair while you were working, you can’t put it back. You’re stuck halfway, with the room in neither state. That’s UPDATE_ROLLBACK_FAILED.
The way out is to say “forget the chair”, finish putting the rest back, then buy a new chair so the room matches your notes again.
Engineer's View
How stack failures work
When a resource fails, CloudFormation stops and rolls back by default. A failed create ends in ROLLBACK_COMPLETE: the stack can’t be updated, only deleted and created again (use --disable-rollback or --retain-except-on-create while debugging). The first CREATE_FAILED event in time order is the cause; later ones usually say “Resource creation cancelled”.
A failed update rolls back to the last good configuration. If the rollback itself fails, often because a resource was deleted or changed by hand, the stack enters UPDATE_ROLLBACK_FAILED. Fix the cause if you can, then run continue-update-rollback, optionally with --resources-to-skip; skipped resources are then out of step with the template until you fix them.
Pre-deployment validation and change sets catch many mistakes before anything changes.
Drift detection compares live resources with the template and lists property differences. Drift is how hand-made changes become future rollback failures, so find it and either revert it or put the change into the template.
How it works, step by step
Here is how to handle a broken stack.
1Read the status
ROLLBACK_COMPLETE, UPDATE_ROLLBACK_FAILED, DELETE_FAILED and drift each have a different way out.
2Find the first failure
Sort the stack events by time and take the first FAILED one that isn’t “cancelled”. That’s the cause; the rest are consequences.
3Unblock, then make reality match
Delete and recreate, continue the rollback, or retain a resource, then fix the drift between the template and what really exists.
4Know the stuck states
The common ones and their fixes:
Most rollback failures start with a “quick fix in the console”. Make the template the only way production changes.
✅ Checkpoint: You can name the way out of ROLLBACK_COMPLETE and UPDATE_ROLLBACK_FAILED, and explain how hand-made changes cause rollback failures.
Hands-on lab: fail, unstick and realign a stack
Sign in, save the template and helpers:
cd C:\aws-lab
aws login --profile saat-admin
$env:AWS_PROFILE = 'saat-admin'
$v = Get-Content C:\aws-lab\saat-vpc.json | ConvertFrom-Json
$tpl = @'
AWSTemplateFormatVersion: '2010-09-09'
Description: SaatVedha Lesson 67 stack
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
RoleDescription:
Type: String
Default: Lesson 67 app role
Resources:
LogBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Delete
AppParam:
Type: AWS::SSM::Parameter
Properties:
Name: /saat/cfn67/env
Type: String
Value: prod
AppSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: saat-cfn67-sg
GroupDescription: Lesson 67 app tier
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.20.0.0/16
AppRole:
Type: AWS::IAM::Role
Properties:
RoleName: saat-cfn67-role
Description: !Ref RoleDescription
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
Outputs:
BucketName:
Value: !Ref LogBucket
'@
$tpl | Set-Content cfn67.yaml
function Wait-Stack([string]$Stack) {
do { Start-Sleep 10; $s = aws cloudformation describe-stacks --stack-name $Stack --query "Stacks[0].StackStatus" --output text } until ($s -notmatch 'IN_PROGRESS$')
$s
}
function Get-StackFailure([string]$Stack) {
$ev = aws cloudformation describe-stack-events --stack-name $Stack --query "StackEvents[?ends_with(ResourceStatus, 'FAILED')].[Timestamp,LogicalResourceId,ResourceStatus,ResourceStatusReason]" --output json | ConvertFrom-Json
$ev | Sort-Object { $_[0] } | Where-Object { $_[3] -notmatch 'cancelled' } | Select-Object -First 1 | ForEach-Object { "$($_[1]) $($_[2]): $($_[3])" }
}
function Get-Drift([string]$Stack) {
$id = aws cloudformation detect-stack-drift --stack-name $Stack --query StackDriftDetectionId --output text
do { Start-Sleep 5; $st = aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id $id --query "[DetectionStatus,StackDriftStatus]" --output text } until ($st -notmatch 'DETECTION_IN_PROGRESS')
$st
aws cloudformation describe-stack-resource-drifts --stack-name $Stack --stack-resource-drift-status-filters MODIFIED DELETED --query "StackResourceDrifts[].[LogicalResourceId,StackResourceDriftStatus,PropertyDifferences[0].PropertyPath,PropertyDifferences[0].DifferenceType]" --output text
}
aws cloudformation validate-template --template-body file://cfn67.yaml --query "Parameters[].ParameterKey" --output textYou should see this:
VpcId RoleDescription1Deploy the stack
Paste this whole block:
aws cloudformation create-stack --stack-name saat-cfn67 --template-body file://cfn67.yaml --parameters "ParameterKey=VpcId,ParameterValue=$($v.VpcId)" --capabilities CAPABILITY_NAMED_IAM --tags Key=course,Value=saatvedha-aws | Out-Null
Wait-Stack saat-cfn67
aws cloudformation describe-stack-resources --stack-name saat-cfn67 --query "StackResources[].[LogicalResourceId,ResourceStatus]" --output textYou should see something like this:
CREATE_COMPLETE
AppParam CREATE_COMPLETE
AppRole CREATE_COMPLETE
AppSg CREATE_COMPLETE
LogBucket CREATE_COMPLETE2Preview a change with a change set
Before changing anything, see what CloudFormation would do:
aws cloudformation create-change-set --stack-name saat-cfn67 --change-set-name desc-v2 --use-previous-template --parameters "ParameterKey=VpcId,UsePreviousValue=true" "ParameterKey=RoleDescription,ParameterValue=Lesson 67 app role v2" --capabilities CAPABILITY_NAMED_IAM | Out-Null
aws cloudformation wait change-set-create-complete --stack-name saat-cfn67 --change-set-name desc-v2
aws cloudformation describe-change-set --stack-name saat-cfn67 --change-set-name desc-v2 --query "Changes[].ResourceChange.[LogicalResourceId,Action,Replacement]" --output text
aws cloudformation delete-change-set --stack-name saat-cfn67 --change-set-name desc-v2You should see this:
AppRole Modify FalseOnly the role changes, in place, with no replacement. Change sets are how you avoid surprises such as a database being replaced because one property changed. You deleted it for now; you’ll make this change later.

3Watch a create fail and roll back
Paste this whole block. The second template refers to a managed policy that doesn’t exist:
$bad = @'
AWSTemplateFormatVersion: '2010-09-09'
Description: SaatVedha Lesson 67 failing stack
Resources:
BadParam:
Type: AWS::SSM::Parameter
Properties:
Name: /saat/cfn67/bad
Type: String
Value: created-first
BadRole:
Type: AWS::IAM::Role
Properties:
RoleName: saat-cfn67-bad-role
ManagedPolicyArns:
- arn:aws:iam::aws:policy/DoesNotExist
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
'@
$bad | Set-Content cfn67-bad.yaml
aws cloudformation create-stack --stack-name saat-cfn67-bad --template-body file://cfn67-bad.yaml --capabilities CAPABILITY_NAMED_IAM | Out-Null
Wait-Stack saat-cfn67-bad
Get-StackFailure saat-cfn67-bad
aws ssm get-parameter --name /saat/cfn67/bad --query Parameter.Value --output text 2>&1 | Select-Object -First 1
aws cloudformation delete-stack --stack-name saat-cfn67-bad
aws cloudformation wait stack-delete-complete --stack-name saat-cfn67-badYou should see something like this:
ROLLBACK_COMPLETE
BadRole CREATE_FAILED: Policy arn:aws:iam::aws:policy/DoesNotExist does not exist or is not attachable. (Service: Iam, Status Code: 404, Request ID: ...)
An error occurred (ParameterNotFound) when calling the GetParameter operation:The parameter was created first, then removed by the rollback, so nothing was left half-built. A stack in ROLLBACK_COMPLETE can’t be updated: you fix the template, delete the stack and create it again.

✅ Checkpoint: saat-cfn67 is CREATE_COMPLETE, the change set showed AppRole Modify with no replacement, and saat-cfn67-bad failed on BadRole, rolled back cleanly and was deleted.
Break It, Then Fix It
Two problems that start with someone changing things outside CloudFormation.
1Break it: UPDATE_ROLLBACK_FAILED
Someone deletes the role by hand (“it looked unused”). Then the pipeline runs the description change you previewed:
aws iam delete-role --role-name saat-cfn67-role
aws cloudformation update-stack --stack-name saat-cfn67 --use-previous-template --parameters "ParameterKey=VpcId,UsePreviousValue=true" "ParameterKey=RoleDescription,ParameterValue=Lesson 67 app role v2" --capabilities CAPABILITY_NAMED_IAM | Out-Null
Wait-Stack saat-cfn67
Get-StackFailure saat-cfn67You’ll get something like this:
UPDATE_ROLLBACK_FAILED
AppRole UPDATE_FAILED: The role with name saat-cfn67-role cannot be found. (Service: Iam, Status Code: 404, Request ID: ...)The update failed because the role is gone, and the rollback failed for the same reason: CloudFormation can’t put back a role that no longer exists. The stack is now blocked; no updates can run until it’s unstuck. Fix: skip that resource to finish the rollback, then recreate the role so reality matches what CloudFormation remembers:
aws cloudformation continue-update-rollback --stack-name saat-cfn67 --resources-to-skip AppRole
Wait-Stack saat-cfn67
'{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }' | Set-Content role-trust.json
aws iam create-role --role-name saat-cfn67-role --description "Lesson 67 app role" --assume-role-policy-document file://role-trust.json --query Role.RoleName --output text
aws cloudformation update-stack --stack-name saat-cfn67 --use-previous-template --parameters "ParameterKey=VpcId,UsePreviousValue=true" "ParameterKey=RoleDescription,ParameterValue=Lesson 67 app role v2" --capabilities CAPABILITY_NAMED_IAM | Out-Null
Wait-Stack saat-cfn67You should see something like this:
UPDATE_ROLLBACK_COMPLETE
saat-cfn67-role
UPDATE_COMPLETEThe stack is healthy and the change you wanted went through. Skipping without recreating would have left a stack that fails on every future update to that role.
2Break it: drift from a console change
Someone opens SSH to the world on the stack’s security group “just for a minute”, then you check drift:
$sg = aws cloudformation describe-stack-resource --stack-name saat-cfn67 --logical-resource-id AppSg --query "StackResourceDetail.PhysicalResourceId" --output text
aws ec2 authorize-security-group-ingress --group-id $sg --protocol tcp --port 22 --cidr 0.0.0.0/0 | Out-Null
Get-Drift saat-cfn67You’ll get something like this:
DETECTION_COMPLETE DRIFTED
AppSg MODIFIED /SecurityGroupIngress/1 ADD
Drift detection found the rule that isn’t in the template. Left alone, the next stack update might remove it silently, or a later rollback might trip over it. Fix: revert it (or, if it’s really needed, add it to the template), then check again:
aws ec2 revoke-security-group-ingress --group-id $sg --protocol tcp --port 22 --cidr 0.0.0.0/0 | Out-Null
Get-Drift saat-cfn67You should see this:
DETECTION_COMPLETE IN_SYNCNow clean up:
aws cloudformation delete-stack --stack-name saat-cfn67
aws cloudformation wait stack-delete-complete --stack-name saat-cfn67
Remove-Item cfn67.yaml, cfn67-bad.yaml, role-trust.json✅ Checkpoint: You recovered from UPDATE_ROLLBACK_FAILED with continue-update-rollback plus a recreated role, and found and reverted drift. Both stacks are deleted.
This happened at work
The pipeline that was blocked for a day
A media company’s deploy pipeline failed one morning: the main network stack was in UPDATE_ROLLBACK_FAILED. Nobody had changed the template in weeks.
Stack events showed the rollback failing on a security group that “could not be found”. During an incident the night before, an engineer had deleted and recreated it by hand under the same name. CloudFormation still held the old ID, and every update tried to touch a group that no longer existed.
They skipped the resource in continue-update-rollback, imported the new group into the stack, and ran drift detection on every production stack. Console write access for networking was removed, and a nightly drift check now posts to the team channel.
Common mistakes and troubleshooting
| Mistake | What happens | What to do |
|---|---|---|
| Reading the last event instead of the first | Chasing “creation cancelled” messages | Sort by time, take the first FAILED |
| Trying to update a ROLLBACK_COMPLETE stack | Error: can’t update | Delete and create again |
| Changing stack resources by hand | Drift, then rollback failures | Change only through the template |
| Skipping resources and moving on | Stack and reality disagree forever | Recreate, import or remove them properly |
| Updating without a change set | Surprise replacements | Preview with create-change-set |
| Never checking drift | Hidden risks until an update | Scheduled detect-stack-drift with alerts |
Interview questions
Click a question to open it. Each one is written the way an interviewer would ask it. Under it you will see why they ask it and one way you could answer, in your own words.
Fresher level
1. You create a new stack and it ends in ROLLBACK_COMPLETE. What does that mean, and what can you do with it?
Why they ask this: They want to check that you know this state, because people often waste time trying to update a stack that can only be deleted.
How you might answer: It means the create failed and CloudFormation rolled everything back. A stack in this state can only be deleted, not updated.
So I find the reason in the events, fix the template, delete the stack and create it again.
2. A stack has failed. How do you find the real reason?
Why they ask this: They want to see that you can find the root cause in a long list of events, not just the last error on the screen.
How you might answer: I run describe-stack-events and sort by time. I look for the first FAILED event that isn’t “Resource creation cancelled”.
Those cancelled ones are just side effects; the first real failure is the cause.
3. How would you explain a change set to a new team member?
Why they ask this: They want to know you preview changes before applying them, especially changes that replace resources.
How you might answer: I’d say a change set is a preview of what an update will do, before you run it. It shows what will be added, changed or removed.
Most important, it shows replacements, so you don’t delete and recreate a database by accident.
2–5 years experience
4. It’s late at night and a production stack is stuck in UPDATE_ROLLBACK_FAILED. How do you get it working again?
Why they ask this: They want to see a calm recovery process, and that you know skipping resources leaves work to clean up afterwards.
How you might answer: First I find the failing resource in the events and fix the cause if I can. Then I run continue-update-rollback, using --resources-to-skip if a resource still can’t roll back.
After that, I make the skipped resources match the template again, so the stack and reality agree.
5. What is stack drift, and how would you find it?
Why they ask this: They want to see that you know manual changes break the link between code and reality, and how to detect them.
How you might answer: Drift is when the live resources no longer match the template, usually because someone changed them by hand.
I find it with detect-stack-drift, then describe-stack-resource-drifts to see exactly which properties changed.
6. Someone built a resource by hand, and now you want CloudFormation to manage it. How would you do that?
Why they ask this: They want to know you can bring existing resources into IaC without deleting and recreating them.
How you might answer: I’d use resource import. I add the resource to the template with a DeletionPolicy, then create a change set of type IMPORT.
Once that runs, the resource is part of the stack and changes go through the template from then on.
Senior level
7. Your teams keep getting stacks stuck in bad states. What guardrails would you put in place?
Why they ask this: They want to see how you prevent problems with process and controls, not just fix them one by one.
How you might answer: I’d run all changes through pipelines with change sets and approvals, and add stack policies on critical resources. Console changes to stack-managed resources would be denied using tags and SCPs.
I’d also schedule drift detection, turn on termination protection, and give the team runbooks for the stuck states.
8. A stack delete fails with DELETE_FAILED because the S3 bucket isn’t empty. How do you handle it?
Why they ask this: They want to see that you can finish the delete safely, and then fix the template so it doesn’t happen again.
How you might answer: I either empty the bucket, or keep it with --retain-resources, and then delete the stack again.
In the template I’d make a clear choice, using DeletionPolicy: Retain or lifecycle rules on purpose, so the next delete behaves as we expect.
Quick quiz
Q1. What can you do with a stack in ROLLBACK_COMPLETE?
- Update it
- Delete it and create again
- Nothing ever
- Import into it
Show answer
B — It can’t be updated.
Q2. Which event shows the real cause?
- The last one
- The first FAILED that isn’t “cancelled”
- Any COMPLETE
- The stack event
Show answer
B — Later failures are consequences.
Q3. What caused UPDATE_ROLLBACK_FAILED here?
- Bad YAML
- The role was deleted by hand
- No permissions
- Wrong Region
Show answer
B — Rollback couldn’t restore it.
Q4. Which command unblocks UPDATE_ROLLBACK_FAILED?
- update-stack
- continue-update-rollback
- delete-change-set
- validate-template
Show answer
B — Optionally with --resources-to-skip.
Q5. What found the extra SSH rule?
- CloudTrail
- Drift detection
- Change set
- Stack policy
Show answer
B — detect-stack-drift.
Quick recap
- Find the first FAILED event; the rest are consequences.
- ROLLBACK_COMPLETE: delete and create again.
- UPDATE_ROLLBACK_FAILED: fix or skip with continue-update-rollback, then realign.
- Preview updates with change sets; detect drift regularly.
- Change stack resources only through the template.
Next lesson
Lesson 68: Billing surprises: find what is costing money → You’ll trace unexpected charges to the service, resource and person behind them, and stop them.
Further reading: CloudFormation rollback failed
Want to go deeper into CloudFormation rollback failed? Read Troubleshooting CloudFormation and Detect unmanaged configuration changes with drift detection. You can also see the full plan for this course on the AWS Cloud Administration course page.
