CRITICAL

AWS IAM: Fix AccessDeniedException for EC2 Instance Profile during High Traffic Scaling

Quick Fix Summary

TL;DR

Immediately attach a known-good IAM role with sufficient permissions to the affected EC2 instance.

An AccessDeniedException for an EC2 instance profile during high traffic scaling indicates the IAM role attached to the instance lacks the necessary permissions for the actions being attempted, often under high concurrency. This can be due to misconfigured policies, service-linked role issues, or IAM propagation delays.

Diagnosis & Causes

  • IAM role permissions are insufficient for the specific API calls made by the application.
  • The IAM role's trust policy does not allow the EC2 service to assume it, or the instance profile attachment failed.
  • Recovery Steps

    1

    Step 1: Verify Instance Profile Attachment and Permissions

    Confirm the IAM role is correctly attached to the EC2 instance and simulate the failing API call to identify the missing permission.

    bash
    # Get the instance profile attached to the affected EC2 instance.
    INSTANCE_ID="i-1234567890abcdef0"
    aws ec2 describe-instances --instance-ids $INSTANCE_ID --query 'Reservations[0].Instances[0].IamInstanceProfile.Arn' --output text
    
    # Simulate the API call that is failing using the IAM policy simulator.
    ROLE_NAME="YourInstanceRoleName"
    ACTION="s3:GetObject"
    RESOURCE_ARN="arn:aws:s3:::your-bucket/your-key"
    aws iam simulate-principal-policy --policy-source-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/$ROLE_NAME --action-names $ACTION --resource-arns $RESOURCE_ARN
    2

    Step 2: Check and Attach Correct IAM Policy

    Identify the required permissions and attach a policy granting them to the instance role. Use managed policies for known services.

    bash
    # List policies attached to the instance role.
    ROLE_NAME="YourInstanceRoleName"
    aws iam list-attached-role-policies --role-name $ROLE_NAME
    
    # Attach a managed policy (e.g., for Amazon S3 read-only access).
    aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
    
    # For custom permissions, create and attach an inline policy.
    cat > /tmp/inline-policy.json << EOF
    {
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket/*"
        }]
    }
    EOF
    aws iam put-role-policy --role-name $ROLE_NAME --policy-name S3GetObjectFix --policy-document file:///tmp/inline-policy.json
    3

    Step 3: Validate IAM Role Trust Relationship

    Ensure the IAM role's trust policy allows the EC2 service to assume it. This is critical for the instance profile mechanism.

    bash
    # View the trust relationship policy document of the role.
    ROLE_NAME="YourInstanceRoleName"
    aws iam get-role --role-name $ROLE_NAME --query 'Role.AssumeRolePolicyDocument'
    
    # The trust relationship MUST include the EC2 service principal. Example correct policy:
    cat > /tmp/trust-policy.json << EOF
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": { "Service": "ec2.amazonaws.com" },
          "Action": "sts:AssumeRole"
        }
      ]
    }
    EOF
    # Update the role's trust policy if incorrect.
    aws iam update-assume-role-policy --role-name $ROLE_NAME --policy-document file:///tmp/trust-policy.json
    4

    Step 4: Reattach Instance Profile and Reboot EC2

    If the role is correct but the instance cannot assume it, reattach the instance profile and reboot to force metadata service refresh.

    bash
    # Disassociate the current instance profile.
    INSTANCE_ID="i-1234567890abcdef0"
    aws ec2 disassociate-iam-instance-profile --association-id $(aws ec2 describe-iam-instance-profile-associations --filters "Name=instance-id,Values=$INSTANCE_ID" --query 'IamInstanceProfileAssociations[0].AssociationId' --output text)
    
    # Associate the correct instance profile.
    INSTANCE_PROFILE_NAME="YourInstanceProfileName"
    aws ec2 associate-iam-instance-profile --instance-id $INSTANCE_ID --iam-instance-profile Name=$INSTANCE_PROFILE_NAME
    
    # Reboot the instance to refresh the IMDS credentials.
    aws ec2 reboot-instances --instance-ids $INSTANCE_ID
    5

    Step 5: Check for IAM Throttling and Service Quotas

    During high traffic scaling, IAM or STS request rate limits may be hit. Check CloudWatch metrics and consider request batching.

    bash
    # Check CloudWatch for IAM or STS throttling errors (approximate command).
    aws cloudwatch get-metric-statistics --namespace AWS/IAM --metric-name RequestCount --dimensions Name=Resource,Value=RoleName --start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) --period 300 --statistics Sum
    
    # Check your IAM role quota.
    aws iam get-account-summary --query 'SummaryMap[Roles]'
    
    # Implement exponential backoff and retry logic in application code to handle throttling.

    Architect's Pro Tip

    "This often happens when a new, scaled-out instance launches with an outdated launch template or Auto Scaling group configuration that references an IAM role with insufficient permissions. Always update your launch template *first* before modifying the live instance role."

    Frequently Asked Questions

    The IAM policy looks correct. Why am I still getting AccessDenied?

    IAM policy changes can take several seconds to propagate across AWS regions. Under high concurrency, a new instance might assume the role before propagation is complete. Implement a short delay in your application startup or user-data script after instance launch.

    Can IAM permissions cause issues only during high traffic?

    Yes. At low request rates, you might stay under IAM's request throttling limits. High traffic can trigger throttling (HTTP 429), which some SDKs may misinterpret or surface as AccessDenied. Additionally, missing permissions in rarely-used code paths are only exposed under specific high-load operations.

    Related AWS Guides