Two related mechanisms let an instance configure itself and learn about itself. User data is a script you hand the instance at launch, run once on first boot. Instance metadata is a local service the running instance queries to discover its own id, region, IAM credentials, and more.
Both are central to automation: user data bootstraps the machine, and metadata is how code on the instance answers "who and where am I?" without any hardcoding. This page covers passing user data via the SDK and reading metadata through the modern IMDSv2 token flow.
You supply user data when you launch, in the UserData field of RunInstances. A shell script begins with a shebang; a cloud-config document begins with #cloud-config. There is one important SDK difference: boto3 base64-encodes the string for you, but SDK v3 does not - you must encode it yourself.
User data runs as root during the first boot, before the instance is usable. Keep it small and idempotent; for anything substantial, have it pull a versioned script or configuration rather than embedding the whole thing.
Once the instance is running, code on it reads facts from the Instance Metadata Service (IMDS) at the link-local address 169.254.169.254. Modern instances enforce IMDSv2, which requires a session token: you PUT to get a short-lived token, then send it as a header on each GET.
Note that reading metadata does not use an AWS SDK client - it is a plain HTTP call to a local address, because the code is asking the platform, not an AWS API endpoint. The SDK's own credential provider uses this same path under the hood to fetch role credentials.
The metadata tree exposes many facts about the instance. The ones you reach for most:
Path
Returns
instance-id
The i-... id
instance-type
e.g. t3.micro
placement/availability-zone
e.g. us-east-1a
placement/region
e.g. us-east-1
local-ipv4
Private IP
public-ipv4
Public IP (if any)
iam/security-credentials/<role>
Temporary role credentials
mac and network/interfaces/...
Networking details
There is also a sibling ../user-data endpoint (not under meta-data) that returns the raw user data you passed, handy for a boot script that wants to re-read its own configuration.
IMDSv1 was a simple unauthenticated GET. That made it a target: a server-side request forgery (SSRF) bug in an application could be tricked into fetching iam/security-credentials/... and leaking the instance's role credentials. IMDSv2's token requirement closes that hole, because an SSRF attacker generally cannot make the required PUT with a custom header, and the token is bound with a hop limit that stops it leaving the instance.
You should require IMDSv2, not just prefer it. Set MetadataOptions.HttpTokens to required at launch, or change it on a running instance with ModifyInstanceMetadataOptions. Setting HttpPutResponseHopLimit to 1 keeps the token from being routed beyond the instance itself.
Put the same MetadataOptions in your launch template so every instance and every ASG launch enforces IMDSv2 from birth, rather than fixing instances after the fact.
Forgetting to base64-encode in SDK v3. boto3 encodes UserData for you; SDK v3 sends it verbatim, so an un-encoded string is rejected or misinterpreted.
Expecting user data to re-run. By default cloud-init runs user data only on the first boot. A stop/start does not re-run it unless configured to.
Using a plain GET against IMDSv2. When HttpTokens is required, a tokenless GET returns 401. Always do the PUT token step first.
Leaving IMDSv1 enabled.HttpTokens: "optional" still allows the insecure v1 path. Set it to required to actually close the SSRF risk.
Putting secrets in user data. User data is readable from within the instance and via DescribeInstanceAttribute. Fetch secrets from Secrets Manager or SSM at boot instead.
A too-short token TTL. A one-second TTL forces a new token per call; use a longer TTL (up to six hours) and cache the token.
Launch templates to store UserData and MetadataOptions once so every launch is consistent, rather than passing them on each RunInstances call.
SSM Agent / Run Command to push configuration to running instances without baking it into boot-time user data.
The ec2-metadata CLI or SDK credential provider when you only need role credentials - the SDK already reads them from IMDS for you, so you rarely fetch them by hand.
Configuration management (Ansible, cloud-init modules) for complex bootstrapping that outgrows a shell script.
Use user data for lightweight first-boot setup; move heavier or ongoing configuration to SSM or a config-management tool.
A script or cloud-config document you pass at launch. Cloud-init runs it once on first boot, as root, to bootstrap the instance - install packages, write config, start services.
Do I need to base64-encode user data?
In boto3, no - it encodes the string for you. In SDK v3, yes - you must base64-encode UserData yourself before passing it.
Does user data run every boot?
By default only on the first boot. You can configure cloud-init to run it on every boot, but a plain stop/start will not re-run it otherwise.
What is instance metadata?
A local service at 169.254.169.254 that tells code on the instance about itself - its id, type, region, networking, and IAM role credentials - without any hardcoding.
What is the IMDSv2 token flow?
You PUT to /latest/api/token with a TTL header to get a short-lived token, then include it as X-aws-ec2-metadata-token on each metadata GET.
Why does IMDSv2 exist?
To defend against SSRF attacks that abused the unauthenticated IMDSv1 to steal role credentials. The required token PUT and hop limit make that much harder.
How do I require IMDSv2?
Set MetadataOptions.HttpTokens to required at launch or with ModifyInstanceMetadataOptions, ideally with HttpPutResponseHopLimit of 1.
How do I read the instance's own id in code?
Fetch a token, then GET /latest/meta-data/instance-id with the token header. It is a plain HTTP call, not an AWS SDK operation.
Can I read the IAM role credentials from metadata?
Yes, at iam/security-credentials/<role-name>, but you rarely need to - the SDK's credential provider reads them for you automatically.
Should I store secrets in user data?
No. User data is readable from the instance and via DescribeInstanceAttribute. Pull secrets from AWS Secrets Manager or SSM Parameter Store at boot instead.