---
cuppy_hub: aws
cuppy_path: reference/ec2/instance-metadata-and-roles.md
cuppy_section: docs
cuppy_doc: 7D8H-EBTE
cuppy_doc_uuid: d38c809e-2037-4b0b-9fed-631ef6f20cc2
cuppy_permalink: https://cuppy.io/aws/d/7D8H-EBTE
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/reference/ec2/instance-metadata-and-roles
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 463df685047104816fe1efcb20df24ab6d5a91756a79929463500f3a3bb5b722
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Instance metadata (IMDSv2) and instance roles

**An instance's AWS credentials come from the role in its instance profile, served by the instance metadata service (IMDS). Require IMDSv2 (session tokens) everywhere, and set the hop limit to 2 if containers on the instance need credentials.**

## The pieces

| Piece | What it is |
|---|---|
| Role | An IAM role trusting `ec2.amazonaws.com`. Role changes propagate to all instances using it, and EC2 role credentials aren't limited by the role's maximum session duration (trust principal: UNVERIFIED in the collected sources) |
| Instance profile | The container that attaches a role to an instance. One role per profile and one profile per instance (the same role can be in several profiles). The IAM console creates a same-named profile automatically; the CLI, API and SDKs create role and profile separately, possibly with different names, and launches name the **profile**. To change an instance's permissions, **replace the instance profile**: removing a role from a profile can take up to an hour to take effect |
| Launch permission | The launcher needs `iam:PassRole` on the role. Its absence shows up as an encoded `UnauthorizedOperation` (`reference/ec2/errors.md`) |
| IMDS | An HTTP endpoint at `169.254.169.254`, reachable only from the instance, that serves metadata and the role's temporary credentials |

The AWS SDKs and the CLI find instance credentials automatically, at the end of the default credential chain. Environment variables or a profile set on the instance take precedence and are a common reason the "wrong" identity is used (`reference/sts/credential-sources.md`).

## IMDSv1 vs IMDSv2

- **IMDSv2** requires a session token obtained with a `PUT`, then sent as a header on each `GET`. That blocks the simple request-forgery attacks that make IMDSv1 dangerous.
- `HttpTokens=required` disables IMDSv1 for the instance. Current AWS SDKs use IMDSv2 automatically.

```
TOKEN=$(curl -sX PUT http://169.254.169.254/latest/api/token -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
```

(Token header names and paths: UNVERIFIED in the collected sources; they are the documented IMDSv2 interface.)

## Hop limit

`HttpPutResponseHopLimit` limits how many network hops the token response can travel. With the value **1**, a process in a container with its own network namespace, such as Docker bridge mode, cannot get a token, so the SDK in the container finds no credentials. Set it to **2** for instances that run containers. AWS's own example settings use 2.

## Setting it

| Scope | Command |
|---|---|
| One launch | `run-instances … --metadata-options "HttpEndpoint=enabled,HttpTokens=required,HttpPutResponseHopLimit=2"` |
| Running instance | `aws ec2 modify-instance-metadata-options --instance-id <id> --http-tokens required --http-put-response-hop-limit 2` (flags match the `ModifyInstanceMetadataOptions` API model: `HttpTokens`, `HttpPutResponseHopLimit`, `HttpEndpoint`, `InstanceMetadataTags`) |
| Account default, per Region | `aws ec2 modify-instance-metadata-defaults --http-tokens required --http-put-response-hop-limit 2` |
| Enforce, per Region | `aws ec2 modify-instance-metadata-defaults --http-tokens-enforced enabled`: launches that would allow IMDSv1 are refused |

- Account defaults apply to **new** launches in that Region and do not change running instances.
- A setting in the AMI or launch template, or on the launch request, overrides the account default (UNVERIFIED precedence order).
- The SDK field is `HttpTokensEnforced` (`disabled`, `enabled`, `no-preference`) on `ModifyInstanceMetadataDefaultsCommand`, confirmed against @aws-sdk/client-ec2's types.

## Symptoms

| Symptom | Likely cause | Check |
|---|---|---|
| SDK on the instance: `Could not load credentials from any providers` | No instance profile attached, IMDS disabled (`HttpEndpoint=disabled`), or a container blocked by hop limit 1 (the message is verbatim from the AWS SDK for JavaScript v3 source) | `aws ec2 describe-iam-instance-profile-associations --filters Name=instance-id,Values=<i>`; `aws ec2 describe-instances --instance-ids <i> --query 'Reservations[0].Instances[0].MetadataOptions'` |
| `curl 169.254.169.254/latest/meta-data/` returns 401 | IMDSv2 is required and the request had no token: use the token flow above | Same `MetadataOptions` → `HttpTokens: required` |
| Credentials for the wrong role | Environment variables or `~/.aws` on the instance take precedence over the instance profile | On the instance: `env | grep AWS_`, `ls ~/.aws`, then `aws sts get-caller-identity` |
| Launch refused after adding a profile | The launcher lacks `iam:PassRole`: decode the message | `aws sts decode-authorization-message --encoded-message <msg>` |

## The instance identity role (not yours to configure)

Every instance also has an **instance identity role**, `arn:aws:iam::<account>:assumed-role/aws:ec2-instance/<instance-id>`, served at `/identity-credentials/ec2/security-credentials/ec2-instance`. It has no trust policy, is not subject to identity or resource policies, and works only with the services integrated with it: EC2 Instance Connect, Systems Manager Default Host Management Configuration, GuardDuty runtime monitoring, Lambda Managed Instances, and STS `GetCallerIdentity`. Seeing it in CloudTrail is normal; it isn't the instance-profile role.

## Scope note

ECS tasks and Lambda functions do **not** use IMDS for their roles. They get credentials from their own task or execution role (`reference/ecs/roles-and-networking.md`, `reference/lambda/permissions.md`).


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
