How to Fix Terraform Permission Errors in AWS
When Terraform fails with an AWS permission error, it is always an IAM authorization failure. But Terraform adds failure points that generic AWS guides don’t cover: backend and state access, tagging calls, iam:PassRole, assume-role flows, and partially applied resources. Fixing it quickly means knowing which layer failed, not just which action was denied.
This page gives you a fast checklist, a breakdown of each failure pattern, and the specific fix for each one.
Simple explanation
Think of Terraform like a general contractor building a house. Getting a building permit doesn’t mean you can do the electrical work. Each trade needs its own permit. If the electrician shows up without one, the job stops at that step, even though the foundation is already poured.
Terraform works the same way. A single terraform apply makes dozens of AWS API calls across multiple services:
sts:AssumeRoleto authenticates3:GetObjectands3:PutObjectto read and write stateec2:CreateVpcto create the resourceec2:CreateTagsto apply tagsec2:DescribeVpcsto read the resource back into state
Each call needs its own IAM permission. A single missing permission breaks the step it covers, even if every earlier step succeeded. That is why you can see a resource created in the AWS console but Terraform still reports an error: creation worked, tagging failed.
Quick diagnosis checklist
Run through this before debugging the policy:
- Who is Terraform authenticating as? Run
aws sts get-caller-identityin the same terminal Terraform runs in. - Did the error happen during
terraform initor duringplan/apply? Init failures are almost always backend or state access. Apply failures are resource or IAM action gaps. - What is the exact denied action, resource, and principal? Read the error message carefully. These three values tell you exactly what to fix.
- Is this a tagging failure? If the resource was created but the error mentions tagging, add the missing tag action.
- Is
iam:PassRolein the error? This is a specific pattern when Terraform attaches a role to Lambda, ECS, EC2, or a similar resource. - Are SCPs, permissions boundaries, or session policies involved? An explicit deny from a Service Control Policy overrides any allow in the identity policy.
- Is this a trust policy problem? A role’s trust policy controls who can assume it. A role’s permission policy controls what the assumed role can do. These are separate.
- Did a previous apply fail partway? Run
terraform planbefore re-runningterraform applyto see what Terraform thinks needs to happen next.
How Terraform permission errors work in AWS
AWS enforces two concepts that beginners often mix up:
- Authentication: proving who you are (access keys, assumed role, instance profile)
- Authorization: whether the authenticated identity is allowed to perform the requested action
A good analogy: authentication is your ID card that gets you into the building. Authorization is the key card that unlocks specific rooms. You can be a real employee (authenticated) but still be blocked from the server room (not authorized).
Terraform permission errors are always authorization failures. But they can happen at any point in the Terraform workflow, and the root cause depends on where they occur.
There are two distinct categories:
Backend and state access errors
These happen during terraform init or at the start of terraform plan/apply when Terraform reads or writes the remote state file.
For an S3 backend, Terraform needs:
s3:ListBucketto list workspaces and check the buckets3:GetObjectto read the current states3:PutObjectto write updated state
For state locking with S3 native locking (use_lockfile = true), Terraform stores a .tflock file in S3 and also needs s3:DeleteObject to release the lock. DynamoDB-based locking is deprecated in newer Terraform versions, but older setups may still use it. Those need dynamodb:DescribeTable, dynamodb:GetItem, dynamodb:PutItem, and dynamodb:DeleteItem on the lock table.
See Terraform State Management for full backend configuration details.
Resource and apply errors
These happen during terraform plan or terraform apply when Terraform calls the AWS APIs for your actual infrastructure. Each resource lifecycle step (create, tag, read-back, update, destroy) may require a different IAM action. Missing any one of them causes Terraform to fail at that step.
Step 1: Confirm which AWS identity Terraform is using
This is the single most common source of confusion in Terraform permission errors.
aws sts get-caller-identityThe output shows Account, UserId, and Arn. Check all three:
- Account: is this the right AWS account? Not a dev account when deploying to prod?
- ARN: is this the IAM user or role intended for Terraform?
- Role/user: does this identity have the Terraform IAM policy attached?
Terraform uses the AWS credential chain in this order:
- Environment variables:
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY AWS_PROFILEenvironment variable- Default profile in
~/.aws/credentials - EC2 instance metadata or ECS task role (when running on AWS)
If you have multiple profiles, Terraform uses the default profile unless overridden. Check the provider block:
provider "aws" {
region = "us-east-1"
profile = "terraform-deployer"
}For assume-role workflows, Terraform can assume a role directly in the provider block:
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::111122223333:role/terraform-deployer"
}
}When this is configured, the trust policy of terraform-deployer must allow the calling identity to assume it. If the trust policy is wrong, Terraform fails here with an sts:AssumeRole error before doing anything else.
STS temporary credentials and role assumption are the recommended patterns for CI/CD and short-lived Terraform access.
Before every production apply: Run aws sts get-caller-identity and confirm the account ID and role ARN match what you expect. A leftover AWS_PROFILE from an earlier terminal session can silently point Terraform at the wrong account.
Step 2: Classify the error — backend, state, or resource
Error patterns differ by layer:
S3 backend access denied
Error: Failed to get existing workspaces: AccessDenied: Access Denied
status code: 403Happens during terraform init or at the start of a plan/apply. The Terraform identity is missing S3 permissions on the state bucket.
State lock failure
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failedA previous Terraform process holds the lock, or crashed without releasing it. Use terraform force-unlock LOCK_ID to release it. Only do this when you are certain no other process is actively running.
Resource creation denied
Error: error creating S3 Bucket (my-app-bucket): AccessDenied:
status code: 403
with aws_s3_bucket.app_bucket, on main.tf line 12The Terraform identity is missing the IAM action for that resource creation step.
Tagging denied
Error: error tagging resource (arn:aws:ec2:us-east-1:111122223333:vpc/vpc-0abc123):
AccessDenied: You are not authorized to perform this operation.The resource was created. The tagging step failed. The policy has the create action but not the tag action.
AssumeRole denied
Error: configuring Terraform AWS Provider:
AssumeRole: failed to assume role
AccessDenied: User is not authorized to assume roleEither the trust policy of the target role does not allow the calling identity, or the calling identity is missing sts:AssumeRole permission.
Step 3: Extract the exact denied action and resource
Every Terraform permission error gives you enough to fix it. Pull out:
| Field | Where to find it |
|---|---|
| Principal | aws sts get-caller-identity |
| Action | The error message (“error creating” translates to s3:CreateBucket) |
| Resource | The ARN or name in the error |
| Phase | Was it init, plan, apply, tagging, or destroy? |
| Terraform resource | The with aws_xxx.name line in the error |
| File and line | The on main.tf line N in the error |
If the error message doesn’t show the exact IAM action, use CloudTrail to look up which API call was denied:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=terraform-deployer \
--start-time 2026-05-13T10:00:00Z \
--end-time 2026-05-13T11:00:00Z \
--query 'Events[*].[EventName,ErrorCode]' \
--output tableBuilding a minimal policy? Run Terraform against a dev account with CloudTrail enabled, then query every API call it made. This gives you the exact list of actions to include in the policy instead of guessing.
Step 4: Common Terraform-specific permission failure patterns
Missing tagging permissions
The issue: Tags are applied after resource creation as a separate API call. A policy can allow resource creation but block tagging.
How it appears: Terraform creates the resource but immediately fails on a *:CreateTags or *:PutBucketTagging error. The resource now exists in AWS but is partially configured.
Fix: Add the tagging action to the policy. Common ones:
{
"Effect": "Allow",
"Action": [
"ec2:CreateTags",
"s3:PutBucketTagging",
"lambda:TagResource",
"ecs:TagResource",
"rds:AddTagsToResource"
],
"Resource": "*"
}After a partial apply: Do not immediately re-run terraform apply. Run terraform plan first and review it carefully. Terraform may try to recreate, destroy, or modify a resource that already exists in AWS but is only partially recorded in state.
Missing iam:PassRole
The issue: When Terraform creates a resource that attaches an IAM role (Lambda execution role, ECS task role, EC2 instance profile), the Terraform identity must have iam:PassRole on that role’s ARN. AWS requires this to prevent privilege escalation. Without it, anyone who could create a Lambda could silently attach an admin role to it.
How it appears:
Error: error creating Lambda Function (my-function): AccessDenied:
User is not authorized to perform: iam:PassRole on resource:
arn:aws:iam::111122223333:role/my-lambda-execution-roleFix: Add iam:PassRole scoped to the exact role ARN, not *:
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::111122223333:role/my-lambda-execution-role"
}This applies to Lambda, ECS task definitions, EC2 instance profiles, CodeBuild, CodePipeline, and any AWS service that assumes an IAM role at runtime. See Managing IAM with Terraform for how to structure these policies cleanly.
Wrong AWS profile or stale session credentials
The issue: AWS_PROFILE set from a previous terminal session causes Terraform to authenticate as the wrong identity silently. Stale session tokens cause failures mid-apply.
How it appears: The policy looks correct but Terraform still gets denied. aws sts get-caller-identity shows an unexpected account or role.
Fix:
# Check what Terraform will use
printenv | grep AWS
aws sts get-caller-identity
# Override explicitly
AWS_PROFILE=terraform-prod terraform applyCommon gotcha: If you recently ran aws sso login or assumed a role in another terminal, that session may be set as the active profile. Terraform inherits this silently. Always verify the identity before a production apply.
Role trust policy problems
The issue: Think of it like a nightclub. The trust policy is the bouncer at the door. The permission policy is the list of things you’re allowed to do once you’re inside. A role can have all the permissions in the world, but if the trust policy doesn’t list you as a Principal, the bouncer won’t let you in.
How it appears: sts:AssumeRole denied, even though the Terraform identity has sts:AssumeRole in its own policy.
Fix: Check the trust policy of the target role:
{
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:user/terraform-user"
},
"Action": "sts:AssumeRole"
}]
}The Principal must exactly match the identity running Terraform. See IAM Roles Explained for the full breakdown of trust policy structure.
SCP, permissions boundary, or session policy restrictions
The issue: IAM policy evaluation has multiple layers, each of which can produce an explicit deny. An allow in the identity policy means nothing if a Service Control Policy at the organization level, a permissions boundary on the role, or a session policy restricts the action.
How it appears: The identity policy looks correct and complete. Terraform still gets denied. CloudTrail shows explicitDeny in the event.
Explicit deny wins, always. A Deny in any policy layer overrides any Allow, anywhere. If CloudTrail shows explicitDeny, find the source of the Deny before adding more Allow statements. More Allows won’t help until the Deny is resolved.
Fix: Check each layer:
- Does an SCP at the OU or account level deny the action?
- Does the role have a permissions boundary that excludes the action?
- If the role was assumed with
--policy, is the session policy too narrow?
Use the IAM Policy Simulator to evaluate the full chain for a given principal, action, and resource.
Bucket policy or VPC endpoint policy restrictions
The issue: Resource-based policies can deny access independently of identity policies. A bucket policy on the Terraform state bucket, or a VPC endpoint policy that restricts which principals can reach the endpoint, can block Terraform even when the identity policy allows the action.
How it appears: Terraform fails to access the state bucket or a resource even though the identity policy looks correct.
Fix: Check the S3 bucket policy on the state bucket. If the Terraform runner is inside a VPC, check whether a VPC endpoint policy is restricting access. See IAM Access Denied Errors for the full policy evaluation sequence.
Remote state bucket or lock permissions
The issue: The Terraform identity has permissions to manage resources but not to read or write the S3 state file. This causes Terraform to fail before it even reads your configuration.
Required permissions on the state bucket:
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::my-terraform-state-bucket",
"arn:aws:s3:::my-terraform-state-bucket/*"
]
}For S3 native locking (use_lockfile = true), the .tflock file also needs s3:DeleteObject to release the lock:
{
"Effect": "Allow",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::my-terraform-state-bucket/*.tflock"
}For older setups using DynamoDB locking, add:
{
"Effect": "Allow",
"Action": [
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:111122223333:table/terraform-state-lock"
}Partially applied resources after a failed run
The issue: When Terraform fails mid-apply, some resources may have been created but not fully configured. Terraform records what it knows in state, but the state may be incomplete or out of sync with what actually exists in AWS.
How it appears: The next terraform apply tries to modify, destroy, or recreate resources in unexpected ways.
Fix: Always run terraform plan after a failed apply before running terraform apply again. Review the plan carefully. If a resource exists in AWS but not in Terraform state, use terraform import to bring it under management before re-running apply.
How it works
Think of a Terraform run like following a recipe. You can’t skip to step 6 (serve the food) if step 3 (preheat the oven) failed. Each step depends on the previous one completing successfully, and each step needs its own set of “ingredients” (IAM permissions).
A full Terraform run touches AWS in this sequence:
- Authenticate: resolve credentials from the environment, profile, or provider block; optionally assume a role via
sts:AssumeRole - Read backend/state: contact the S3 bucket (and lock table or lockfile if configured) to get the current state file
- Read current infrastructure: call
Describe*andList*APIs to refresh the real-world state of existing resources - Plan changes: compare desired state (your
.tffiles) to actual state and produce a diff - Create/update resources: call create, update, or delete APIs for each changed resource
- Tag and configure resources: apply tags, policies, and secondary configuration as separate API calls
- Write updated state: write the new state back to S3
A permission error at any step stops the run at that point. Steps 1 through 4 are needed for plan. Steps 1 through 7 are needed for apply. Step 2 is needed for init.
This is why the same Terraform configuration can fail with completely different errors on different runs. The error depends on which step was reached and which action was missing.
When to use this
This page covers:
AccessDeniederrors duringterraform init,plan, orapplyon AWSiam:PassRoledenials when Terraform creates Lambda, ECS, EC2, or similar resourcessts:AssumeRolefailures in Terraform provider blocks- S3 backend state or lockfile access failures
- Tagging failures after successful resource creation
- Wrong profile or wrong account problems
- Partial apply failures and state drift
Use IAM Access Denied Errors instead if:
- The access denied error is not from Terraform (a direct CLI call or an application runtime)
- You need the full IAM policy evaluation sequence or policy simulator walkthrough
- The error is unrelated to Terraform’s workflow
Terraform permission errors vs generic AWS AccessDenied errors
| Dimension | Terraform on AWS | Generic IAM AccessDenied |
|---|---|---|
| Backend/state | Separate permission set for S3 and lock | Not applicable |
| Tagging | Separate API call; can fail after create succeeds | Same pattern, less common to encounter |
| Partial apply risk | Resource created, state drift possible | No partial state risk |
| Profile/credential confusion | Provider block can silently override env vars | Only env vars and profile chain |
iam:PassRole | Required for Lambda, ECS, EC2, and other role-attaching resources | Not Terraform-specific, but applies equally |
| Trust policy vs permission policy | Both matter; assume-role failures are common | Trust policy only matters for cross-account calls |
| Layer of denial | SCP, boundary, session policy, bucket policy, VPC endpoint policy, or identity policy | Same layers, but Terraform exercises more of them in a single run |
Common mistakes
- Using personal admin credentials for Terraform: running Terraform as an account admin bypasses least-privilege and makes auditing difficult. Use a dedicated deployment role. See IAM Roles Explained for how to set one up.
- Forgetting
iam:PassRole: every resource that attaches an IAM role at runtime requiresiam:PassRolein the Terraform identity policy, scoped to that role’s ARN. Applies to Lambda, ECS task definitions, EC2 instance profiles, CodeBuild, and more. - Mixing backend and provider assumptions: the identity that reads the state bucket and the identity that creates resources can be different. Make sure both have the right permissions.
- Silently using the wrong profile:
AWS_PROFILEleft over from a previous terminal session causes Terraform to authenticate as the wrong identity without warning. Runaws sts get-caller-identitybefore every production apply. - Assuming one allow policy is enough when SCPs or boundaries exist: an explicit deny at the organization level from a Service Control Policy overrides any allow in the identity policy. Both layers must allow the action.
- Confusing trust policy errors with permission errors:
sts:AssumeRoledenied usually means the trust policy of the target role does not list the calling identity as a Principal. Check the trust policy, not the permission policy. - Re-running apply after partial failure without checking state: always run
terraform planafter a failed apply before runningterraform applyagain. Skipping this can cause Terraform to create duplicate resources or apply conflicting changes.
Frequently asked questions
Why does terraform init fail for one reason but terraform apply fail for a different one?
They touch different AWS services. terraform init reads the backend (usually S3, plus a lockfile or DynamoDB table), so init failures are almost always backend access problems. terraform apply calls the AWS APIs for each resource in your config: Lambda, EC2, IAM, S3, and so on. The permissions needed for init and apply are completely separate, and a role that can access the state bucket may still be missing actions needed to create or tag the actual resources.
Why can Terraform create a resource but then fail when tagging it?
Creating a resource and tagging it are separate IAM actions. s3:CreateBucket creates the bucket, but s3:PutBucketTagging applies tags. If the policy allows CreateBucket but not PutBucketTagging, the bucket is created and the tag step immediately fails. This leaves a partially-configured resource in AWS and a partially-updated state file. Always run terraform plan after a failed apply before re-running terraform apply.
Do I need admin permissions to run Terraform in AWS?
No. Terraform only needs the permissions required for the specific resources it manages, but calculating that exact set is tedious. A practical approach is to start with the obvious create/describe/delete actions for each resource type, run terraform apply, and add missing permissions as Terraform reports errors. Use CloudTrail to audit exactly which API calls Terraform made during a run.
How do I fix iam:PassRole errors in Terraform?
Add iam:PassRole to the Terraform identity policy scoped to the exact execution role ARN, not *. Example: {"Effect": "Allow", "Action": "iam:PassRole", "Resource": "arn:aws:iam::111122223333:role/my-lambda-execution-role"}. This error appears when Terraform creates a Lambda function, ECS task definition, EC2 instance profile, or any other resource that attaches an IAM role.
Why does the AWS CLI work fine but Terraform still gets AccessDenied?
Terraform and the AWS CLI may be using different credentials. The CLI uses AWS_PROFILE or the default profile. Terraform uses whatever credentials are configured in the provider block, or environment variables, or the default profile. Run aws sts get-caller-identity in the same environment Terraform runs in to confirm they match. Also check whether the Terraform provider block has an explicit profile or assume_role block that overrides the environment.