AWS "UnrecognizedClientException: The Security Token Included in the Request Is Invalid": Every Cause, Fixed

Logeshwaran
—

"UnrecognizedClientException: The security token included in the request is invalid" means AWS looked at the access key ID in your request and found nothing it recognizes: a key that was deleted, a key from another account, or, most often, temporary credentials sent without the third piece, the session token. It is not a permissions error. A key with full administrator rights gets it just the same. The fix is to find out which credentials the failing command is actually using, with aws configure list and aws sts get-caller-identity, then either give it the session token it is missing, replace the dead key, or clear the stale environment variable that is overriding the good one. The detail that catches careful people: aws configure never asks for a session token, so pasting temporary credentials into it produces exactly this error, every time.

Jake runs a phone repair shop, and a small script on the shop PC copies the day's booking export to S3 every evening. It had run for months. Then Ethan moved the shop's AWS access over to IAM Identity Center, Jake dutifully opened the access portal, chose the account, clicked the "command line access" link, pasted the key ID and secret into aws configure like the old days, and the script answered with this error on the first run. Nothing was hacked and nothing was revoked. The portal had given him three values and aws configure had asked for two. This page is the order Ethan worked through with him: the two commands that show what AWS actually received, the seven causes in order of how often they turn out to be the one, the cases that only show up in Lambda, Docker, CI and opt-in Regions, and why the same bad key produces four differently worded errors depending on which service you ask.

⚡ Quick answer

• See what is being used: aws configure list shows the key and where it came from; aws sts get-caller-identity shows who AWS thinks you are, or fails the same way.

• Temporary credentials: they are three values. If you only set the key and secret, add aws_session_token to the credentials file or AWS_SESSION_TOKEN to the environment.

• Stale environment: an old AWS_SESSION_TOKEN next to long-term keys is invalid too. env | grep AWS_, then unset what you did not mean.

• Dead key: aws iam list-access-keys --user-name NAME. Inactive or missing means a new key, not a new config.

• Opt-in Region: tokens from the global STS endpoint do not work in Hong Kong, Milan and friends. Use the Regional STS endpoint or switch the account to version 2 tokens.

• Lambda: never build an SDK client from only the key and secret environment variables. Let the SDK read all three on its own.

What AWS is telling you, in its own words

AWS's common error reference defines UnrecognizedClientException as "The X.509 certificate or AWS access key ID you provided doesn't exist in our records. Verify that you're using valid credentials and that they haven't expired", with HTTP status 403. The sentence most people actually see, "The security token included in the request is invalid", is the message that rides along with it on services that speak JSON: DynamoDB, Lambda, Bedrock, ECS, Kendra, and most newer APIs. Either way the meaning is the same. AWS received a request, looked up the access key ID in the signature, and could not match it to any live identity.

That rules out a whole family of things in one line. It is not a permissions problem; a denied permission is AccessDenied or AccessDeniedException, and it comes after AWS has recognized you. It is not a missing credential; that is Unable to locate credentials, which means the CLI found nothing to send. And it is not a clock or signing problem; those are SignatureDoesNotMatch and RequestExpired. This error sits between them: credentials were found, they were sent, and AWS does not know them.

There are exactly three reasons AWS would not know a key it is looking at. The key no longer exists on AWS's side, because it was deleted or deactivated, or belongs to another account or partition. The key exists but the request is missing a part of it, which is the session-token case. Or the key exists and is complete, but it was issued in a form that is not valid where the request landed, which is the opt-in Region case. Everything on this page is one of those three, dressed differently.

Jake: "So it is saying my key is fake?"

Ethan: "It is saying it cannot find it. Yours is real, but the portal gave you a three-part key and you sent AWS two parts. From where AWS sits, that is a stranger."

The two commands that end the guessing

Before you change anything, find out what the failing command is actually sending. People lose afternoons editing a credentials file that the command was never reading.

aws configure list
aws sts get-caller-identity

The first command prints the access key's last four characters and, in the Location column, where it came from: env for an environment variable, shared-credentials-file for ~/.aws/credentials, config-file, or a profile name. That column is the whole diagnosis more often than not, because the CLI reads credentials in a fixed order, environment variables first, then the files, then container and instance roles, and the first one it finds wins. An AWS_ACCESS_KEY_ID exported in a shell last week beats a perfectly good profile every time.

$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************ABCD  shared-credentials-file
secret_key     ****************ABCD  shared-credentials-file
    region                us-west-2              env    AWS_DEFAULT_REGION

The second command asks AWS to name the caller. If it succeeds, your credentials are fine and the error you saw came from somewhere else: another profile, another process, another Region. If it fails with the same error, you have reproduced the problem in the simplest possible call, and the rest of this page applies. Add --profile NAME to both commands if the failing script used a named profile, and run them in the same shell, as the same user, that the script runs in. A script under cron or a service account reads a different home folder from the one you are sitting in.

When those two are not enough, --debug shows the key ID the CLI signed with, in the Credential= part of the Authorization header, and the line Found credentials in ... tells you which source it chose. Search the output for AccessKeyId and compare the value against the keys you think you have.

aws sts get-caller-identity --debug 2>&1 | grep -iE "Found credentials|Credential=" | head
What you findWhat it meansGo to
Key starts with ASIA, no session token setTemporary credentials sent incompleteThe session-token section
Key starts with AKIA, AWS_SESSION_TOKEN is setA stale token is being sent with a long-term keyThe environment section
Location says env but you edited a fileAn environment variable is overriding the fileThe environment section
Key is not in list-access-keys, or shows InactiveThe key was deleted or switched offThe dead-key section
Works in us-east-1, fails in ap-east-1 or eu-south-1A global-endpoint token in an opt-in RegionThe Regions section
Works on your laptop, fails in Lambda, Docker or CIThe runtime hands credentials differentlyThe runtimes section

One clue hides in the key ID itself. Long-term access keys for IAM users begin with AKIA. Temporary credentials, from Identity Center, an assumed role, or an instance role, begin with ASIA, and an ASIA key always travels with a session token. If the four characters after **************** do not help, run the full debug and look at the prefix.

The missing third piece: temporary credentials are three values

This is Jake's case and it is the most common one by a distance. Temporary credentials, the kind you get from the IAM Identity Center access portal, from aws sts assume-role, from get-session-token with MFA, or from CloudShell, come as three values: an access key ID, a secret access key, and a session token. The session token is what makes the other two valid. Send the first two without it and AWS has nothing to match, so it answers that the security token is invalid, which is literally true.

The trap is that aws configure asks four questions, and none of them is the session token. People paste the key and the secret, see no place for the third line, and assume it is optional. The portal's own "command line or programmatic access" page shows you three ways to use the values, and every one of them includes the token.

# ~/.aws/credentials: a temporary set needs all three lines
[shop-export]
aws_access_key_id = ASIAEXAMPLEKEYID
aws_secret_access_key = EXAMPLESECRET
aws_session_token = EXAMPLE...LONG...TOKEN

# or, for one shell session
export AWS_ACCESS_KEY_ID=ASIAEXAMPLEKEYID
export AWS_SECRET_ACCESS_KEY=EXAMPLESECRET
export AWS_SESSION_TOKEN=EXAMPLE...LONG...TOKEN

Two more things about temporary credentials that save a second visit to this page. They expire, an hour by default for an Identity Center permission set and for most assumed roles, so even a correctly pasted set stops working later; the error for that is usually ExpiredToken or ExpiredTokenException, not this one, but a token that has expired and been copied around can show up either way. And pasting them at all is the wrong habit for anything that runs more than once. The durable fix for Jake's script was not a better paste; it was an Identity Center profile with an sso-session block, so the CLI fetches fresh credentials itself, or for a server, a role attached to the machine.

Jake: "Why would AWS design a form with no box for the third thing?"

Ethan: "Because that form predates temporary keys. It was built for the old permanent ones. Nobody ever added a box, and this error is the result, a thousand times a day."

The stale variable that outranks your file

The second most common cause is the mirror image of the first: a long-term key with a leftover session token. Last week you exported three temporary values into a shell for a one-off job. Today you put a permanent AKIA key in your credentials file and the CLI still fails, because the shell still has AWS_SESSION_TOKEN set, the CLI reads the environment before the file, and it sends your permanent key together with a dead token. AWS, reasonably, rejects the pair.

# macOS and Linux
env | grep ^AWS_
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE

# PowerShell
Get-ChildItem Env:AWS*
Remove-Item Env:AWS_ACCESS_KEY_ID, Env:AWS_SECRET_ACCESS_KEY, Env:AWS_SESSION_TOKEN

Print the variable names, never the values, if you are pasting this anywhere. Then run aws configure list again and watch the Location column change from env to the file. A variable set in a shell profile, a .env file loaded by your tooling, an IDE launch configuration, or a container's environment all count, and a program inherits its environment when it starts, so a long-running process keeps the old values until you restart it. AWS_PROFILE deserves its own look: it quietly points every command at a profile you may have forgotten exists.

The key that no longer exists

If the key and secret are complete and there is no stray token, the key itself may be gone. An administrator rotated it, a leaked-key response deleted it, someone clicked Deactivate in the IAM console, or the IAM user was removed altogether. AWS has no memory of deleted keys, so from its side the request looks identical to a made-up key.

aws iam list-access-keys --user-name jake-shop-export
aws iam get-access-key-last-used --access-key-id AKIAEXAMPLEKEYID
aws iam update-access-key --user-name jake-shop-export --access-key-id AKIAEXAMPLEKEYID --status Active

You need working credentials of your own, or an administrator, to run those, which is the point: a dead key cannot inspect itself. list-access-keys shows every key on the user with its status. If the failing key is not in the list, it was deleted and the fix is a new key, created in the console or with create-access-key, then written into the profile that the script uses. If it is there but Inactive, update-access-key switches it back on, and it is worth asking why it was switched off before you do. get-access-key-last-used tells you when and from which service the key last worked, which settles arguments about whether it ever did.

A key from the wrong place fails the same way. Keys belong to one account and one partition: a key from the AWS China partition or from GovCloud does not exist in the commercial partition, and a key from a colleague's account does not exist in yours. If a key was copied between projects in a CI system, confirm which account it was minted in before you conclude it is broken.

And check the paste itself. The CLI documentation warns that secret keys containing characters such as +, / or % can be mangled by scripts and shells that build credential files, and the cure it gives is to regenerate the key until you get one without the troublesome character. A trailing space, a swapped key and secret, or a key truncated by a copy that caught only the first line all produce this error; cat -A ~/.aws/credentials shows invisible characters, and aws configure rewrites the entry cleanly.

Opt-in Regions: a valid token that is not valid here

Here is the cause that makes experienced people doubt themselves, because every command works until the one that targets Hong Kong, Milan, Jakarta, Cape Town, Melbourne or another Region that is disabled by default. Temporary credentials come from AWS STS, and STS has a global endpoint, sts.amazonaws.com, plus a Regional endpoint in every Region. Session tokens from a Regional endpoint are valid everywhere. Session tokens from the global endpoint are valid only in Regions enabled by default. Send a global-endpoint token to an opt-in Region and that Region does not recognize it, which it reports as an invalid security token, or on STS and query-style services as InvalidClientTokenId.

There are two fixes, and AWS recommends the first. Make your tools call Regional STS endpoints, which the SDKs and CLI do when the sts_regional_endpoints setting, or the AWS_STS_REGIONAL_ENDPOINTS environment variable, is regional; current SDKs default to this. Or, if something must keep using the global endpoint, switch the account to version 2 tokens, which are valid in all Regions and simply longer.

# prove it: same credentials, two Regions
aws sts get-caller-identity --region us-east-1
aws sts get-caller-identity --region ap-east-1

# account-wide: make global-endpoint tokens valid everywhere (needs iam:SetSecurityTokenServicePreferences)
aws iam set-security-token-service-preferences --global-endpoint-token-version v2Token

Two side rules matter in companies with several accounts. The opt-in Region has to be enabled in the account that owns the role you are assuming, not only in the account you are calling from. And an opt-in Region cannot have STS deactivated once it is enabled, so if the Region is on, STS is on; the question is only which endpoint minted your token. If a service in a Region you never enabled answers with OptInRequired instead, that is a different error with an honest name: enable the Region in the account settings first.

Jake: "I do not even know what an opt-in Region is."

Ethan: "Then it is not your problem today. It is the one that bites the people who run in Hong Kong or Milan, and it is here so they stop blaming their keys."

Lambda, Docker, CI and LocalStack: where the credentials come from changes

Your laptop and a Lambda function do not get credentials the same way, and most of the "works locally, fails in production" versions of this error live in that difference.

Lambda. The runtime gives your function temporary credentials for its execution role through three environment variables, key, secret and session token. The classic mistake is code that builds an SDK client with only two of them: new AWS.DynamoDB({ accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY }). It worked on the laptop, where those variables held a permanent key, and it fails in Lambda, where they hold an ASIA key whose token you dropped. The fix is to pass no credentials at all and let the SDK read all three itself, or, if you truly must pass them, include sessionToken: process.env.AWS_SESSION_TOKEN. The same applies to any code that calls assume-role and then forgets the token on the next client.

// Lambda, Node.js: let the runtime's credentials flow through
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
const ddb = new DynamoDBClient({ region: process.env.AWS_REGION });   // no credentials block

# Lambda, Python: same idea
import boto3
ddb = boto3.client("dynamodb")   # no aws_access_key_id= arguments

Docker and CI. A container image with a key baked in keeps using that key after it was rotated, and a CI job that was handed keys copied from another project may be holding keys from another account. Check which identity the job really runs as with aws sts get-caller-identity inside the job, and prefer a runner identity, an OIDC federation into a role, over any pasted key. A container that was suspended and resumed can also drift its clock far enough to break signing, but that surfaces as SignatureDoesNotMatch or RequestExpired, not as this error.

LocalStack and other emulators. If your code is meant to talk to a local emulator and you see this error, the request went to real AWS. The endpoint_url was not applied to this client, or the environment variable that sets it was missing in this process, so your fake test credentials reached the genuine DynamoDB and were, correctly, unrecognized. Fix the endpoint, not the keys.

CloudShell. It starts with the credentials of the console session you opened it from. A profile or environment variable you set inside it takes precedence, which is how a CloudShell session can suddenly fail after a paste that was meant for another machine.

API Gateway with IAM authorization. Calling an API that uses IAM authorization with a bad or incomplete key returns a 403 carrying this same sentence. The API is fine; the signing identity is not. Sign the request with complete credentials, session token included, and check the key is from the account the API trusts.

Getting temporary credentials the right way, step by step

Since the missing session token is the top cause, here is the clean way to obtain and use temporary credentials when you genuinely need them by hand, for example assuming a role into another account for an afternoon, or satisfying an MFA requirement on the command line. The shape is always the same: one call to STS, three values back, all three exported, then prove the identity.

  1. Call STS for the credentials. For a role: aws sts assume-role --role-arn arn:aws:iam::123456789012:role/ShopAdmin --role-session-name jake. For an MFA-protected session on your own user: aws sts get-session-token --serial-number arn:aws:iam::123456789012:mfa/jake --token-code 123456.
  2. Read the Credentials block in the response. It contains AccessKeyId, SecretAccessKey, SessionToken and Expiration. The key will start with ASIA.
  3. Export all three into the shell that will run the commands, or write all three into a named profile. Leaving out SessionToken is the whole error on this page.
  4. Run aws sts get-caller-identity and read the ARN. For an assumed role it shows assumed-role/ShopAdmin/jake, which is your proof the three values are being used together.
  5. Note the Expiration. When it passes, the error changes to ExpiredToken, and the fix is to repeat from step one, not to edit anything.
  6. When the job is done, unset the three variables, so tomorrow's commands do not inherit a dead token.

Better still, let the CLI do the assuming. A profile with role_arn and source_profile in ~/.aws/config, plus mfa_serial if the role requires it, makes the CLI call STS, cache the three values, and refresh them when they expire, and you never see a session token at all. That is the same idea as the sso-session block for Identity Center: hand the CLI the recipe, not the ingredients.

# ~/.aws/config: the CLI assumes the role and keeps the three values fresh
[profile shop-admin]
role_arn = arn:aws:iam::123456789012:role/ShopAdmin
source_profile = jake
mfa_serial = arn:aws:iam::123456789012:mfa/jake
region = us-east-1

When the script runs as someone else: cron, services and sudo

A script that works when you run it and fails under cron or as a service is almost always reading a different home folder, and therefore a different credentials file or none at all. The .aws folder lives under the home of whichever user runs the process. Root's is /root/.aws; a service account's is wherever its home points; a Windows scheduled task running as SYSTEM has a profile of its own. The error can even differ: an empty folder gives Unable to locate credentials, while a folder with a stale key gives this page's error.

# see what the other user's AWS CLI would use
sudo -u backup-user -H aws configure list
sudo -u backup-user -H aws sts get-caller-identity

# or point the job at an explicit file instead of guessing its home
AWS_SHARED_CREDENTIALS_FILE=/etc/shop/aws-credentials aws sts get-caller-identity

Ethan's own rule for the shop PC was simpler than any of that: the evening export runs under Jake's account with an explicit --profile on the one command that talks to AWS, so there is never a question of whose home folder is in play. On a real server the answer is a role on the machine and no credentials file at all. A root-owned /root/.aws/credentials with a forgotten key in it has broken more patching and backup jobs than any typo, because nobody remembers it is there until Systems Manager or a backup agent starts failing with this exact sentence.

One bad key, four different error names

This is the part no other page explains, and it is why people search for three different errors and find the same answer each time. AWS services speak two protocols. The older query-style services, STS, IAM, EC2, and the CLI's S3 commands, report an unknown key as InvalidClientTokenId, EC2 as AuthFailure, and S3's own API as InvalidAccessKeyId. The JSON-style services, DynamoDB, Lambda, Bedrock, ECS, SSM and most newer ones, report it as UnrecognizedClientException. Same key, same cause, four spellings.

Error you seeWhereWhat AWS is sayingFix lives in
UnrecognizedClientException, "security token included in the request is invalid"DynamoDB, Lambda, Bedrock, ECS and other JSON servicesKey ID not found, or token missing or wrongThis page
InvalidClientTokenId, same sentenceSTS, IAM, aws s3 ls and other query servicesSame thingThis page
InvalidAccessKeyId, "does not exist in our records"S3 API directlySame thingThis page
AuthFailure, "credentials could not be validated"EC2Same thing, or an account not yet allowed to use EC2This page, then billing
ExpiredToken / ExpiredTokenExceptionAny serviceA real token that has run outRefresh the credentials
SignatureDoesNotMatchAny serviceKey known, secret or signing wrong, clock offSecret, clock, special characters
AccessDenied / AccessDeniedExceptionAny serviceKey known, action not allowedIAM policy
Unable to locate credentialsCLI and SDKsNothing was sent at allProfile and environment

The practical use of the table: read the error name before the sentence. AccessDenied means AWS knows you and said no, so stop touching keys and read the policy. ExpiredToken means a real session ended, so refresh it. Anything in the first four rows means AWS does not know the key, and the diagnosis above applies whichever service spelled it.

The fix for each cause, in the order to try them

  1. Find the source. aws configure list in the same shell and user as the failing command. Note the Location column and the key's last four characters.
  2. Clear what you did not mean. If Location says env and you expected a file, unset the AWS_ variables and check again. If AWS_PROFILE is set, make sure it is the profile you think.
  3. Complete the credentials. If the key begins with ASIA, add the session token to the same place the key lives. If the values came from the Identity Center portal, use aws configure sso instead and let the CLI refresh them itself.
  4. Prove the identity. aws sts get-caller-identity --profile NAME. Read the account and ARN; a working profile for the wrong account is a different bug.
  5. Check the key on AWS's side. With working admin credentials, aws iam list-access-keys --user-name NAME. Reactivate or replace.
  6. Check the Region. Run the identity command with --region set to the Region that failed. If only an opt-in Region fails, use Regional STS endpoints or set version 2 tokens.
  7. Check the runtime. In Lambda, remove explicit credentials from client constructors. In Docker and CI, run the identity command inside the job. With an emulator, confirm the endpoint is applied.
  8. Replace the habit. A script that needs pasted keys every morning is a script that needs a role or an Identity Center profile. Fix that once and this page stops being relevant to it.

Resist two shortcuts. Creating a permanent access key on the root user to "just make it work" trades a one-hour problem for a permanent liability. And deleting the entire .aws folder, which half the forum answers suggest, destroys every working profile on the machine to fix one line in one of them.

What Jake's evening actually looked like

The whole thing took Ethan eleven minutes, and most of them were the drive over. aws configure list showed a key ending in a different four characters from the one Jake had pasted, with Location env: the old permanent key was still exported in the shop PC's shell profile from the year before. Underneath it, in the credentials file, sat the new ASIA key with no session token. Two causes at once, which is more common than it sounds, because the second mistake is usually made while trying to fix the first.

Ethan removed the export from the shell profile, deleted the pasted ASIA lines, and ran aws configure sso --profile shop-export, pointing it at the portal with an sso-session block. Jake signed in once in the browser. aws sts get-caller-identity --profile shop-export returned the shop account and the right role. The export script got --profile shop-export added to its one AWS command and ran clean. It has not asked for a key since; when the session ends, the CLI tells Jake to sign in, which is one browser tab rather than an evening.

Jake: "So the portal was right and the form was old."

Ethan: "The portal was right, the form was old, and your shell was holding a grudge from last year. None of it was your fault, and all of it was findable from one command."

Not meeting it again

For a person: keep one named profile per account and role, built with aws configure sso so the CLI refreshes credentials itself, and run aws sts get-caller-identity before anything that matters. Never export temporary credentials into a shell profile; a one-off export belongs in a one-off shell. For a server, a container, or a function: give it a role and pass no credentials in code. For a pipeline: federate the runner into a role rather than storing keys, and put the identity check at the top of the job so a rotated key fails on line one with a clear name instead of forty lines in.

For the administrator: when you rotate or deactivate a key, get-access-key-last-used tells you who is about to break, and a line in the team chat beats an evening of this error on someone else's screen. And if your company works in an opt-in Region, set Regional STS endpoints as the standard or move the account to version 2 tokens once, before the first person spends a day on it.

Questions people search for this error with

What does UnrecognizedClientException mean in AWS?

AWS could not match the access key ID in your request to any live identity. The key was deleted or belongs to another account, the request is missing its session token, or the token came from the global STS endpoint and was used in an opt-in Region. It is not a permissions error.

How do I fix "The security token included in the request is invalid"?

Run aws configure list to see which credentials are used and where they come from, then aws sts get-caller-identity. Add the missing aws_session_token for temporary keys, unset stale AWS_ environment variables, replace a deleted or inactive key, or use Regional STS endpoints for opt-in Regions.

What is the difference between UnrecognizedClientException and InvalidClientTokenId?

Nothing in cause. JSON-style services such as DynamoDB, Lambda and Bedrock report an unknown key as UnrecognizedClientException; query-style services such as STS, IAM and the CLI's S3 commands report it as InvalidClientTokenId. EC2 says AuthFailure and the S3 API says InvalidAccessKeyId.

Why does aws configure not ask for a session token?

The aws configure prompts predate temporary credentials and only ask for a key, secret, Region and output format. Temporary credentials need a third value, so add aws_session_token to the credentials file by hand, export AWS_SESSION_TOKEN, or use aws configure sso so the CLI fetches all three itself.

Why does my key work in us-east-1 but not in Hong Kong or Milan?

Those are opt-in Regions, and session tokens from the global STS endpoint are valid only in Regions enabled by default. Use the Regional STS endpoint, or run aws iam set-security-token-service-preferences --global-endpoint-token-version v2Token to make global tokens valid everywhere.

Why does my Lambda function get UnrecognizedClientException?

Almost always because the code builds an SDK client from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY without AWS_SESSION_TOKEN. Lambda's credentials are temporary and need all three. Remove the explicit credentials and let the SDK read the environment.

How do I check whether an AWS access key is still valid?

With working credentials, run aws iam list-access-keys --user-name NAME to see the key's status, and aws iam get-access-key-last-used --access-key-id ID to see when it last worked. A key that is missing from the list was deleted.

Does UnrecognizedClientException mean my key was leaked or hacked?

No. It means AWS does not recognize the key you sent, which is almost always a configuration problem on your side. If you suspect a leak, the signs are unexpected activity in CloudTrail and billing, not this error.

What does ExpiredToken mean compared with an invalid security token?

ExpiredToken means AWS recognized a real temporary credential whose lifetime has ended; refresh it. An invalid security token means AWS could not recognize the credential at all; complete it, replace it, or fix where it came from.

Why does the error appear only in Docker or my CI pipeline?

The container or job is using different credentials from your laptop: a key baked into an image that has since been rotated, keys copied from another account, or an emulator endpoint that did not apply so the request reached real AWS. Run aws sts get-caller-identity inside the job to see.

Can clock skew cause UnrecognizedClientException?

Not this one. A wrong clock produces SignatureDoesNotMatch or RequestExpired, because the signature is checked after the key is recognized. If your clock is off, fix it, but the invalid-token error has a different cause.

Why does aws s3 ls say InvalidClientTokenId?

The CLI's S3 commands go through a query-style path that names the unknown-key error InvalidClientTokenId. It is the same problem as UnrecognizedClientException elsewhere: check the credentials source, the session token and the key's status.

How do I see which credentials the AWS CLI is using?

aws configure list shows the key's last four characters and the Location column tells you whether it came from an environment variable, the shared credentials file, the config file or a profile. Add --debug to a command to see the full key ID in the Credential= header.

What order does the AWS CLI read credentials in?

Command-line options first, then environment variables, then the shared credentials and config files, then container and instance roles. The first source found wins, which is why a forgotten AWS_ACCESS_KEY_ID in the shell overrides a correct profile.

Should I delete ~/.aws to fix this?

No. That destroys every working profile on the machine. Find the one broken entry with aws configure list and fix that line, or use aws configure sso to rebuild the single profile that failed.

Does API Gateway return "security token included in the request is invalid"?

Yes, with a 403, when an API that uses IAM authorization is called with a bad or incomplete key. The API itself is fine; sign the request with complete credentials from the account the API trusts.

What is the difference between an AKIA key and an ASIA key?

AKIA keys are long-term access keys for IAM users and travel as a pair. ASIA keys are temporary credentials from STS, Identity Center or a role, and always travel with a session token. An ASIA key sent without its token produces this error.

How do I stop pasting keys every day?

For a person, aws configure sso with an sso-session block, so the CLI refreshes credentials on its own. For anything that runs unattended, a role: an execution role on Lambda, an instance role on EC2, a task role on ECS, or OIDC federation for a CI runner.

If this error is on your screen right now, run the one command before you edit anything, because the fix is almost always in the Location column and not in the key. Jake's script runs every evening again, his shell no longer carries last year's export, and the only time he types anything AWS-related is the browser sign-in the CLI asks for when a session ends. The error never meant what it sounded like. It meant AWS was handed something incomplete, and the complete version was one line away.

📌 If you keep one line from this page

"Security token invalid" means AWS does not recognize the key it was sent. Run aws configure list, complete the three values or replace the dead key, and stop pasting temporary credentials into a form that has no box for them.

Permissions problems say AccessDenied. This one never does.

Revision note. Written October 8, 2026, with the current STS token rules for opt-in Regions and the error names each AWS protocol uses. If a script that ran for months has just started failing with this, the shell it runs in is the first suspect, and the fix is usually one line.

Related