Retrieving EC2 Instance ID via Instance Metadata Service (IMDSv2)

Retrieving your EC2 instance ID from within a running script is a foundational task — but using the wrong metadata approach exposes your workload to SSRF-based credential theft.

TL;DR: IMDSv1 vs IMDSv2 for Retrieving Instance ID

ApproachMechanismBest ForComplexity
IMDSv1 (legacy)Single unauthenticated GETIsolated dev environments onlyLow
IMDSv2 (recommended)Session-oriented PUT + GET with tokenAll production workloadsLow-Medium

Why IMDSv2 Is Safer for EC2 Instance Metadata Retrieval

IMDSv2 requires a session token obtained via an HTTP PUT request before any metadata can be read. This session-oriented design defeats the most common SSRF attack vector: a forged GET request from a compromised application that silently reads IAM credentials or the instance ID from 169.254.169.254. IMDSv1 has no such gate — any process or proxied request reaching that IP gets a response. IMDSv2 enforces a two-step handshake that SSRF payloads cannot complete without controlling the PUT verb and the TTL header.

Think of IMDSv1 as a door with no lock — anyone who reaches it walks in. IMDSv2 adds a deadbolt that requires a specific knock sequence only the instance itself can perform reliably.

How the IMDSv2 Token Flow Works

The sequence below shows the two-leg exchange your script must complete before reading any metadata path, including instance-id.

sequenceDiagram participant Script participant IMDS as IMDS 169.254.169.254 Script->>IMDS: PUT /latest/api/token TTL=21600 IMDS-->>Script: 200 OK body=TOKEN Script->>IMDS: GET /latest/meta-data/instance-id token=TOKEN IMDS-->>Script: 200 OK body=i-0abcdef1234567890
  1. PUT /latest/api/token — The instance sends a PUT with a TTL header. The IMDS endpoint returns a session token in the response body.
  2. GET /latest/meta-data/instance-id — The instance attaches the token as a request header. IMDS validates the token and returns the instance ID string.
  3. If the PUT is blocked (e.g., HttpEndpoint disabled or hop-limit exceeded), the token request fails and no metadata is accessible.
The hop-limit is the silent enforcer: IMDSv2 defaults to a TTL of 2 on the link-local packet, so a request proxied through even one additional network hop is dropped before it reaches the endpoint.

Step 1: Verify IMDSv2 Is Enforced on Your Instance

Before writing any script, confirm the instance is configured to require IMDSv2. An instance left in optional mode still accepts IMDSv1 requests, which negates the security benefit.

— Why this step: if HttpTokens is still set to optional, your hardened script works correctly but an attacker's IMDSv1 request also works — the enforcement gap is invisible at the script level.

aws ec2 describe-instances \
  --instance-ids i-0abcdef1234567890 \
  --query 'Reservations[*].Instances[*].MetadataOptions' \
  --output table \
  --region us-east-1

Look for HttpTokens: required and HttpEndpoint: enabled in the output. If HttpTokens shows optional, enforce IMDSv2 with:

aws ec2 modify-instance-metadata-options \
  --instance-id i-0abcdef1234567890 \
  --http-tokens required \
  --http-endpoint enabled \
  --region us-east-1
Enforcing required at the instance level is a hard gate — it cannot be overridden by the script or the application running on it.

Step 2: Retrieve the Instance ID Using IMDSv2 (Shell)

The two-step pattern below is the AWS-documented approach. The token is returned in the response body of the PUT, so reading stdout directly is both correct and sufficient.

— Why this step: the token lives in the response body, not in a header — scripts that try to parse headers for the token will silently receive an empty value and fail on the subsequent GET.

Show: Production-hardened shell snippet
#!/bin/bash
set -euo pipefail

# Step 1: Obtain IMDSv2 session token (TTL = 6 hours)
TOKEN=$(curl -sf -X PUT \
  "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") \
  || { echo "ERROR: Failed to obtain IMDSv2 token. Verify HttpEndpoint is enabled."; exit 1; }

# Step 2: Use token to retrieve instance ID
INSTANCE_ID=$(curl -sf \
  "http://169.254.169.254/latest/meta-data/instance-id" \
  -H "X-aws-ec2-metadata-token: ${TOKEN}") \
  || { echo "ERROR: Failed to retrieve instance-id."; exit 1; }

echo "Instance ID: ${INSTANCE_ID}"

Key flags:

  • -s — silent mode, suppresses progress output
  • -f — fail on HTTP error codes (4xx/5xx), enabling the || { ... } error trap
  • -X PUT — required for the token endpoint; a GET to that path returns a 405
  • X-aws-ec2-metadata-token-ttl-seconds — mandatory header; omitting it returns a 400

Step 3: Retrieve the Instance ID Using IMDSv2 (Python)

For application code, the requests library maps cleanly to the two-step flow. The pattern below is safe for Lambda-style initialization blocks and long-running daemon processes alike.

— Why this step: Python scripts that use boto3 to call describe-instances for self-identification make an outbound API call requiring IAM permissions and network egress — the metadata endpoint requires neither.

Show: Python IMDSv2 snippet
import requests
import sys

IMDS_BASE = "http://169.254.169.254/latest"
TOKEN_TTL = "21600"

def get_imdsv2_token() -> str:
    resp = requests.put(
        f"{IMDS_BASE}/api/token",
        headers={"X-aws-ec2-metadata-token-ttl-seconds": TOKEN_TTL},
        timeout=2,
    )
    resp.raise_for_status()
    return resp.text

def get_instance_id(token: str) -> str:
    resp = requests.get(
        f"{IMDS_BASE}/meta-data/instance-id",
        headers={"X-aws-ec2-metadata-token": token},
        timeout=2,
    )
    resp.raise_for_status()
    return resp.text

if __name__ == "__main__":
    try:
        token = get_imdsv2_token()
        instance_id = get_instance_id(token)
        print(f"Instance ID: {instance_id}")
    except requests.exceptions.RequestException as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        sys.exit(1)

Set timeout=2 on both calls. The IMDS endpoint is link-local and responds in milliseconds on a healthy instance — a hung request almost always means the endpoint is disabled or the hop-limit is misconfigured, not a transient network blip.

Step 4: Confirm the Hop-Limit Is Not Blocking Container Workloads

Container workloads add a network hop between the container and the host. If the http-put-response-hop-limit is set to 1 (the default for instances launched before the IMDSv2 enforcement rollout), the PUT response never reaches the container and the token request times out silently.

— Why this step: the symptom — a curl timeout on the PUT — looks identical to a disabled endpoint, so engineers fix the wrong thing first and lose time.

aws ec2 describe-instances \
  --instance-ids i-0abcdef1234567890 \
  --query 'Reservations[*].Instances[*].MetadataOptions.HttpPutResponseHopLimit' \
  --output text \
  --region us-east-1

If the output is 1 and your workload runs inside a container, increase it to 2:

aws ec2 modify-instance-metadata-options \
  --instance-id i-0abcdef1234567890 \
  --http-put-response-hop-limit 2 \
  --http-tokens required \
  --http-endpoint enabled \
  --region us-east-1
A hop-limit of 2 covers the container-to-host boundary without opening the endpoint to requests proxied further up the stack.

Required IAM Permissions for Metadata Option Management

Reading and modifying instance metadata options requires EC2 API permissions, not IMDS permissions. The IMDS endpoint itself requires no IAM — it is accessible to any process on the instance. The IAM policy below covers the diagnostic and enforcement steps in this post.

Show: IAM policy for metadata option management
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DescribeInstanceMetadataOptions",
      "Effect": "Allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    },
    {
      "Sid": "EnforceIMDSv2",
      "Effect": "Allow",
      "Action": "ec2:ModifyInstanceMetadataOptions",
      "Resource": "arn:aws:ec2:us-east-1:123456789012:instance/i-0abcdef1234567890"
    }
  ]
}

ec2:DescribeInstances does not support resource-level restrictions and requires "Resource": "*". ec2:ModifyInstanceMetadataOptions supports instance-level ARN restrictions and should be scoped accordingly.

Troubleshooting IMDSv2 Token Failures

graph LR A[PUT to token endpoint] --> B{Response?} B -->|400| C[Missing TTL header] B -->|Timeout| D{Endpoint enabled?} D -->|No| E[Enable HttpEndpoint] D -->|Yes| F[Check hop-limit] B -->|200 token| G[GET instance-id] G --> H{Response?} H -->|401| I[Token expired or wrong header] H -->|404| J[Check metadata path] H -->|200| K[Instance ID retrieved]
  1. PUT returns 400 — The X-aws-ec2-metadata-token-ttl-seconds header is missing or malformed. Verify the header name and that the value is a positive integer.
  2. PUT times out — Either HttpEndpoint is disabled, or the hop-limit is too low for the network path (common in containers). Run Step 1 and Step 4 diagnostics above.
  3. GET returns 401 — The token has expired or was not passed correctly. Re-run the PUT to obtain a fresh token and verify the header name on the GET is X-aws-ec2-metadata-token.
  4. GET returns 404 — The metadata path is incorrect. The canonical path for instance ID is /latest/meta-data/instance-id — verify there is no trailing slash or typo.

Glossary

IMDS (Instance Metadata Service)
A link-local HTTP endpoint at 169.254.169.254 that provides instance-specific data to processes running on an EC2 instance without requiring IAM credentials or network egress.
IMDSv2
Session-oriented version of IMDS introduced in 2019. Requires a PUT-based token exchange before metadata can be read, mitigating SSRF-based credential theft.
HttpTokens
Instance metadata option controlling whether IMDSv2 is required or optional. Setting this to required disables IMDSv1 access entirely.
HttpPutResponseHopLimit
The maximum number of network hops the IMDSv2 PUT response packet can traverse. Default is 1; container workloads typically require 2.
SSRF (Server-Side Request Forgery)
An attack where a compromised application is tricked into making HTTP requests to internal endpoints — such as the IMDS — on behalf of an attacker.
Link-local address
The 169.254.0.0/16 address range, non-routable beyond the local network segment. The IMDS endpoint uses 169.254.169.254 within this range.

Retrieving EC2 Instance ID Safely: Final Checklist

Using IMDSv2 to retrieve your EC2 instance ID is a two-line change that closes a well-documented attack surface. Enforce HttpTokens: required at the instance level, set the hop-limit correctly for your runtime environment, and scope the IAM policy for metadata option management to specific instance ARNs. For deeper context on securing EC2 workloads, see the AWS documentation on Configuring the Instance Metadata Service and the EC2 Security Best Practices guide.

Related Posts

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Fix Internet Gateway and Route Table

EC2 SSH Connection Timeout: Which Security Group Rules to Check

Difference Between IAM User and IAM Role: Which One Should Your EC2 Use?