skip to content

On an EC2 instance, how does IMDSv2 differ from IMDSv1 at the request level, and what does setting the instance's metadata option HttpTokens to `required` actually enforce?

level: middleimportance: must knowfreq 58%

answer

  1. two steps instead of one
  2. the verb is not GET
  3. the token rides in a header
  4. a switch decides whether v1 still answers
  5. proves the caller is local, grants nothing

basics

~20 s

IMDSv1 answers a plain GET to 169.254.169.254. IMDSv2 requires a PUT to /latest/api/token first, and every later GET must carry that token in an X-aws-ec2-metadata-token header. Setting HttpTokens to required makes the instance reject token-less requests.

solid answer

~50 s

IMDSv1 is a bare HTTP GET against the link-local address `169.254.169.254`, with no credential of any kind — anything that can make an outbound request from the instance can read it, including the instance role's temporary credentials under `/latest/meta-data/iam/security-credentials/`. IMDSv2 makes it a two-step session: the client sends a `PUT` to `/latest/api/token` with an `X-aws-ec2-metadata-token-ttl-seconds` header, gets back an opaque token, and must present that token in an `X-aws-ec2-metadata-token` header on every subsequent GET. Three properties do the work: it takes a PUT rather than a GET, the service refuses token requests that carry an `X-Forwarded-For` header, and the token response is emitted with a small IP hop limit so it cannot be relayed off the instance. `HttpTokens=required` is the switch that stops the instance from answering IMDSv1-style token-less requests at all; while it is `optional`, both versions work and the hardening is only theoretical.

code

bash · 5 lines
bash
TOKEN=$(curl -s -X 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

go deeper

for a junior

Know that instance metadata lives at 169.254.169.254, that IMDSv2 needs a PUT to /latest/api/token first, and that the returned token goes in a header on every later request.

for a middle

Explain the mechanics: the TTL header on the PUT, the token header on the GET, and what HttpTokens=required versus optional changes about which requests the instance answers.

for a senior

Show a safe rollout: audit with the MetadataNoToken metric, update SDKs and agents, bake MetadataOptions into launch templates, and be clear that IMDSv2 does nothing about code already running on the box.

for a principal

Own it as a fleet-wide guardrail rather than an instance setting — account-level metadata defaults, an IAM condition key on RunInstances, and a stated position on whether instances should carry broadly-scoped roles at all.

## What the metadata service is Every EC2 instance can query a link-local HTTP endpoint at `169.254.169.254` (and `fd00:ec2::254` over IPv6) for facts about itself: instance ID, instance type, Availability Zone, network configuration, the user data it was launched with, and — the part that matters — the temporary credentials of the IAM role attached through its instance profile, under `/latest/meta-data/iam/security-credentials/<role-name>`. The AWS SDKs read that path automatically, which is why code on an instance "just has" credentials without any file on disk. That convenience is also the exposure: those credentials are readable by anything that can cause the instance to make an HTTP request to a chosen URL. ## The v1 request ```bash curl http://169.254.169.254/latest/meta-data/instance-id ``` One unauthenticated GET, no headers, no state. Simple, and impossible to distinguish from any other GET the instance might be tricked into making. ## The v2 handshake IMDSv2 introduces a session token: ```bash TOKEN=$(curl -s -X 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 ``` The TTL header is mandatory on the PUT and sets how long the token stays valid, up to a maximum of six hours (21600 seconds). The token is opaque, bound to that instance, and carries no privileges of its own — it is not a credential you can carry elsewhere; presenting it merely proves the requester was able to complete a local round trip. ## Why those specific choices Each element of the design closes off a way that a request could be issued by something other than software running on the instance: - **It is a PUT, not a GET.** A great many request-forging primitives can only cause a GET with no custom headers, so they cannot obtain a token at all. - **The token must be echoed in a custom request header.** Anything that can control only a URL cannot add headers. - **A token request carrying `X-Forwarded-For` is refused.** That header is what a proxy adds when it forwards someone else's request, so its presence is proof the request is not the instance's own. - **The token response is emitted with a small IP hop limit** (`HttpPutResponseHopLimit`, default 1). Even if a token were somehow requested through the instance, the response packet's TTL expires before it can be routed out to a remote caller. ## Turning it on The metadata behaviour is an instance property, in `MetadataOptions`: - `HttpTokens` — `optional` (both v1 and v2 answered) or `required` (v2 only) - `HttpEndpoint` — `enabled` or `disabled`; disabling it turns the metadata service off entirely, which is the right answer for an instance that needs no instance role - `HttpPutResponseHopLimit` — 1 to 64 - `InstanceMetadataTags` — whether instance tags are readable through IMDS ```bash aws ec2 modify-instance-metadata-options --instance-id i-0123456789abcdef0 \ --http-tokens required --http-endpoint enabled --http-put-response-hop-limit 1 ``` For new instances, set `MetadataOptions` in the launch template so every replacement inherits it, and enforce it at the policy layer with the IAM condition key `ec2:MetadataHttpTokens` on `RunInstances` so nobody can launch an instance in `optional` mode. There is also an account-and-Region-level default (`modify-instance-metadata-defaults`) so newly launched instances start hardened. ## Migrating without breaking things Flipping `HttpTokens` to `required` breaks any client still speaking v1 — an old SDK, a vendored tool, a hand-rolled `curl` in a bootstrap script. The safe order is: update SDKs and agents (all current AWS SDKs and the CLI speak v2 and fall back only when allowed), watch the per-instance CloudWatch metric `MetadataNoToken`, which counts token-less calls, wait for it to sit at zero, then set `required`. AWS has itself been progressively making IMDSv2 the default for newer instance types and launch paths, so a fleet audit is worth doing rather than assuming a uniform starting point. ## The limits of the control IMDSv2 raises the bar for reaching the metadata service from outside the instance. It does nothing about code that is genuinely running on the instance — a compromised process reads credentials either way. The complementary controls are a narrow instance role, disabling the endpoint entirely where no role is needed, and giving individual workloads their own credentials rather than sharing the host's.

  • How would you find out which instances are still being called with IMDSv1 before you enforce v2?
    Each instance publishes a CloudWatch metric named `MetadataNoToken` counting token-less metadata calls. Alarm on it across the fleet, fix whatever is still making them — usually an old SDK, an agent, or a bootstrap script using plain `curl` — and only flip `HttpTokens` to `required` once the metric has been flat at zero for long enough to cover infrequent code paths.
  • What does setting HttpEndpoint to disabled do, and when is that the better choice?
    It turns the metadata service off for that instance entirely, so nothing local can read instance identity or role credentials. It is the right setting for an instance that has no instance profile and needs no self-discovery — a pure worker fed everything through configuration. It is stricter than `HttpTokens=required` and worth using whenever the instance genuinely has nothing to read.
  • Does IMDSv2 protect the instance role credentials from a process running on the instance itself?
    No, and claiming otherwise is the common overstatement. Any code executing on the instance can complete the PUT and read the credentials exactly as the SDK does. IMDSv2 defends against requests that originate outside the instance and are relayed through it. Limiting what the instance role can do, and disabling the endpoint where no role is needed, are the controls that address local compromise.

saying these in an interview costs you the question

  • Calls the IMDSv2 token an IAM credential
  • Thinks the token is fetched with a GET
  • Believes IMDSv2 protects against a compromised process on the box
  • Assumes setting HttpTokens=optional already enforces v2
  • Confuses the token TTL header with the hop limit

context