Terraform AWS Provider Auth Errors — Fix in Minutes
Terraform AWS provider auth failing? Fix credential chain order, missing region, expired SSO, and assume-role trust in minutes.
20+ years shipping production backend systems. Lessons pulled from things that broke in production.
- ✓AWS CLI v2 installed with one working profile
- ✓A Terraform workspace with an AWS provider block
- ✓IAM permission to call sts:GetCallerIdentity
- The AWS credential chain is fixed: provider args beat env vars, which beat shared files, which beat instance metadata — the highest-ranked source always wins
- Run aws sts get-caller-identity first: if the Arn isn't who you expect, stale env vars are shadowing your profile
- MissingRegion means no region in the provider block, AWS_REGION, or loaded config files — export AWS_REGION and assert it in CI
- SSO and STS tokens expire: refresh with aws sso login --profile prod when you see ExpiredToken, and re-check identity before apply
- Assume-role needs both halves: your sts:AssumeRole permission and your ARN in the role's trust policy Principal
Think of Terraform as a valet who needs two things before touching your AWS account: your ID badge (credentials) and the parking lot address (region). If someone else's badge is clipped on top of yours, the valet uses the wrong one — that's the credential chain shadowing your profile. If nobody wrote down the address, the valet can't drive anywhere — that's the missing region. And if your badge expired at noon, the 2 PM trip fails — that's SSO expiry. Every fix here is just checking the right badge is on top, the address is written down, and nothing's expired.
It's 10 AM, you run terraform plan, and instead of a clean diff you get NoCredentialProviders, MissingRegion, or a wall of AccessDenied. Nothing changed in your code since yesterday. Your first instinct is to blame the provider version — don't. In nearly every case we've seen, the provider is fine and the environment around it shifted: an SSO token expired overnight, a CI job lost its AWS_PROFILE export, or someone's exported static key started shadowing the profile you thought was active.
These failures hurt because they block everything. You can't plan, you can't apply, and you can't even refresh state to see what's deployed. The error text points at Terraform, so teams reinstall providers and pin versions while the real problem sits in a stale token cache or a missing region variable.
This guide walks the full fix path in the order you'll actually need it. You'll learn the credential chain order that decides which identity Terraform uses, why region resolution fails even when a config file looks right, how SSO and STS expiry masquerade as provider bugs, what both sides of an assume-role trust relationship must contain, and how shared config and credentials files split responsibilities. By the end you'll diagnose any AWS provider auth failure with two commands and stop it from recurring with a pre-apply checklist.
When Terraform reports auth errors, your first job is figuring out which identity it actually used — because it's often not the one you intended. The AWS SDK resolves credentials in a strict order: explicit access keys in the provider block win first, then environment variables like AWS_ACCESS_KEY_ID, then the profile named by AWS_PROFILE from the shared files, then the container or ECS credential endpoint, and finally EC2 instance metadata. There's no merging and no fallback blending. The highest-ranked source that yields any credentials wins outright, and everything below it is ignored.
That ordering is why debugging exports are so dangerous. You set AWS_ACCESS_KEY_ID on Friday to test something, forget to unset it, and on Monday your AWS_PROFILE=prod SSO login succeeds but Terraform never looks at it — the stale static key outranks it. The symptom is confusing: aws sso login works, the profile looks right, yet Terraform acts as a different identity or fails with expired static credentials. The two tools read different rungs of the same ladder and you only checked one.
Make identity verification a habit, not a rescue step. Before any plan against a real account, run aws sts get-caller-identity and read the Arn. If it isn't the role or user you expect, dump the environment with env | grep AWS_ and remove the shadowing variables. One credential source per shell is the rule: either a profile or static keys, never both. Teams that print the caller Arn into every CI log catch mismatches in seconds instead of burning hours on provider version archaeology.
MissingRegion is the most literal error Terraform produces: some AWS API call needs a region and none was provided anywhere the SDK looks. The provider checks the region argument in its own block first, then AWS_REGION and AWS_DEFAULT_REGION, then the region key in the shared config file, and gives up after that. An empty result means your config file might as well not exist.
The trap is that ~/.aws/config looks correct on disk while being completely inert. CI runners very often have no ~/.aws/config at all, or AWS_PROFILE isn't set so the profile with the region isn't selected, or the region sits under a profile other than [default] — so the region = us-east-1 line you carefully added never loads. Local shells have a populated config and a selected profile, so the same code works on your laptop and dies in CI. You'll stare at the file, insist the region is right there, and be technically correct while the SDK never reads it.
The durable fix has two halves. First, declare region where it's unambiguous: a region argument in the provider block, or an exported AWS_REGION in every environment that runs Terraform. Second, make CI assert it before plan runs with a guard like test -n "$AWS_REGION" that fails with a human-readable message. Regions rarely change per workspace, so hard-coding intent beats hoping a dotfile loads. When the region is explicit and asserted, MissingRegion disappears as a category instead of recurring every time someone rebuilds a runner image.