AWS

Using aws-nuke with the Landing Zone Accelerator and Control Tower

The nuke config you need so a sandbox reset does not wipe your LZA baseline

Jan 29, 2026 · 7 min read

Photo from Unsplash

Introduction

We've all been there: you hand a developer a sandbox account, they spend two weeks spinning up VPCs, RDS instances, half-finished EKS clusters and a Bedrock experiment nobody remembers, and now you want it clean again. Not gone, not re-vended through Control Tower, just clean. Reset to the same baseline every other account has.

The obvious tool for that is aws-nuke. Point it at an account, and it removes everything it can find. I use ekristen's fork (as the original one from rebuy-de is no longer maintained), currently on the v3 line.

But "everything" is exactly the problem. If your accounts are governed by AWS Control Tower and managed by the Landing Zone Accelerator (LZA), a naive nuke run happily deletes the AWSControlTowerExecution role, the AWSReservedSSO_* roles from IAM Identity Center, and every AWSAccelerator-* resource the pipeline put there. The account survives, but it has drifted out of governance, and the next LZA pipeline run fails when attempting to re-baseline it manually.

So the trick is not running nuke, it is telling it which resources to keep with the appropriate rules.

Let's jump in.

The challenge

When Control Tower vends an account and LZA baselines it, three sets of resources land in that account that you must never touch:

  1. Control Tower baseline: the AWSControlTowerExecution role, config recorders, the org CloudTrail, the guardrail plumbing (all named or tagged aws-controltower / AWSControlTower).
  2. IAM Identity Center (SSO): the AWSReservedSSO_* permission-set roles and the AWSSSO_..._DO_NOT_DELETE SAML provider.
  3. Landing Zone Accelerator: everything the pipeline deploys, named or tagged AWSAccelerator, aws-accelerator, or cdk-accel (the CDK bootstrap it uses). KMS keys, S3 buckets, Lambda functions, CloudFormation stacks, SSM parameters, Config rules, the lot.

Delete any of those, and the account breaks in a way that is annoying to fix. Also, it is highly possible that deleting those resources is protected by SCPs, and you cannot delete them even with an administrator role. Re-registering an account in Control Tower or forcing an LZA re-baseline is a lot more work than the nuke was supposed to save you.

Now, aws-nuke has a presets feature for exactly this: named blocks of filters you attach to an account so matching resources are skipped. And the community config presets already ship an sso preset and a controltower preset. Good start.

However there is a gap: there is no LZA preset in the community docs. If you nuke an LZA-managed account with only sso and controltower filters, the accelerator's entire footprint is in scope for deletion. That is the missing piece.

The solution

The best LZA preset I found is not in the aws-nuke docs at all, it is buried in an AWS sample repo: the default-nuke-config-template.yml from sandbox-accounts-for-events (the old Disposable Cloud Environment / DCE project). It runs nuke on a schedule to reset event sandbox accounts, and it carries an lza preset alongside the sso and controltower ones.

That file is a Go template rendered per-account inside CodeBuild ({{ .ID }}, {{ .Regions }} and friends). Below is the same thing de-templated into a standalone config you can run yourself.

Install

Grab the binary from the releases page, or on macOS use the author's tap:

brew install ekristen/tap/aws-nuke

Check you are on a v3 build:

aws-nuke --version

The config

Two safety rails first, because nuke is unforgiving:

  • account-blocklist is mandatory. Put your management account (and any other account you never want touched) here. If the account you are authenticated to appears on the blocklist, nuke refuses to run. This is your seatbelt against pointing it at the wrong account.
  • Runs are a dry run by default. You only delete for real when you pass --no-dry-run. Keep it that way until the output looks right.
nuke-config.yaml
regions:
  - "global"       # global services: IAM, Route 53, CloudFront, ...
  - "us-east-1"    # keep this even for eu workloads: many global APIs live here
  - "eu-central-1" # add EVERY region you ever deployed into
 
account-blocklist:
  - "123456789012" # your management account - NEVER nuke this
 
resource-types:
  excludes:
    - S3Object          # let S3Bucket delete its own objects (much faster)
    - GuardDutyDetector # part of the org GuardDuty integration
    - SecurityHub       # same, org-managed
    - OSPackage         # avoids non-nukeable AWS-managed OpenSearch packages
 
accounts:
  "210987654321": # the sandbox account you are resetting
    presets:
      - "sso"
      - "controltower"
      - "lza"
    filters:
      # keep the cross-account access role Organizations creates
      IAMRole:
        - type: "contains"
          value: "OrganizationAccountAccessRole"
      IAMRolePolicy:
        - property: RoleName
          value: "OrganizationAccountAccessRole"
      IAMRolePolicyAttachment:
        - property: RoleName
          value: "OrganizationAccountAccessRole"

Then the presets. First the two from the community docs, sso and controltower:

nuke-config.yaml (presets, part 1)
presets:
  sso:
    filters:
      IAMSAMLProvider:
        - type: "regex"
          value: "AWSSSO_.*_DO_NOT_DELETE"
      IAMRole:
        - type: "glob"
          value: "AWSReservedSSO_*"
      IAMRolePolicyAttachment:
        - type: "glob"
          value: "AWSReservedSSO_*"
      IAMRolePolicy:
        - type: "glob"
          value: "AWSReservedSSO_*"
 
  controltower:
    filters:
      CloudTrailTrail:
        - type: "contains"
          value: "aws-controltower"
      CloudWatchEventsRule:
        - type: "contains"
          value: "aws-controltower"
      EC2VPC:
        - type: "contains"
          value: "aws-controltower"
        - property: tag:Name
          type: "contains"
          value: "aws-controltower"
      EC2Subnet:
        - type: "contains"
          value: "aws-controltower"
        - property: tag:Name
          type: "contains"
          value: "aws-controltower"
      EC2RouteTable:
        - type: "contains"
          value: "aws-controltower"
      OpsWorksUserProfile:
        - type: "contains"
          value: "AWSControlTowerExecution"
      CloudWatchLogsLogGroup:
        - type: "contains"
          value: "aws-controltower"
        - type: "contains"
          value: "AWSControlTowerBP"
      ConfigServiceDeliveryChannel:
        - type: "contains"
          value: "aws-controltower"
      ConfigServiceConfigurationRecorder:
        - type: "contains"
          value: "aws-controltower"
      CloudFormationStack:
        - type: "contains"
          value: "AWSControlTower"
      LambdaFunction:
        - type: "contains"
          value: "aws-controltower"
      IAMRole:
        - type: "contains"
          value: "aws-controltower"
        - type: "contains"
          value: "AWSControlTower"
      IAMRolePolicyAttachment:
        - type: "contains"
          value: "aws-controltower"
        - type: "contains"
          value: "AWSControlTower"
      IAMRolePolicy:
        - type: "contains"
          value: "aws-controltower"
        - type: "contains"
          value: "AWSControlTower"

And the part that makes this work for LZA is the lza preset. Every LZA resource is matched three ways: by the AWSAccelerator name, the aws-accelerator name, and the cdk-accel CDK bootstrap prefix, plus the Accelerator tag the pipeline stamps on things:

nuke-config.yaml (presets, part 2 - lza)
  lza:
    filters:
      IAMRole: &lza
        - type: "contains"
          value: "AWSAccelerator"
        - type: "contains"
          value: "aws-accelerator"
        - type: "contains"
          value: "cdk-accel"
        - property: tag:Accelerator
          type: "contains"
          value: "AWSAccelerator"
      IAMPolicy: *lza
      IAMRolePolicy: *lza
      IAMRolePolicyAttachment: *lza
      CloudFormationStack: *lza
      LambdaFunction: *lza
      S3Bucket: *lza
      KMSKey: *lza
      KMSAlias: *lza
      SSMParameter: *lza
      SNSTopic: *lza
      SNSSubscription: *lza
      CloudWatchEventsRule: *lza
      CloudWatchEventsTarget: *lza
      CloudWatchLogsLogGroup: *lza
      ConfigServiceConfigRule: *lza
      AWSBackupVault: *lza

I used a YAML anchor (&lza / *lza) here because the filter is identical for every resource type. The DCE (Disposable cloud environments) original spells each one out in full; if your tooling does not like anchors, expand it back to one block per resource type. The set of resource types is the important part, that is the full list of things LZA creates in a member account.

Dry run, then nuke

Preview what nuke would do without deleting anything. The default run is already a dry run, but explain-config is even safer, it just prints the plan:

aws-nuke explain-config -c nuke-config.yaml

Then a real dry run against the account (assume the role into the sandbox account first, e.g. via AWS_PROFILE):

aws-nuke run -c nuke-config.yaml --profile sandbox

Read the output. Everything AWSAccelerator-*, aws-controltower* and AWSReservedSSO_* should be listed as filtered (skipped), and your workload junk should be listed as would remove. If a baseline resource appears under "would remove", stop and fix your filters before proceeding. Do not skip this step.

When it looks right, run it in explicit --no-dry-run mode:

aws-nuke run -c nuke-config.yaml --profile sandbox --no-dry-run

nuke also refuses to run against an account with no account alias set, another guardrail to prevent you from accidentally targeting a fresh production account. It runs in loops, retrying resources whose dependencies still exist, until the account is drained down to the baseline you told it to keep.

Conclusion

The community sso and controltower presets get you most of the way, but the missing lza preset is what stops a sandbox reset from turning into an afternoon of re-baselining. The one I use comes straight out of the AWS sandbox-accounts-for-events template, de-templated so you can run it by hand instead of only inside their CodeBuild job.

Personally, my rule of thumb is: for a sandbox or throwaway dev account, reset with nuke rather than closing and re-vending it through Control Tower. Re-vending means a new email alias, a new account under the OU, and waiting on the pipeline; a filtered nuke run gets you a clean account in minutes and retains the same account ID. Keep the config in version control, treat the blocklist and the dry run as mandatory, and it becomes a boring, repeatable operation.

I hope this saves you the broken-baseline detour. Happy AWS account nuking, carefully.

This post was written with AI assistance and verified plus enhanced by a human.

More from the blog

Keep reading - related notes from recent engagements.

DevOps

Advanced GitLab CI features in v19

A tour from job grouping to dynamic child pipelines, and a look at the experimental functions feature, every example run on a real GitLab 19 instance.

Ready to ship faster on AWS?

Tell us what you are building. We will map the fastest safe path to production and the platform to keep it there.

Notes from production

Occasional, no-fluff writing on AWS, DevOps, and running platforms that stay up. No spam, unsubscribe anytime.

Practical, not promotional
Real lessons from real engagements - architecture, automation, and incident post-mortems.
No spam
A few emails a year at most. Your address is never shared or sold.