Symptoms
- A deployment pipeline fails with
Stack is in UPDATE_ROLLBACK_FAILED state and can not be updated. cdk deployorsam deployhangs on one resource for an hour, then the stack rolls back and fails.delete-stackends inDELETE_FAILEDand the stack can't be removed.
All three come from the same thing: CloudFormation could not create, update or delete a resource, and it then failed to undo that change. Until you fix the stack, nobody can deploy to it.
Caution
Don't delete a production stack to "start clean". Deleting the stack also deletes every resource it manages that has no
DeletionPolicy: Retain, including databases, buckets and queues. Fix the rollback instead.
1. Find the resource that failed
The first *_FAILED event in time order is usually the real cause. Events after it are side effects.
STACK=prod-app
aws cloudformation describe-stacks --stack-name $STACK \
--query "Stacks[0].{status:StackStatus,reason:StackStatusReason}" --output table
aws cloudformation describe-stack-events --stack-name $STACK \
--query "StackEvents[?contains(ResourceStatus,'FAILED')].[Timestamp,LogicalResourceId,ResourceType,ResourceStatus,ResourceStatusReason]" \
--output table
For nested stacks, the parent only says Embedded stack ... was not successfully updated. Find the child stack and read its events too:
aws cloudformation list-stack-resources --stack-name $STACK \
--query "StackResourceSummaries[?ResourceType=='AWS::CloudFormation::Stack'].[LogicalResourceId,PhysicalResourceId,ResourceStatus]" \
--output table
2. Match the reason to a fix
| Status reason (abridged) | Cause | Fix |
|---|---|---|
... is not authorized to perform ... | The deploy role or the stack's service role lost a permission | Restore the permission, then continue the rollback |
Resource ... not found / does not exist | Someone deleted the resource outside CloudFormation | Skip it in continue-update-rollback |
The bucket you tried to delete is not empty | S3 buckets must be empty before deletion | Empty the bucket, or retain it |
... has a dependent object / DependencyViolation | A security group, ENI or subnet is still used by something outside the stack | Remove the dependency (often a leftover Lambda ENI or an attached instance) |
Failed to receive N resource signal(s) | cfn-signal never arrived from EC2 or the ASG | Fix the bootstrap script or network path, then retry |
CloudFormation did not receive a response from your Custom Resource | Custom resource Lambda crashed or never called the response URL | Fix the function; see step 5 |
Rate exceeded / Throttling | API throttling during a large update | Retry; split large stacks |
3. Recover from UPDATE_ROLLBACK_FAILED
First fix the cause where you can (restore the IAM permission, recreate the dependency, raise the quota). Then retry the rollback:
aws cloudformation continue-update-rollback --stack-name $STACK
aws cloudformation wait stack-rollback-complete --stack-name $STACK
If you can't fix a resource, for example because it was deleted by hand, tell CloudFormation to skip it. CloudFormation then marks it as rolled back without touching it:
aws cloudformation continue-update-rollback --stack-name $STACK \
--resources-to-skip AppSecurityGroup MyNestedStack.WorkerFunction
Use NestedStackLogicalId.ResourceLogicalId for resources inside nested stacks, and run the command against the root stack.
Warning
A skipped resource's real state and the template no longer match. Run drift detection afterwards (step 6) and fix the difference on the next deploy, or the next update may fail on the same resource.
You can't skip a resource that other resources still depend on unless you skip the dependants too. Only skip resources whose status is UPDATE_FAILED.
4. Recover from DELETE_FAILED
Find the resources that blocked the delete, then pick one approach:
aws cloudformation list-stack-resources --stack-name $STACK \
--query "StackResourceSummaries[?ResourceStatus=='DELETE_FAILED'].[LogicalResourceId,PhysicalResourceId,ResourceType]" \
--output table
Fix and retry. Empty the bucket, detach the ENI, then delete again:
aws s3 rm s3://my-app-logs-bucket --recursive
aws cloudformation delete-stack --stack-name $STACK
aws cloudformation wait stack-delete-complete --stack-name $STACK
On a versioned bucket, aws s3 rm leaves old versions and delete markers behind. Use a lifecycle rule or the console's Empty action instead.
Retain specific resources. The stack is deleted and the named resources stay in your account, no longer managed by CloudFormation:
aws cloudformation delete-stack --stack-name $STACK \
--retain-resources AppLogsBucket LegacyQueue
Force delete. This skips every resource that failed to delete. They show as DELETE_SKIPPED and stay in your account:
aws cloudformation delete-stack --stack-name $STACK --deletion-mode FORCE_DELETE_STACK
--retain-resources and FORCE_DELETE_STACK only work when the stack is already in DELETE_FAILED. Retained or skipped resources keep costing money, so record their IDs and clean them up or import them into another stack. If the stack has termination protection turned on, you have to turn it off before any delete will work.
5. Stack stuck IN_PROGRESS
UPDATE_IN_PROGRESS on one resource for a long time usually means CloudFormation is waiting on something:
Custom resource. If the Lambda function never sends a response to the pre-signed URL, CloudFormation waits until the timeout. That's one hour by default; you can lower it with the
ServiceTimeoutproperty on newer templates. Check the function's logs:aws logs tail /aws/lambda/my-custom-resource-fn --since 2hThe fastest way out is to send a
SUCCESSorFAILEDresponse to theResponseURLfrom the logged event, using the documented custom resource response format.ECS service. The service never stabilises because tasks keep failing health checks. Fix the task, or turn on the deployment circuit breaker so the deployment fails fast.
Creation policy or wait condition. Instances never sent
cfn-signal.
You can cancel an update that is still running. The stack then rolls back:
aws cloudformation cancel-update-stack --stack-name $STACK
6. Check drift after recovery
DRIFT_ID=$(aws cloudformation detect-stack-drift --stack-name $STACK --query StackDriftDetectionId --output text)
aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id $DRIFT_ID
aws cloudformation describe-stack-resource-drifts --stack-name $STACK \
--stack-resource-drift-status-filters MODIFIED DELETED \
--query "StackResourceDrifts[].[LogicalResourceId,StackResourceDriftStatus]" --output table
Fix each drifted resource in the template, or put it back to the template's values, before the next deploy.
Prevent it next time
- Put
DeletionPolicy: RetainandUpdateReplacePolicy: Retainon stateful resources (databases, buckets, KMS keys, file systems), and turn on termination protection for production stacks. - Use change sets (
create-change-set, then review, thenexecute-change-set) so you see replacements before they happen. - During development, deploy with
--disable-rollback. A failed stack stays inUPDATE_FAILEDso you can fix the resource and retry, or callaws cloudformation rollback-stack. - Never change stack-managed resources by hand. Give engineers read-only console access in production, let changes go through the pipeline's role, and run drift detection on a schedule (the AWS Config managed rule
cloudformation-stack-drift-detection-checkcan do this). - Make custom resource functions always send a response, including in
except/finallyblocks, and set a shortServiceTimeout.