Fix AWS CLI ExpiredToken

Package aws-cli/2.37.3
AWS Region ap-south-1 (Mumbai) for the examples. IAM is global; AWS STS supports global and Regional endpoints, and AWS recommends Regional STS endpoints.
Lab environment Workstation with AWS CLI v2 and configured credentials — AWS CLI tutorial
Install AWS CLI on Ubuntu · Install AWS CLI on Debian
Privilege Access required by the credential source being refreshed. GetSessionToken itself does not require an IAM permission; AssumeRole requires the target role to trust the caller and any required caller-side permission to assume that role; IAM Identity Center and aws login require their respective sign-in access.
Scope Why AWS returns ExpiredToken when temporary STS credentials are past their expiration time, how to read aws configure list to see whether environment variables, a named profile, AssumeRole cache, or login/SSO session supplied the keys, and how to refresh each source. Does not cover rejected or unknown credentials (InvalidClientTokenId) or missing credentials (Unable to locate credentials).
Related guides AWS CLI Tutorial
Fix AWS CLI InvalidClientTokenId
Fix Terraform AWS ExpiredToken
Fix AWS CLI Unable to locate credentials
AWS Tutorial
Short on time?Beta Get the gist of this 12-minute read in seconds, then ask follow-up questions

ExpiredToken means the AWS CLI is using temporary credentials that have passed their expiration time. The credentials may have come from STS, an assumed role, IAM Identity Center, aws login, or exported environment variables. The fix is to refresh the source that created those credentials, not to change IAM permissions.

Start with aws configure list to find which credential provider the failing shell is actually using. The walkthrough uses isolated credential files and short-lived sts get-session-token sessions so each failure mode is visible without touching production profiles. Editing duration_seconds in a profile or rotating a long-term access key does not extend a session that was already issued.


What ExpiredToken means

ExpiredToken is an authentication failure on temporary credentials. AWS accepted the request format but the session token is past its expiration time. HTTP status is 403 Forbidden, and the error code is not AccessDenied.

Common message:

text
An error occurred (ExpiredToken) when calling the GetCallerIdentity operation: The security token included in the request is expired

That differs from InvalidClientTokenId, where the credential bundle is rejected as invalid, and from Unable to locate credentials, where the CLI never resolved signing material. See Fix AWS CLI InvalidClientTokenId when the error names an invalid token rather than an expired one.

Error code Meaning Typical next step
ExpiredToken Temporary session past expiration Refresh SSO, login, role, or exported session credentials
InvalidClientTokenId Credential or token rejected as invalid Run aws configure list; fix stale keys or incomplete triplets
SignatureDoesNotMatch (with clock wording) Signature or clock skew Sync NTP; then refresh credentials if needed
AccessDenied Identity authenticated; action denied Read IAM policies — not an ExpiredToken case
Unable to locate credentials No credential chain Configure a profile or export keys

Temporary access key IDs use the ASIA prefix. They are valid only with the matching secret key and session token until the expiration timestamp AWS returned when the session was created.

                ExpiredToken
                    ↓
          aws configure list
                    ↓
  ┌──────────┬──────────┬──────────┬──────────┐
  │          │          │          │          │
 env ASIA?  role prof  SSO/login  EC2 role?
  │          │          │          │
  ↓          ↓          ↓          ↓
 unset +   refresh    aws sso     unset stale
 re-export source     login or    env; let
           profile    aws login   IMDS refresh
  │          │          │          │
  └──────────┴──────────┴──────────┴──────────┘
                    ↓
        aws sts get-caller-identity

Increasing duration_seconds changes the requested length of future AssumeRole sessions. It does not extend an AssumeRole credential set that has already been issued or exported. IAM Identity Center and aws login session lifetimes are managed separately by those authentication mechanisms.


Find which temporary credentials are active

Run aws configure list in the same shell where the failing command runs. The TYPE and LOCATION columns show whether environment variables, a profile, or assume-role resolution supplied the keys.

bash
aws configure list

When temporary keys come from the environment, LOCATION shows env for the access key row:

output
NAME       : VALUE                    : TYPE             : LOCATION
profile    : <not set>                : None             : None
access_key : ****************3VHR     : env              :
secret_key : ****************xs48     : env              :
region     : ap-south-1               : env              : ['AWS_REGION', 'AWS_DEFAULT_REGION']

For an assume-role profile, TYPE shows assume-role after the CLI resolves role credentials:

bash
aws configure list --profile glc-expired-lab-role
output
NAME       : VALUE                    : TYPE             : LOCATION
profile    : glc-expired-lab-role     : manual           : --profile
access_key : ****************ZQZH     : assume-role      :
secret_key : ****************Y3vL     : assume-role      :
region     : ap-south-1               : env              : ['AWS_REGION', 'AWS_DEFAULT_REGION']

List credential-related environment variables when LOCATION is env or when a script exported credentials earlier in the day:

bash
env | grep '^AWS_'

Look for:

  • AWS_ACCESS_KEY_ID starting with ASIA
  • AWS_SESSION_TOKEN set from exported credentials, a CI job, or a prior get-session-token run
  • AWS_PROFILE pointing at a profile whose underlying SSO or login session expired

Check expiration on a session you created with get-session-token or assume-role by reading the Expiration field in the JSON response. AWS does not store that timestamp in aws configure list; inspect the CLI cache when debugging role sessions.

Cached credential locations by source:

  • ~/.aws/cli/cache — AssumeRole sessions
  • ~/.aws/sso/cache — IAM Identity Center authentication tokens
  • ~/.aws/login/cache — aws login sessions (CLI v2.32+)

Refresh IAM Identity Center and login credentials

IAM Identity Center profiles use a cached SSO session to obtain short-lived AWS role credentials. The CLI refreshes those AWS credentials automatically while the SSO session remains valid. When the SSO session itself expires or becomes invalid, sign in again:

bash
aws sso login --profile corp-sso

The SSO authentication token lives under ~/.aws/sso/cache. When that session expires, the next API call may return ExpiredToken or a refresh error such as:

text
refresh cached SSO token failed, failed to refresh cached credentials, the SSO session has expired or is invalid

Recommended ~/.aws/config layout uses a shared sso-session block:

ini
[profile corp-sso]
sso_session = corp-sso
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = ap-south-1

[sso-session corp-sso]
sso_start_url = https://my-org.awsapps.com/start
sso_region = ap-south-1
sso_registration_scopes = sso:account:access

AWS CLI v2.32 and later also supports aws login for Management Console–backed workstation sessions. Cached credentials and refresh metadata live under ~/.aws/login/cache; the CLI refreshes temporary credentials while the refresh token remains valid. When the login cache is missing or expired, the CLI may report:

bash
aws sts get-caller-identity
output
aws: [ERROR]: Error loading login session token: Unable to load a existing login session for session arn:aws:iam::123456789012:root, Please reauthenticate with 'aws login'.

This is not an STS ExpiredToken response—the local aws login session is missing or expired. Sign in again, then retry the AWS command:

bash
aws login

After SSO or login refresh, confirm identity with the profile in use:

bash
aws sts get-caller-identity --profile corp-sso

Terraform workflows that export credentials into the shell should refresh the login or SSO session first, then export only the credentials that profile resolves to; see Fix Terraform AWS ExpiredToken for the provider-side error text.


Refresh AssumeRole credentials

Assume-role profiles in ~/.aws/config use a source_profile (or another credential source) to call sts:AssumeRole. The CLI caches the resulting temporary credentials under ~/.aws/cli/cache.

While the cache entry is valid, calls succeed. When cached role credentials expire, the CLI tries to assume the role again using the source profile. If the source session is still valid, refresh is silent. If the source credentials expired, the next call returns ExpiredToken.

Example profile chain:

ini
[profile glc-expired-lab-session]
aws_access_key_id = ASIAIOSFODNN7EXAMPLE
aws_secret_access_key = ***
aws_session_token = ***

[profile glc-expired-lab-role]
role_arn = arn:aws:iam::123456789012:role/glc-expired-lab-role
source_profile = glc-expired-lab-session
region = ap-south-1
duration_seconds = 900

When the source session profile holds expired ASIA keys, the role profile cannot assume the role. Even aws configure list may surface the failure while resolving credentials:

bash
aws configure list --profile glc-expired-lab-role
output
aws: [ERROR]: An error occurred (ExpiredToken) when calling the AssumeRole operation: The security token included in the request is expired
NAME       : VALUE                    : TYPE             : LOCATION
profile    : glc-expired-lab-role     : manual           : --profile

Refresh the source first. Issue a new session token on a long-term profile, update the [glc-expired-lab-session] block with the new access key, secret key, and session token from the JSON output, then call the role profile again:

bash
aws sts get-session-token --profile glc-expired-lab-base --duration-seconds 900

Update the [glc-expired-lab-session] block with the new access key, secret key, and session token from the JSON output, then verify:

bash
aws sts get-caller-identity --profile glc-expired-lab-role
output
{
    "UserId": "AROAU7QAIUYJEH4KAMJLO:botocore-session-1790529898",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/glc-expired-lab-role/botocore-session-1790529898"
}

Clearing ~/.aws/cli/cache forces a fresh AssumeRole call after the source credentials are valid again. Deleting cache files alone does not extend an expired session.


Remove stale environment variables

Many teams export credentials once and reuse the shell for hours. When the underlying STS session ends, exported AWS_* variables still point at expired material.

Reproduce the failure with only environment credentials and no shared-credentials fallback:

bash
export AWS_ACCESS_KEY_ID='ASIA...'
export AWS_SECRET_ACCESS_KEY='***'
export AWS_SESSION_TOKEN='***'
aws sts get-caller-identity

After expiration, the same exports return:

bash
aws sts get-caller-identity
output
aws: [ERROR]: An error occurred (ExpiredToken) when calling the GetCallerIdentity operation: The security token included in the request is expired

Remove the stale triplet before refreshing:

bash
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

Obtain a new session from a long-term profile (only valid when the profile uses IAM user access keys, not SSO or assume-role-only profiles). Export the three values from the GetSessionToken response—not from aws configure export-credentials on the long-term profile, which resolves static AKIA keys instead of the STS session you just created:

bash
read AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN < <(
  aws sts get-session-token --profile glc-expired-lab-base --duration-seconds 900 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' --output text
)
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

The environment now holds the new STS credential triplet returned by GetSessionToken.

After a fresh session is in the environment, identity checks succeed again:

bash
aws sts get-caller-identity
output
{
    "UserId": "AIDAU7QAIUYJA7FMGBFY7",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/glc-expired-lab-user"
}

If AWS_PROFILE is set alongside exported variables, remember that environment variables override profile credentials for the default chain. Unset the exports when switching back to a named profile.


Check clock only if signature errors also appear

If the error code is specifically ExpiredToken, refresh the temporary credentials first. Clock problems more commonly appear as request or signature timing failures. Check NTP only when the error also mentions request time, signature expiry, or clock skew:

bash
timedatectl status

If System clock synchronized is no, enable NTP or run chronyc makestep, then refresh credentials. NTP sync alone does not replace renewing an expired STS session when AWS returns ExpiredToken.


Verify with STS

aws sts get-caller-identity confirms which principal the CLI sends after a refresh. Run it in the same shell, with the same AWS_PROFILE and environment variables as the failing command.

bash
aws sts get-caller-identity

When authentication succeeds, the response includes Account, UserId, and Arn. When temporary credentials are still expired, the call returns ExpiredToken again — the refresh step did not reach the provider the CLI actually uses.

After identity checks pass, retry the original command (aws s3 ls, terraform plan, or another API call). If the error changes to AccessDenied, authentication succeeded and authorization deserves attention on a different page.


Troubleshooting

Symptom Likely cause Fix
ExpiredToken right after lunch in the same terminal Exported AWS_SESSION_TOKEN from an earlier session unset the three env vars; issue a new STS session and export its Credentials fields
ExpiredToken in AWS CLI on an EC2 instance that has an IAM role Stale exported temporary credentials override the auto-refreshing instance-role credentials Run aws configure list; if LOCATION shows env, unset the three credential variables and retry
Role profile fails; source profile uses ASIA keys Source session expired; AssumeRole cannot refresh Renew source session credentials, then call the role profile
aws configure list shows env but profile was refreshed Stale environment override unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
SSO refresh error mentioning cached SSO token IAM Identity Center portal session expired aws sso login --profile <name>
Please reauthenticate with 'aws login' CLI login cache cleared or expired aws login
Rotated AKIA key; still ExpiredToken Long-term key rotation does not renew existing STS sessions Refresh the temporary session, not the static key
Error changed to InvalidClientTokenId Incomplete or mismatched triplet after partial unset Export all three fields or use InvalidClientTokenId guide

Normally you should not manually copy EC2 instance-role credentials into environment variables. The instance metadata credential provider refreshes role credentials automatically; exported copies do not.


Summary

ExpiredToken means the temporary credential set reached its STS expiration time. The CLI is still sending ASIA keys and a session token, but AWS will not honor them until the underlying session is renewed.

aws configure list shows whether environment variables, an assume-role profile, or a login/SSO-backed profile supplied the keys. Refresh the source of the temporary credentials — IAM Identity Center login, aws login, a session profile used by AssumeRole, or exported environment variables — rather than editing static access keys or bumping profile duration for a session that was already issued.

After refresh, aws sts get-caller-identity confirms the principal AWS sees. When the error flips to AccessDenied, authentication succeeded and IAM authorization is the next layer to inspect.


References


Frequently Asked Questions

How long do STS credentials last?

GetSessionToken credentials created from long-term IAM-user credentials can last from 15 minutes up to 36 hours (default 12 hours). For root or account credentials, the maximum is one hour. AssumeRole sessions normally last from 15 minutes up to the target role's configured maximum, which can be as high as 12 hours; role chaining is limited to one hour. In every case, the issued credentials have a fixed expiration timestamp—changing a profile later does not extend an existing session.

Does rotating an access key fix ExpiredToken?

No. Rotating a long-term AKIA access key replaces static credentials. ExpiredToken applies to an existing temporary session (ASIA plus session token). Issue a new STS session or refresh SSO/login instead.

Why does a profile keep using old credentials?

Environment variables override profile credentials when they are set. A profile can look correct in ~/.aws/credentials while the shell still exports an expired triplet from an earlier job. Run aws configure list and env | grep '^AWS_' in the failing shell; unset stale exports before testing the profile.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)