Set Up IAM Roles for EC2
Set up IAM roles for EC2 — Cloud security essentials.
Focus: set up iam roles for ec2
Ever found yourself hardcoding an AWS access key into a config file, only to see it surface in a git commit and trigger a panic-inducing security alert? If you've been there, you already know the pain: leaked credentials, revoked keys, and the dreaded scramble to rotate secrets across every environment. The good news is that AWS gives you a far better way — IAM roles for EC2 — which lets your instances borrow temporary permissions without ever holding a long-lived secret. In this lesson, you'll learn how to set up IAM roles for EC2, why they're the single most important security upgrade for any EC2 workload, and how to apply them in under ten minutes. By the end, you'll be ready to move to the next lesson in the Cloud security essentials track.
The problem this lesson solves
Every time you ssh into an EC2 instance, there's a good chance you've got a file somewhere containing your AWS keys — maybe in ~/.aws/credentials, maybe in a .env file, or worse, in a public repo. Those static credentials are a ticking time bomb. They never expire (unless you manually rotate them), they're shared across every environment, and if one leaks, an attacker can impersonate you with full access to your account.
But the real problem isn't just the leak — it's the blast radius. With static keys, there's no way to scope permissions to a particular instance. If your web server needs to read from S3, you probably gave it s3:* access, and now that same key can also delete your database backups. That's the opposite of least privilege.
Here's the punchline: IAM roles for EC2 eliminate the need to store credentials on the instance entirely. Instead of embedding a secret, you attach a role to the instance at launch, and the AWS credentials system dynamically issues short-lived credentials to the instance. Those credentials automatically rotate, they're scoped to the role's permissions, and they're never written to disk. The problem of "where do I put my keys?" disappears.
Still not convinced? Think about the lifecycle: when you use static keys and an instance is terminated, the key still exists. An attacker who stole it can keep using it for years. With a role, the credentials vanish the moment the instance stops — there's nothing left to steal.
Core concept / mental model
Let's build a mental model that makes roles stick. Imagine a valet key for your car. A regular key unlocks everything — doors, trunk, glovebox. A valet key only unlocks the door and starts the engine, and it stops working after the parking session ends. That's exactly what an IAM role is: a temporary, scoped set of permissions that an EC2 instance can request.
Here's the full picture in a few layers:
- IAM role — a container for permissions, defined by a trust policy (who can assume it) and a permissions policy (what actions are allowed).
- Instance profile — a wrapper that attaches the role to an EC2 instance. You create the profile, add the role to it, and then specify the profile at launch.
- EC2 metadata service — a magic IP (
169.254.169.254) that the instance queries to fetch temporary credentials. The instance's SDK or CLI does this automatically when you use a role.
Here's the flow in words:
- You create an IAM role and attach it to an instance profile.
- You launch an EC2 instance and assign the profile.
- The instance calls the metadata service, which contacts STS (Security Token Service) on your behalf.
- STS returns temporary credentials (access key, secret key, session token) that are valid for a short window (usually 1–6 hours).
- The AWS CLI or SDK on the instance automatically refreshes those credentials before they expire.
The beauty is that you never touch a key. The instance proves its identity by being a live, running AWS resource — AWS trusts it because it's already in your account.
How it works step by step
Now let's get concrete. Setting up an IAM role for EC2 involves seven steps, and each one builds on the last:
Step 1: Plan the permissions
Before you create anything, ask: What does this instance actually need to do? Write down the exact actions and resources. For example, a web app that reads images from an S3 bucket only needs s3:GetObject on that specific bucket — not s3:*.
Step 2: Create the IAM role
Go to the IAM console → Roles → Create role. Select AWS service → EC2 as the trusted entity. This automatically sets the trust policy to allow EC2 to assume the role.
Step 3: Attach a permissions policy
Choose a managed policy (like AmazonS3ReadOnlyAccess) or write a custom one. A custom policy gives you precise control:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-images",
"arn:aws:s3:::my-app-images/*"
]
}
]
}
Step 4: Create the instance profile (usually automatic)
When you create the role in the console, AWS also creates an instance profile with the same name. If you're using the CLI, you may need to create the profile and add the role manually.
Step 5: Launch the EC2 instance with the profile
In the EC2 launch wizard, under Advanced details, select IAM instance profile and pick your profile. If you're using the CLI, you'll pass the profile name at launch.
Step 6: Test from the instance
SSH in, run aws sts get-caller-identity, and confirm you're using the role's temporary credentials.
Step 7: Clean up
If you were testing with static keys, remove them from ~/.aws/credentials and from any config files. The instance no longer needs them.
Hands-on walkthrough
The fastest way to learn is to do it. We'll set up an EC2 instance that can list its own bucket — a classic starter task.
Create the role and policy with the AWS CLI
# Step 1: Create a trust policy file for EC2
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# Step 2: Create the IAM role
aws iam create-role \
--role-name s3-reader-role \
--assume-role-policy-document file://trust-policy.json
# Step 3: Create a permissions policy file
cat > s3-reader-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-images",
"arn:aws:s3:::my-app-images/*"
]
}
]
}
EOF
# Step 4: Attach the policy to the role
aws iam put-role-policy \
--role-name s3-reader-role \
--policy-name s3-reader-policy \
--policy-document file://s3-reader-policy.json
# Step 5: Create the instance profile and add the role
aws iam create-instance-profile --instance-profile-name s3-reader-profile
aws iam add-role-to-instance-profile \
--instance-profile-name s3-reader-profile \
--role-name s3-reader-role
Launch an instance with the profile
# Launch a basic t2.micro instance with the profile
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t2.micro \
--iam-instance-profile Name=s3-reader-profile \
--key-name my-keypair
In the AWS Console, fill in the same options: launch an instance, expand Advanced details, and pick s3-reader-profile from the IAM instance profile dropdown.
Test from the instance
# SSH into the instance, then check identity
aws sts get-caller-identity
# Output:
# {
# "UserId": "AROA...":"i-0abc123",
# "Account": "123456789012",
# "Arn": "arn:aws:sts::123456789012:assumed-role/s3-reader-role/i-0abc123"
# }
# List your bucket
aws s3 ls s3://my-app-images
If you see the role's ARN (not a user's), you've succeeded. Now try a forbidden action to prove the scoping works:
# This should fail with AccessDenied
aws s3 rm s3://my-app-images/sensitive.txt
You'll get an AccessDenied error — exactly what you want. That's least privilege in action.
Pro tip: The metadata service is only accessible from within the instance. Never expose it publicly; always use the IMDSv2 token-based endpoint for extra security.
Compare options / when to choose what
Below is a quick comparison of the three common ways to handle EC2 credentials:
| Approach | Where are credentials stored? | Lifetime | Security | Use when |
|---|---|---|---|---|
| IAM role | Nowhere (fetched dynamically) | Short-lived, auto-rotated | Best | Any EC2 workload, especially production |
| Static IAM user keys | In instance files/environment | Long-lived until you revoke | Risky — leak hazard | Legacy/testing only |
| Stored secrets manager (e.g., AWS Secrets Manager) | In a secret store, fetched at runtime | Configurable | Good, but adds complexity | When you need non-AWS secrets too |
The trade-off is clear: roles are the default, and static keys should be the exception. If you need to give an instance access to resources outside AWS, a different pattern (like Secrets Manager) might be worth it, but for any AWS service interaction, roles are the go-to. In other words: when in doubt, use an IAM role.
Troubleshooting & edge cases
Even with roles, things can go sideways. Here are the most common hiccups and how to fix them:
An error occurred (AccessDenied) when calling the AssumeRole operation— Your instance's trust policy doesn't allow EC2, or you're trying to assume a role that's not attached. Verify the trust policy hasec2.amazonaws.comas the principal.- Credentials still appear in
~/.aws/credentials— The CLI looks for static keys first. If you have a profile configured, the role won't be used. Remove the old credentials file and set the environment variableAWS_EC2_METADATA_DISABLED=false. - Instance can't reach the metadata service — If you're using a VPC with no internet gateway, metadata (at
169.254.169.254) still works, but you can't reach it if you've disabled network access. Ensure the metadata service is enabled on the instance (default yes). - Role policy uses wildcards — You may have given
s3:*accidentally. Go back and narrow theActionandResourceto the exact need. - IAM instance profile not found at launch — You must create the profile before launching the instance, and the name must match exactly.
- Conflicting permissions with a user — If your instance uses a role, it ignores any IAM user credentials you may have configured. That's actually a feature, not a bug.
What you learned & what's next
You've now mastered the core of set up IAM roles for EC2: you know the pain of static keys, you understand the valet-key mental model, and you can create, attach, and test a role in minutes. You've also seen how to compare roles against alternatives and how to debug the most common issues.
To cement this knowledge, try this mini-exercise: take an existing EC2 instance that uses static keys, and migrate it to a role. Delete the credentials file, attach a role with least-privilege permissions, and verify that your app still works. Then, remove the static user from your AWS account entirely.
In the next lesson of the Cloud security essentials track, you'll build on these dynamic credentials and explore how to monitor and audit their usage — because even roles need oversight. Get ready to dive into CloudTrail and IAM Access Analyzer.
Practice recap
Now that you've set up your first IAM role, try this: launch a new EC2 instance, attach the role, and verify you can read from your bucket while write operations fail. Then, remove any old static credentials from your environment and confirm your app still works. This hands-on exercise will solidify the concept and prepare you for the next lesson on auditing role usage.
Common mistakes
- Creating an IAM role but forgetting to attach it to an instance profile — the instance silently falls back to no credentials or static keys.
- Leaving
~/.aws/credentialson the instance after setting up a role — the CLI uses static keys if present, so your role is ignored. - Using overly broad permissions like
s3:*instead of scoping to specific actions and resources, defeating the purpose of least privilege. - Launching the instance before creating the instance profile, which results in an 'Invalid IAM Instance Profile name' error.
Variations
- Use AWS Systems Manager (SSM) to attach a role to an existing instance without relaunching.
- Create the role and instance profile using Infrastructure as Code (e.g., Terraform) for repeatable, version-controlled security.
- Use a custom trust policy to allow instance roles from other accounts (cross-account roles) for advanced scenarios.
Real-world use cases
- A web application on EC2 reads user-uploaded images from an S3 bucket using a read-only role, with no keys stored on the instance.
- A batch processing server accesses a DynamoDB table to update job statuses via a role with least-privilege permissions.
- A CI/CD runner on EC2 needs to publish artifacts to an S3 deployment bucket, using a role with
s3:PutObjectonly during the build.
Key takeaways
- IAM roles for EC2 provide short-lived, automatically rotating credentials, removing the need for static access keys on instances.
- A role consists of a trust policy (who can assume it) and a permissions policy (what actions are allowed).
- The EC2 metadata service at 169.254.169.254 is how the instance transparently gets temporary credentials.
- Always scope permissions to the exact actions and resources the instance needs — least privilege is non-negotiable.
- An instance profile is the wrapper that binds a role to an EC2 instance at launch.
- When troubleshooting, check the trust policy, the instance profile attachment, and any lingering static credentials.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.