Back to blog
AWSBest PracticesCloud SecurityCompute & ContainersReliability

EC2 Termination Protection Disabled: Why It Matters and How to Fix It

Learn how to detect and fix disabled EC2 termination protection on AWS, prevent accidental instance deletion, and enforce the setting with IaC and policy-as-code.

TL;DR

This check flags EC2 instances that have API termination protection turned off, which means a single bad command or console click can permanently delete them. Enable protection with aws ec2 modify-instance-attribute --instance-id i-xxxx --disable-api-termination on your critical instances.

Termination protection is one of those settings that costs nothing, takes seconds to enable, and saves you from an entire category of self-inflicted outages. Yet it ships disabled by default on every EC2 instance you launch. The Lensix ec2_deletion_protection check scans your fleet and tells you exactly which instances are one fat-fingered command away from disappearing.

Here is what the check does, why it matters more than people think, and how to fix it across the console, the CLI, and your infrastructure-as-code.


What this check detects

The check inspects the DisableApiTermination attribute on each EC2 instance in the account. When that attribute is false (the default), the instance can be terminated through the API, the CLI, or the console without any extra confirmation step. The check reports those instances as failing.

When DisableApiTermination is true, AWS rejects any TerminateInstances call against that instance until someone explicitly disables protection first. It is a deliberate speed bump between an accidental command and an irreversible deletion.

Note: Termination protection only blocks API and console terminations. It does not stop an instance from being terminated by Auto Scaling, by a Spot interruption, or by an InstanceInitiatedShutdownBehavior set to terminate from inside the OS. It is a guardrail for human and automation mistakes, not a substitute for backups.


Why it matters

Terminating an EC2 instance is permanent. There is no recycle bin, no soft-delete window, no "undo" button. The instance is gone, and unless the attached EBS volumes were set to persist (which is also not the default for the root volume), your data goes with it.

Here are the scenarios this check is built to prevent:

  • The wrong instance ID. An engineer copies an instance ID from the wrong terminal tab and runs terminate-instances against a production database host instead of the scratch box they meant to kill.
  • Bulk cleanup gone wrong. A script loops over instances tagged Environment=dev, but a critical instance was mistagged, and it gets swept up in the teardown.
  • Console multi-select. Someone selects a range of instances in the console, hits terminate, and a load-bearing instance was sitting in the middle of that selection.
  • Compromised credentials. An attacker with EC2 permissions runs a destructive sweep to cause maximum disruption. Termination protection forces an extra step that gives detective controls a chance to fire.

The business impact is straightforward: lost data, an outage that lasts as long as your recovery process takes, and in the worst case a permanent loss if backups were stale or nonexistent. For a stateful single-instance workload, this is the difference between a five-minute scare and a multi-hour incident.

Warning: Termination protection is not a backup strategy. It reduces the chance of accidental deletion, but you still need EBS snapshots, AMIs, or database backups for anything stateful. Treat this check as one layer, not the whole defense.


How to fix it

Console

  1. Open the EC2 console and go to Instances.
  2. Select the instance you want to protect.
  3. Choose Actions, Instance settings, Change termination protection.
  4. Check Enable and save.

CLI

Enable termination protection on a single instance:

aws ec2 modify-instance-attribute \
  --instance-id i-0123456789abcdef0 \
  --disable-api-termination

The flag name reads backwards, but --disable-api-termination with no value sets DisableApiTermination to true, which enables protection. To verify:

aws ec2 describe-instance-attribute \
  --instance-id i-0123456789abcdef0 \
  --attribute disableApiTermination \
  --query 'DisableApiTermination.Value'

A return value of true means protection is on.

To fix the whole account in one pass, list every running instance that lacks protection and enable it. The following loops over running instances and enables protection on each:

for id in $(aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].InstanceId' \
  --output text); do
  echo "Protecting $id"
  aws ec2 modify-instance-attribute --instance-id "$id" --disable-api-termination
done

Tip: Do not blindly protect every instance. Auto Scaling instances and short-lived workers should stay unprotected so the scaling lifecycle works correctly. Scope your bulk fix with a tag filter like Name=tag:Persistent,Values=true so you only protect the instances that are meant to live a long time.

Terraform

Set disable_api_termination on the resource so the protection is baked into your infrastructure definition:

resource "aws_instance" "db" {
  ami                     = "ami-0123456789abcdef0"
  instance_type           = "m6i.large"
  disable_api_termination = true

  tags = {
    Name        = "prod-db-primary"
    Persistent  = "true"
  }
}

CloudFormation

{
  "Type": "AWS::EC2::Instance",
  "Properties": {
    "ImageId": "ami-0123456789abcdef0",
    "InstanceType": "m6i.large",
    "DisableApiTermination": true
  }
}

When you eventually need to retire a protected instance, you have to flip the attribute off first:

Danger: The command below removes the safety net. Once protection is off, the next terminate-instances call will permanently delete the instance and any non-persistent volumes. Double-check the instance ID before you run it.

aws ec2 modify-instance-attribute \
  --instance-id i-0123456789abcdef0 \
  --no-disable-api-termination

How to prevent it from happening again

Fixing the current findings is the easy part. Keeping the setting correct as new instances launch is where most teams slip. A few options, from lightest to most enforced:

1. Default it in your modules

If everyone provisions instances through a shared Terraform module or a launch template, set disable_api_termination = true as the module default for anything tagged persistent. New consumers inherit the safe setting without thinking about it.

2. Gate it in CI/CD with policy-as-code

Use a policy engine to reject plans that create long-lived instances without protection. Here is an example OPA Conftest rule against a Terraform plan:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_instance"
  resource.change.after.tags.Persistent == "true"
  resource.change.after.disable_api_termination == false
  msg := sprintf("Instance %s is tagged Persistent but has termination protection disabled", [resource.address])
}

Wire that into your pipeline so a pull request cannot merge an unprotected persistent instance.

3. Detect drift continuously

Manual console changes and out-of-band scripts will eventually disable protection on something that matters. Run the Lensix ec2_deletion_protection check on a schedule so any instance that drifts back to unprotected gets surfaced quickly instead of discovered during an incident.

Tip: Pair termination protection with an EventBridge rule that alerts on TerminateInstances calls against tagged-critical instances. Even when protection is intentionally disabled for a planned retirement, you get a record of who did it and when.


Best practices

  • Protect by intent, not by default. Apply termination protection to instances that hold state or run singleton services. Leave ephemeral and auto-scaled instances unprotected so their lifecycle automation keeps working.
  • Tag your intent. A Persistent=true tag (or similar) makes it trivial to script, audit, and enforce protection consistently across accounts.
  • Set InstanceInitiatedShutdownBehavior to stop. Termination protection does not block a shutdown issued from inside the OS if the behavior is set to terminate. Set it to stop for protected instances.
  • Preserve the root volume. Set DeleteOnTermination to false on the root EBS volume for critical instances so an accidental termination does not also destroy the disk.
  • Use SCPs for blast-radius control. A Service Control Policy that requires MFA or restricts who can call ec2:ModifyInstanceAttribute on production accounts adds a layer above the per-instance setting.
  • Back up regardless. Termination protection lowers the odds of accidental deletion. Snapshots and AMIs are what actually let you recover. Keep both.

Termination protection is a tiny configuration change with an outsized payoff. Enable it on the instances that matter, enforce it in your pipeline, and let continuous scanning catch the drift. The day a teammate runs the wrong command, you will be glad the speed bump was there.