Server

EC2 Instance Metadata and IMDSv2

By Prabath Thalangama· October 24, 2026· 3 min read
#aws#ec2#security

Introduction

Every EC2 instance can query http://169.254.169.254/ for metadata about itself — instance ID, region, and crucially the temporary IAM credentials for its instance profile. That last part is why the service is a security- sensitive target.

What's there

/latest/meta-data/instance-id
/latest/meta-data/placement/region
/latest/meta-data/iam/security-credentials/<role-name>   <- STS creds, rotated
/latest/dynamic/instance-identity/document                <- signed identity doc
/latest/user-data                                         <- your bootstrap script

The SDKs and CLI read the credentials path automatically when no other credential source is configured — that's how "an EC2 instance just has AWS access".

The IMDSv1 problem

IMDSv1 is a plain unauthenticated GET. If your app has a server-side request forgery (SSRF) bug — it fetches a URL an attacker controls — the attacker points it at http://169.254.169.254/latest/meta-data/iam/security-credentials/ and exfiltrates the instance's IAM credentials. This has caused real, large breaches.

How IMDSv2 fixes it

IMDSv2 requires a session token obtained with a PUT (which most SSRF primitives can't do) that also carries a X-aws-ec2-metadata-token-ttl-seconds header. Responses to PUT and the token requirement mean a naive fetch(attacker_url) SSRF can't reach it, and the low default hop limit (1) stops the token leaving the instance.

TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/instance-id

Enforce it

At launch, or on a running instance:

aws ec2 modify-instance-metadata-options \
  --instance-id i-0abc123 \
  --http-tokens required \          # IMDSv2 only; IMDSv1 requests now fail
  --http-endpoint enabled \
  --http-put-response-hop-limit 1

In a launch template / ASG, set MetadataOptions.HttpTokens=required. There's an account-wide default you can set so new instances get it automatically, and an Organization SCP / Config rule to catch drift.

The hop limit and containers

http-put-response-hop-limit is the IP TTL on metadata responses. Default 1 means the response dies before leaving the instance — good.

But a container adds a network hop (the bridge). A pod/container reaching IMDS needs hop limit 2. Trade-off: hop limit 2 slightly widens the blast radius. Better options for containers:

  • EKS: IAM Roles for Service Accounts (IRSA) or EKS Pod Identity — the pod gets its own role via a projected token, and you can block pod access to IMDS entirely (--http-put-response-hop-limit 1 + a NetworkPolicy / iptables rule dropping 169.254.169.254).
  • ECS: the task metadata endpoint + task roles; disable container access to instance IMDS.

Verification and troubleshooting

# Is IMDSv1 still allowed? (should fail if enforced)
curl -s -o /dev/null -w "%{http_code}\n" http://169.254.169.254/latest/meta-data/
# 401 = IMDSv2 required (good). 200 = IMDSv1 still works.

aws ec2 describe-instances --instance-id i-0abc123 \
  --query 'Reservations[].Instances[].MetadataOptions'

# CloudWatch metric: MetadataNoToken > 0 means something still uses IMDSv1
  • App breaks after enforcing IMDSv2 — an old SDK (pre-2018) or a hand-rolled metadata client that only does IMDSv1. Update the SDK; all current ones speak v2.
  • Container can't get credentials — hop limit is 1. Raise to 2, or (better) use IRSA / Pod Identity / task roles.
  • MetadataNoToken metric nonzero — find the offender before enforcing: it's usually a monitoring agent, a CI tool, or an old base image's SDK.
  • Credentials work then expire mid-job — the instance profile creds are temporary (~6h) and auto-rotate; a long job that cached them at start needs to re-read. SDKs handle this; custom code must.
  • 403 fetching user-data — there is no user-data, or it's been consumed; not an error condition per se.
  • Latency calling IMDS from many threads — the endpoint rate-limits. SDKs cache the token; don't fetch a new token per request.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.