This check flags EC2 instances and launch templates that still allow IMDSv1 (HttpTokens=optional), which leaves the instance metadata service open to SSRF-based credential theft. Fix it by setting HttpTokens=required on the instance or launch template to enforce IMDSv2.
The EC2 Instance Metadata Service (IMDS) is one of those features that quietly powers a huge amount of AWS automation. Your instances use it to fetch temporary IAM credentials, discover their own region, read user data, and more. The problem is that the original version, IMDSv1, was designed in a more trusting era. It responds to any plain HTTP GET request from inside the instance, with no session token required. That single design choice has turned IMDS into one of the most exploited paths to cloud credential theft.
The IMDSv2 Not Enforced check looks for EC2 instances and launch templates where HttpTokens is set to optional. When it is optional, IMDSv1 still works, and an attacker who can coax your application into making a request to 169.254.169.254 can walk away with your instance role credentials.
What this check detects
Every running EC2 instance and every launch template has metadata options that control how the metadata service behaves. The relevant setting is HttpTokens:
- optional — both IMDSv1 (no token) and IMDSv2 (session token) are accepted. This is the insecure state the check flags.
- required — only IMDSv2 requests with a valid session token are accepted. IMDSv1 is effectively disabled.
The check raises a finding whenever it sees HttpTokens=optional on an instance metadata configuration or a launch template. It covers launch templates as well as live instances because templates are where the drift usually starts. A misconfigured template silently produces insecure instances every time an Auto Scaling group scales out.
Note: IMDSv2 works by requiring a two-step request. The client first does a PUT to obtain a short-lived session token, then includes that token as a header on every metadata GET. Because the initial step is a PUT and the token must be sent as a header, most SSRF and reverse-proxy attacks cannot reach the metadata endpoint, since they typically can only force simple GET requests.
Why it matters
IMDS hands out IAM role credentials. If your instance has an instance profile attached (most do), then anything that can reach the metadata endpoint can read those credentials and use them from anywhere. With IMDSv1 enabled, reaching that endpoint is far easier than it should be.
The classic attack: SSRF to credential theft
Server-Side Request Forgery (SSRF) is the most common way IMDSv1 gets exploited. Imagine a web app running on EC2 that fetches a URL supplied by the user, maybe an image previewer or a webhook tester. An attacker sends:
http://169.254.169.254/latest/meta-data/iam/security-credentials/my-app-role
If IMDSv1 is allowed, the app dutifully makes that GET request, the metadata service responds, and the attacker gets back a JSON blob containing an AccessKeyId, SecretAccessKey, and Token. Those credentials carry whatever permissions the instance role has. From there the attacker can pivot into your account, often without ever logging into a single host.
This is not theoretical. The 2019 Capital One breach, which exposed data on roughly 100 million people, used exactly this pattern: an SSRF flaw in a misconfigured WAF was used to query IMDSv1 and steal role credentials that had broad S3 access.
Warning: The blast radius is determined by your instance role's permissions, not by the metadata service. An over-privileged instance profile combined with IMDSv1 turns a minor SSRF bug into a full account compromise. Audit your instance roles alongside this check.
IMDSv2 does not magically fix SSRF, but it raises the bar significantly. Because the attacker would need to force a PUT request with a custom header and then a follow-up GET with the returned token, the simple GET-based SSRF that leaks credentials no longer works. You can also set the metadata response hop limit to 1, which stops the request from being proxied through a container network.
How to fix it
You can enforce IMDSv2 on a running instance with no reboot. Be aware that any code relying on IMDSv1 will break, so check your applications and SDKs first.
Warning: Older AWS SDKs and some third-party agents only speak IMDSv1. Modern SDK versions support IMDSv2 by default, but legacy tooling, custom scripts using raw curl, and old CLI versions may fail once you enforce it. Test in staging before rolling this out fleet-wide.
Fix a running instance via CLI
aws ec2 modify-instance-metadata-options \
--instance-id i-0123456789abcdef0 \
--http-tokens required \
--http-endpoint enabled \
--http-put-response-hop-limit 1
Setting --http-put-response-hop-limit 1 ensures the metadata response cannot travel beyond the instance itself, which blocks containers on a bridged network from reaching it. If you run containers that legitimately need metadata access, use 2 instead, or better, use IAM Roles for Service Accounts equivalents where available.
Fix the console way
- Open the EC2 console and select the instance.
- Choose Actions → Instance settings → Modify instance metadata options.
- Set IMDSv2 to Required.
- Set the metadata response hop limit to 1 (or 2 if you need container access).
- Save.
Fix the launch template
Fixing live instances is treating the symptom. The launch template is usually the source. A launch template version cannot be edited in place, so create a new version and update the default:
# Create a new version that enforces IMDSv2
aws ec2 create-launch-template-version \
--launch-template-id lt-0123456789abcdef0 \
--source-version 1 \
--launch-template-data '{
"MetadataOptions": {
"HttpTokens": "required",
"HttpEndpoint": "enabled",
"HttpPutResponseHopLimit": 1
}
}'
# Make the new version the default
aws ec2 modify-launch-template \
--launch-template-id lt-0123456789abcdef0 \
--default-version 2
Danger: Updating a launch template only affects newly launched instances. To apply the fix to an existing Auto Scaling group, you must trigger an instance refresh, which replaces running instances. Do this during a maintenance window or with a healthy minimum capacity to avoid an availability gap.
To roll the change through an Auto Scaling group:
aws autoscaling start-instance-refresh \
--auto-scaling-group-name my-asg \
--preferences '{"MinHealthyPercentage": 90, "InstanceWarmup": 120}'
Enforce it in Terraform
If you manage infrastructure as code, set the metadata options directly so the secure state is the only state that ships:
resource "aws_instance" "app" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.medium"
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
http_put_response_hop_limit = 1
}
}
resource "aws_launch_template" "app" {
name_prefix = "app-"
image_id = "ami-0123456789abcdef0"
instance_type = "t3.medium"
metadata_options {
http_endpoint = "enabled"
http_tokens = "required"
http_put_response_hop_limit = 1
}
}
Tip: You can set an account-wide default that forces all new instances to require IMDSv2, regardless of what their launch request specifies. Run aws ec2 modify-instance-metadata-defaults --http-tokens required --http-put-response-hop-limit 1 per region. This is the single highest-leverage change for shutting down IMDSv1 at scale.
How to prevent it from happening again
One-time fixes drift. The goal is to make HttpTokens=required the default and to block anything that tries to ship without it.
Set the region-level default
As mentioned in the tip above, modify-instance-metadata-defaults sets an account default per region. Any new instance launched without explicit metadata options inherits the required setting. This catches instances created by tooling you do not control, like third-party deployment systems.
Gate it in CI/CD with policy-as-code
Block insecure templates before they merge. Here is an Open Policy Agent (Rego) rule for Terraform plans:
package terraform.imds
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_launch_template"
opts := resource.change.after.metadata_options[_]
opts.http_tokens != "required"
msg := sprintf("%s must set http_tokens = required", [resource.address])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
opts := resource.change.after.metadata_options[_]
opts.http_tokens != "required"
msg := sprintf("%s must set http_tokens = required", [resource.address])
}
Wire this into your pipeline with conftest test plan.json or a Sentinel equivalent if you use Terraform Cloud. A pull request that introduces IMDSv1 then fails the check instead of reaching production.
Enforce with a Service Control Policy
For organization-wide enforcement, an SCP can deny instance launches that do not require IMDSv2:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyIMDSv1",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
}
]
}
Tip: Run Lensix on a schedule so launch template drift and any instances created outside your IaC pipeline get caught automatically. The region default and SCP cover the front door, but continuous scanning catches the edge cases that slip past, like manually modified instances or imported AMIs.
Best practices
- Default to required everywhere. Treat IMDSv1 as deprecated. New AMIs from AWS already prefer IMDSv2, and there is rarely a reason to keep IMDSv1 enabled in 2024 and beyond.
- Set the hop limit deliberately. Use 1 for standard instances. Only raise it to 2 when containers genuinely need metadata access, and prefer scoped IAM roles for pods or tasks instead.
- Right-size instance roles. IMDSv2 limits how credentials are stolen, but it does not limit what those credentials can do. Apply least privilege to every instance profile so a leak stays contained.
- Disable the endpoint entirely when you can. If an instance has no instance profile and no need for metadata, set
--http-endpoint disabledand remove the attack surface completely. - Monitor for IMDSv1 usage before flipping the switch. CloudWatch publishes the
MetadataNoTokenmetric per instance. A non-zero value means something is still using IMDSv1. Drive that to zero, then enforce.
Note: To find lingering IMDSv1 callers, watch the MetadataNoToken CloudWatch metric in the AWS/EC2 namespace. Any instance with a positive count is still serving IMDSv1 requests, which tells you exactly where you have a compatibility issue to resolve before enforcing.
Enforcing IMDSv2 is one of the cheapest, highest-impact security wins available on AWS. It closes off an entire class of credential theft attacks, requires no new infrastructure, and in most fleets can be rolled out without a single reboot. Fix the live instances, fix the launch templates, set the region default, and gate it in CI so it stays fixed.

