Home › Cloud › Terraform AWS Provider Auth Errors — Fix in Minutes
Intermediate 6 min · September 23, 2026

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.

N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Lessons pulled from things that broke in production.

Follow
✓ Production
production tested
October 31, 2026
last updated
2,099
articles · all by Naren
Before you start⏱ 14 min
  • ✓AWS CLI v2 installed with one working profile
  • ✓A Terraform workspace with an AWS provider block
  • ✓IAM permission to call sts:GetCallerIdentity
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • 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
✦ Definition~90s read
What is Terraform AWS Provider Auth Errors Fix?

The Terraform AWS provider is the plugin that translates your HCL resources into AWS API calls — every aws_s3_bucket, aws_instance, and aws_iam_role flows through it. Before it can create anything, it must authenticate: prove which IAM identity is calling, and pick a region to call into.

★
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).

Authentication doesn't live in your .tf files (beyond an optional profile or assume_role stanza); it lives in the surrounding environment — variables, shared files, SSO caches, and instance metadata. The provider delegates credential discovery to the AWS SDK, which walks its chain top-down and stops at the first source that yields credentials.

That delegation is the source of nearly all confusion. Engineers read their Terraform code, find nothing wrong, and conclude the provider is broken — while the real decision happened in shell exports they forgot or an SSO cache that expired overnight.

The provider also resolves region independently of credentials, through its own block argument, environment variables, or the shared config file, which means you can have perfect credentials and still fail on MissingRegion. Each axis fails with different text, and the fixes live in different places.

In production, provider auth is load-bearing infrastructure: CI runners, wrapper scripts, and SSO session lengths all participate. Teams that treat auth as one-time setup get paged when tokens die mid-quarter; teams that treat it as per-run input — asserted by guards, logged as caller identity, refreshed by routine — stop seeing auth incidents entirely.

Understanding the chain order plus region resolution plus token lifetimes covers roughly 90 percent of every NoCredentialProviders, MissingRegion, ExpiredToken, and AccessDenied report you'll ever triage.

Plain-English First

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.

📊 Production Insight
A shared CI environment carried Friday's debugging exports into Tuesday's deploy: stale static keys outranked the SSO profile on all 6 workspaces. Printing the caller Arn into the job log would have exposed it in the first minute.
🎯 Key Takeaway
Credentials resolve top-down with no merging — env vars beat profiles every time, so verify with get-caller-identity and keep one source per shell.

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.

region-diagnosis.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Show what the SDK will actually see echo "AWS_REGION=$AWS_REGION AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" grep -E 'region' ~/.aws/config 2>/dev/null || echo 'no region in shared config' # Explicit beats implicit: set it for this shell export AWS_REGION=us-east-1 # CI guard — fails fast with a clear message test -n "$AWS_REGION" || { echo 'AWS_REGION is empty, aborting'; exit 1; } # Provider-side declaration (terraform block) # provider "aws" { # region = "us-east-1" # profile = "prod" # } terraform plan

📊 Production Insight
A rebuilt CI image dropped the region from ~/.aws/config and the region went dark across all jobs. The guard that now asserts AWS_REGION would have failed the first job in seconds with a readable message.
🎯 Key Takeaway
Region must be explicit — provider block or exported AWS_REGION — plus a CI guard, because CI runners often have no ~/.aws/config, AWS_PROFILE isn't set to select the profile with the region, or the region sits under a profile other than [default].

SSO and STS credentials are temporary by design, and their expiry produces some of the most misleading errors in the Terraform workflow. An SSO session typically lasts around 8 hours, and assumed-role STS tokens can live anywhere from 15 minutes to 12 hours depending on the role configuration. When they die, you get ExpiredToken, TokenRefreshRequired, or a vague auth failure that reads exactly like a provider bug — especially cruel when the morning's plan worked and the afternoon's apply doesn't.

The confusion comes from the split between login state and credential cache. Running aws sso login populates a cached token under ~/.aws/sso/cache keyed by your SSO session, and the SDK silently uses it until it rots. Terraform never prompts you to refresh; it just fails. Engineers then burn an hour pinning provider versions and wiping .terraform directories while the actual fix is a single login command. Worse, long applies can start with valid tokens and expire mid-run, leaving half-applied state that needs careful recovery.

Build refresh into the routine instead of treating it as setup. Start each work session and each CI job that needs SSO with aws sso login --profile prod, then confirm with aws sts get-caller-identity before running anything stateful. For STS-heavy flows, check the token's Expiration timestamp right after assuming the role and compare it against your apply window — if the apply takes 40 minutes and the token lives 15, you'll fail at minute 16. Short-lived credentials are a security feature, not a bug; the bug is assuming they'll outlive your workday.

sso-refresh.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# Confirm the expiry instead of guessing
aws sts get-caller-identity 2>&1 | tail -3

# Refresh the SSO session for this profile
aws sso login --profile prod

# Prove the refresh worked before touching state
aws sts get-caller-identity --profile prod

# Check how long an assumed-role token will live
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/TerraformDeploy \
  --role-session-name check --query 'Credentials.[Expiration]' --output text
📊 Production Insight
Weekend token expiry turned Friday's harmless debugging exports into Tuesday's total outage: dead static keys plus no valid SSO session meant every workspace failed within 20 seconds. A pre-plan identity check now gates all 6 workspaces.
🎯 Key Takeaway
Temporary credentials die on schedule — refresh with aws sso login each session and verify identity before any plan or apply.

Assume-role failures feel personal — AWS tells you you're not authorized to become a role you can see plain as day — but the logic is strictly two-sided and both sides must agree. Your IAM identity needs permission to call sts:AssumeRole on the role's ARN, and the role's trust policy must name your ARN (or your account, or your SSO role) as a trusted Principal allowed to assume it. Miss either half and you get AccessDenied, and the error rarely tells you which half is broken.

The classic mistake is configuring only the Terraform side. You add an assume_role block with the right ARN, your own IAM policy even allows sts:AssumeRole broadly, and it still fails — because nobody edited the role's trust policy to list your user or SSO role ARN. The mirror image happens after SSO migrations: the trust policy still names the old IAM user ARNs while everyone now arrives via an SSO permission-set role with a completely different ARN. Both produce identical AccessDenied text.

Debug it outside Terraform first, where the feedback loop is seconds. Run aws sts assume-role directly with the same ARN and read the verdict. Then inspect the role with aws iam get-role and compare its trust Principal against your actual Arn from get-caller-identity — character for character, including path and session-name suffixes on assumed roles. Watch for conditions too: an ExternalId or MFA condition in the trust policy denies silently when Terraform doesn't send the matching value. When both halves line up, the provider's assume_role block works on the first try.

assume-role-debug.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Reproduce the denial without Terraform in the loop
aws sts assume-role --role-arn arn:aws:iam::123456789012:role/TerraformDeploy \
  --role-session-name debug 2>&1 | tail -5

# Who am I really? Compare this ARN to the trust policy Principal
aws sts get-caller-identity --query Arn --output text

# Read the role's trust policy verbatim
aws iam get-role --role-name TerraformDeploy \
  --query 'Role.AssumeRolePolicyDocument' --output json

# Trust policy shape that works (replace ACCOUNT and USER):
# {"Version":"2012-10-17","Statement":[{"Effect":"Allow",
#  "Principal":{"AWS":"arn:aws:iam::ACCOUNT:user/deployer"},
#  "Action":"sts:AssumeRole"}]}
📊 Production Insight
After an SSO migration, 14 engineers kept failing AssumeRole because the trust policy still named their old IAM user ARNs. The provider config was perfect; the role simply didn't trust the new SSO role ARNs yet.
🎯 Key Takeaway
Assume-role is a handshake: your sts:AssumeRole permission plus your ARN in the role trust Principal — verify both with direct AWS CLI calls.

ProfileNotFound errors are almost never about AWS being down — they're about a name mismatch between what you exported and what exists on disk. Terraform reads AWS_PROFILE and looks for a matching stanza: [profile prod] in ~/.aws/config for settings like region and SSO, and [prod] in ~/.aws/credentials for static keys. A typo, a missing export in a fresh terminal, or a CI job that never set the variable at all produces the same error, and the message helpfully echoes the wrong name right back at you.

The split between the two files trips up even experienced engineers. SSO profiles live in config, static keys live in credentials, and region plus output format live in config — which the AWS provider v4 and later read by default. Copying a [prod] block into the wrong file, or expecting credentials-file regions to apply, fails quietly. Then there's scope: you export AWS_PROFILE in one terminal tab, open another to run the apply, and the second shell has no idea the first existed. CI multiplies this — each job step can be a fresh shell.

Treat profiles as explicit inputs, not ambient state. Echo the variable, grep both files for the stanza, and run aws configure list to see the resolved profile, region, and credential source in one view. Bake the export into wrapper scripts, direnv files, or CI variables so it travels with the work instead of living in one terminal's memory. Profile problems are spelling and scope problems; a 30-second file check beats a 30-minute provider reinstall every single time.

profile-diagnosis.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
# What profile does this shell think it's using?
echo "AWS_PROFILE=$AWS_PROFILE"

# Does the stanza exist in the right file?
grep -A 4 '\[profile prod\]' ~/.aws/config
grep -A 2 '\[prod\]' ~/.aws/credentials

# Resolved view: profile, region, and credential source
aws configure list

# Create it properly when it's genuinely missing
aws configure --profile prod
📊 Production Insight
CI jobs ran profile-less because AWS_PROFILE was set interactively in one terminal and never exported in the job definition. Pinning it as a CI variable fixed all 6 workspaces at once.
🎯 Key Takeaway
ProfileNotFound is a spelling or scope bug — echo the variable, grep both shared files, and confirm with aws configure list.

Every fix in this guide works once; the checklist makes them stick. The pattern behind all five failures is the same: Terraform inherits ambient shell state, and ambient state rots — tokens expire, exports leak between sessions, runner images change what they load. A pre-apply routine that asserts identity, region, token freshness, role access, and profile resolution turns a whole category of 3-hour mysteries into 30-second guard failures with readable messages.

Keep the routine in version control next to your Terraform code so the whole team runs the same checks. A five-line wrapper script beats tribal knowledge: it prints who you're acting as, fails when the region is empty, refreshes SSO when needed, and only then calls plan. CI should run the same script with the same assertions — environment parity between laptops and runners is what stops works-here-fails-there incidents permanently. Review the checklist quarterly, because AWS changes defaults and session durations more often than anyone admits.

The deeper lesson is cultural: treat credential setup as part of the deploy, not as prerequisites everyone supposedly handled. Log the caller Arn on every run, alert when guard failures spike, and never let debugging exports live past the session that needed them. Teams that do this stop fearing Tuesday deploys.

⚠ Ban static keys from CI
Never export long-lived static keys in shared CI environments. They outrank every profile, they survive the session that needed them, and they turn a routine token expiry into a fleet-wide outage. Use SSO profiles plus short-lived STS everywhere automation runs.
📊 Production Insight
After adopting the wrapper, the team's guard caught 11 stale-token and 4 missing-region runs in the first month — each a would-be blocked deploy resolved in under a minute.
🎯 Key Takeaway
Encode all five checks in a versioned wrapper script so laptops and CI assert the same identity, region, and token freshness before plan.
● Production incidentPOST-MORTEMseverity: high

Stale CI Keys Shadowed SSO and Froze Deploys for 3 Hours

Symptom
Every terraform plan in CI failed within 20 seconds with NoCredentialProviders-style errors alternating with MissingRegion, across all 6 service workspaces. Local runs on engineers' laptops worked fine. The failures started Tuesday at 9:40 AM right after a provider version bump merged Monday evening, and deploys stayed frozen until 12:50 PM — 3 hours and 10 minutes of blocked releases.
Assumption
The team assumed the provider upgrade broke auth because the failures started the morning after the version bump. Nobody connected it to the CI image change two days earlier, which left the runner with no ~/.aws/config at all, or to the senior engineer's debugging exports, which were still sitting in the shared CI environment from a Friday session and left AWS_PROFILE unset, so the profile holding the region was never selected.
Root cause
Two overlapping faults. A senior engineer had exported AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the shared CI environment during Friday debugging and never unset them, so those static keys outranked the team's AWS_PROFILE=prod SSO profile on every run. Separately, a new CI base image had no ~/.aws/config at all, AWS_PROFILE wasn't set so the profile with the region wasn't selected, and no AWS_REGION was exported, so MissingRegion surfaced. Monday's SSO tokens expired over the weekend, which turned the shadowing from silent to fatal: Terraform presented dead static credentials with no region to fall back on. Set AWS_REGION or the provider region explicitly to avoid this.
Fix
Three changes closed it out. First, every CI job got an explicit AWS_REGION export plus a guard step that fails the job when it's empty. Second, the shared environment was scrubbed of all AWS_ACCESS_KEY_ID and AWS_SESSION_TOKEN leftovers, and static keys were banned from CI in favor of profiles plus short-lived STS. Third, the wrapper script now runs aws sts get-caller-identity and prints the Arn into the job log before terraform plan, so the next identity mismatch is visible in seconds, not hours.
Key lesson
  • The credential chain doesn't care about your intentions — whatever ranks highest wins, so audit env | grep AWS_ before blaming the provider.
  • Shared config files are read by default in Terraform AWS provider v4 and later (aws-sdk-go-base v2), including region and SSO settings in ~/.aws/config; AWS_SDK_LOAD_CONFIG only mattered for the old Go SDK v1 / provider v3 and earlier. MissingRegion still happens in CI when the runner has no ~/.aws/config at all, when AWS_PROFILE isn't set so the profile with the region isn't selected, or when the region sits under a profile other than [default]. Set AWS_REGION or the provider region explicitly.
  • Print the caller identity into every CI log before plan — a one-line check turns a 3-hour outage into a 30-second diagnosis.
Production debug guideFive checks that separate environment problems from real IAM problems — run them before you touch provider versions.5 entries
Symptom · 01
Terraform acts as the wrong identity — plan touches resources your profile shouldn't see
→
Fix
Run aws sts get-caller-identity and compare the Arn to the role or user you expect. If it shows the wrong identity, list shadow sources with env | grep -E '^AWS_' and unset the stale AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or AWS_SESSION_TOKEN. Then rerun the identity call until the Arn matches before touching Terraform.
Symptom · 02
MissingRegion error even though ~/.aws/config contains a region
→
Fix
Print the region Terraform will resolve with echo "AWS_REGION=$AWS_REGION AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" and grep -E 'region' ~/.aws/config. If both are empty and the provider block has no region, export AWS_REGION=us-east-1 (your region) and rerun terraform plan. Add test -n "$AWS_REGION" to CI so the job fails with a clear message instead of a provider error.
Symptom · 03
Applies worked hours ago and now fail with ExpiredToken or TokenRefreshRequired
→
Fix
Check token freshness with aws sts get-caller-identity 2>&1 | tail -3. If you see ExpiredToken, refresh with aws sso login --profile prod for SSO setups, or request new STS credentials for assumed roles. Confirm the refresh with a second get-caller-identity call showing your Arn before rerunning terraform apply.
Symptom · 04
AccessDenied on AssumeRole the moment the provider configures
→
Fix
Isolate the role call with aws sts assume-role --role-arn arn:aws:iam::123456789012:role/TerraformDeploy --role-session-name debug 2>&1 | tail -5. An AccessDenied here is IAM, not Terraform. Inspect both sides: aws iam get-role --role-name TerraformDeploy for the trust policy, and your own attached policies for sts:AssumeRole on that ARN.
Symptom · 05
ProfileNotFound naming a profile you're sure exists
→
Fix
Verify the profile exists where the SDK looks: echo $AWS_PROFILE, then grep -A 3 "profile prod" ~/.aws/config and grep -A 2 "\[prod\]" ~/.aws/credentials. If the name is missing or misspelled, fix the export or create it with aws configure --profile prod. Confirm with aws configure list showing your profile name.
Terraform AWS Provider Auth Failures at a Glance
Root CauseHow to ConfirmFixPrevention
Static env vars shadow your profileaws sts get-caller-identity shows the wrong userUnset AWS_ACCESS_KEY_ID or export the right AWS_PROFILEOne credential source per shell; guard in wrapper scripts
No region in provider, env, or filesError names MissingRegion; AWS_REGION is emptySet region in provider block or export AWS_REGIONAssert test -n "$AWS_REGION" in CI before plan
Expired SSO or STS session tokenToken expired message; login was hours agoRun aws sso login --profile prod and retryRe-login each session; cache tokens per profile
Role trust policy doesn't list your ARNAccessDenied on AssumeRole from both sidesAdd your ARN to the role trust PrincipalSimulate both policies before first apply
AWS_PROFILE points at a missing profileProfileNotFound naming your profileCreate the profile or fix the AWS_PROFILE valuePin AWS_PROFILE in CI; verify with aws configure list
⚙ Quick Reference
4 commands from this guide
FileCommand / CodePurpose
region-diagnosis.shecho "AWS_REGION=$AWS_REGION AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION"bash configuration
sso-refresh.shaws sts get-caller-identity 2>&1 | tail -3bash configuration
assume-role-debug.shaws sts assume-role --role-arn arn:aws:iam::123456789012:role/TerraformDeploy \bash configuration
profile-diagnosis.shecho "AWS_PROFILE=$AWS_PROFILE"bash configuration

Key takeaways

1
The credential chain is fixed
provider args beat env vars, which beat shared files, which beat instance metadata.
2
Exported static keys silently shadow AWS_PROFILE
one credential source per shell, never both.
3
MissingRegion means no region in provider, env, or loaded files
set AWS_REGION and assert it in CI.
4
SSO and STS tokens expire; aws sso login refreshes them and a pre-apply check catches staleness.
5
Assume-role needs both sides
your sts:AssumeRole permission and your ARN in the role trust policy.
6
Two commands diagnose almost everything
aws sts get-caller-identity and aws configure list.

Common mistakes to avoid

6 patterns
×

Setting AWS_PROFILE and static keys at the same time

Symptom
Terraform uses credentials you didn't expect, and aws sts get-caller-identity shows a different user than the profile you thought was active.
Fix
Export the right variable for the run: AWS_PROFILE=prod for SSO profiles, or AWS_ACCESS_KEY_ID plus AWS_SECRET_ACCESS_KEY for static keys. Never set both at once. Add a guard at the top of every wrapper script: test -n "$AWS_PROFILE" or test -n "$AWS_ACCESS_KEY_ID" before terraform runs.
×

Relying on ~/.aws/config for region without selecting the right profile

Symptom
MissingRegion errors in CI even though the region is sitting right there in ~/.aws/config on the runner.
Fix
Put region in the provider block or as an exported AWS_REGION, and assert it in CI with test -n "$AWS_REGION" before terraform plan. Shared config files alone won't help if the runner has no ~/.aws/config, AWS_PROFILE isn't set to select the profile with the region, or the region sits under a profile other than [default].
×

Treating aws sso login as a one-time setup step

Symptom
Morning applies work, afternoon applies fail with ExpiredToken, and the team blames Terraform instead of the SSO cache.
Fix
Run aws sso login --profile prod before every session and re-login when you see ExpiredToken. Cache SSO tokens per profile and set a calendar reminder: the default 8-hour session dies mid-afternoon.
×

Adding assume_role to Terraform but forgetting the trust policy

Symptom
AccessDenied on AssumeRole every plan, even though your own IAM policy allows sts:AssumeRole — the role doesn't trust you back.
Fix
Grant the IAM user sts:AssumeRole on the exact role ARN, and add the user's ARN to the role's trust policy Principal. Validate both sides with the IAM policy simulator before touching Terraform.
×

Assuming the profile carries over between terminals and CI jobs

Symptom
Works in your terminal, fails in CI with ProfileNotFound, because the second environment never set AWS_PROFILE.
Fix
Keep it simple: export AWS_PROFILE=prod in each shell, pin it in CI variables, and confirm with aws configure list. Alias tf='AWS_PROFILE=prod terraform' if engineers keep forgetting.
×

Rotating credentials without clearing the old ones

Symptom
New keys don't take effect because an older AWS_SESSION_TOKEN or profile is still shadowing them in the chain.
Fix
Change one variable at a time: unset the profile, export static keys, and rerun aws sts get-caller-identity to see which source wins. Then delete the loser from the environment for good.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Walk me through the AWS credential chain Terraform uses.
Q02JUNIOR
Terraform worked this morning and now fails with ExpiredToken. What happ...
Q03SENIOR
AssumeRole is denied. How do you debug both sides of the trust?
Q04SENIOR
How do you stop MissingRegion errors from ever reaching production?
Q05JUNIOR
What does TF_LOG=DEBUG tell you during an auth failure?
Q01 of 05SENIOR

Walk me through the AWS credential chain Terraform uses.

ANSWER
Static provider arguments beat environment variables, which beat shared credentials and config files, which beat EC2 instance metadata. So an exported AWS_ACCESS_KEY_ID silently overrides AWS_PROFILE. I confirm with aws sts get-caller-identity to see who Terraform really is.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Does setting AWS_PROFILE guarantee Terraform uses that profile?
02
Can I set the region once in the provider instead of per resource?
03
What does ExpiredToken actually mean during an apply?
04
Why does assume_role fail when my IAM policy allows it?
05
How do I debug a profile Terraform claims doesn't exist?
06
Is TF_LOG safe to use for credential debugging?
N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Lessons pulled from things that broke in production.

Follow
✓ Verified
production tested
October 31, 2026
last updated
2,099
articles · all by Naren
🔥

That's Terraform. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
Terraform count vs for_each: Why Index Shifts Destroy Resources
6 / 6 · Terraform
Next
Azure AADSTS700016: Application Not Found in Directory
→