AWS

The security token included in the request is expired

Temporary credentials have a lifetime and yours ran out. How to tell which credential source is stale, and stop hardcoding short-lived keys in the first place.

easy fix5 min read

the aws error
An error occurred (ExpiredToken) when calling the ListBuckets operation: The provided token has expired.

An error occurred (RequestExpired) when calling the DescribeInstances operation: Request has expired.

Error: error configuring S3 Backend: no valid credential sources for S3 Backend found.

ExpiredTokenException: The security token included in the request is expired

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Find out which credential source is being used

    aws configure list

    The Type column says where each value came from: env, shared-credentials-file, or iam-role. Refreshing the wrong source is the most common reason this error persists after a re-login.

  2. 2

    Refresh the session

    aws sso login --profile prod && aws sts get-caller-identity --profile prod

    For SSO this re-authenticates. For a manually assumed role, re-run the assume-role call. get-caller-identity confirms the new credentials work before you retry the real command.

  3. 3

    Clear stale environment variables that override everything else

    unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

    Environment variables take precedence over profiles and instance roles, so an expired AWS_SESSION_TOKEN exported hours ago silently shadows a perfectly good profile.

All 9 sections

Temporary credentials expire. That is the point of them. ExpiredToken means the STS session behind your call has passed its lifetime.

Credential sourceDefault lifetime
IAM Identity Center (SSO)8 hours, configurable
sts:AssumeRole1 hour, up to the role's maximum
GetSessionToken with MFA12 hours
EC2 instance profileRotated automatically
ECS task roleRotated automatically
IRSA on EKSRotated automatically

The last three refresh themselves, so if you are seeing this on an instance, something is bypassing the metadata service.

Which source are you actually using?

aws configure list
      Name                    Value             Type    Location
   access_key     ****************WXYZ              env    AWS_ACCESS_KEY_ID
   secret_key     ****************abcd              env    AWS_SECRET_ACCESS_KEY
       region                eu-west-1      config-file    ~/.aws/config

Type env is the answer in a large share of cases. Environment variables win over everything, so an AWS_SESSION_TOKEN exported this morning shadows the profile you just refreshed, and re-running aws sso login appears to do nothing.

unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

The precedence order, highest first: environment variables, then the CLI's --profile, then AWS_PROFILE, then the default profile, then container credentials, then the instance metadata service.

Refresh it

# IAM Identity Center
aws sso login --profile prod

# Manually assumed role
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/deploy \
  --role-session-name deploy-$(date +%s)

Confirm before retrying the real thing:

aws sts get-caller-identity --profile prod

Stop assuming roles by hand

Configure the profile to do it, and the SDK refreshes automatically when the session expires:

# ~/.aws/config
[profile prod]
sso_session = acme
sso_account_id = 123456789012
sso_role_name = Developer
region = eu-west-1

[profile deploy]
source_profile = prod
role_arn = arn:aws:iam::123456789012:role/deploy
duration_seconds = 3600

[sso-session acme]
sso_start_url = https://acme.awsapps.com/start
sso_region = eu-west-1
sso_registration_scopes = sso:account:access

Now aws --profile deploy s3 ls assumes the role when needed and renews it silently. Exporting the output of assume-role into environment variables is what creates a credential that goes stale in your shell with nothing to refresh it.

Longer sessions

The default assume-role duration is one hour. Raise the role's maximum, then ask for it:

aws iam update-role --role-name deploy --max-session-duration 14400
duration_seconds = 14400

Note role chaining caps at one hour regardless of the maximum. If you assume role A and then assume role B from A, the second session cannot exceed an hour and duration_seconds above that is rejected. This surprises people building nested assume-role setups for a long deployment.

RequestExpired is a different thing

An error occurred (RequestExpired): Request has expired.

This is clock skew, not an expired session. Every signed AWS request carries a timestamp, and the service rejects anything more than five minutes out.

timedatectl status
sudo systemctl restart systemd-timesyncd

Long-suspended laptops and VMs restored from snapshots are the usual sources. Inside a container, the clock comes from the host, so fix it there.

In CI, use OIDC

Static access keys in CI secrets are the thing this error should push you away from. Every major CI platform can exchange an OIDC token for a short-lived AWS role session, with no stored credentials at all:

# GitHub Actions
permissions:
  id-token: write
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
      aws-region: eu-west-1

Credentials are minted per job and expire when it ends. Nothing to rotate, nothing to leak.

Terraform and the SDK

Error: error configuring S3 Backend: no valid credential sources found

Terraform reads the same chain. It also caches backend credentials at init time, so a very long apply can outlive the session it started with. For long applies, raise duration_seconds rather than re-running init partway through.

A checklist

  1. aws configure list and read the Type column.
  2. Type envunset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN.
  3. aws sso login --profile <p>, then aws sts get-caller-identity to confirm.
  4. Configure role_arn and source_profile so the SDK refreshes for you.
  5. Need longer → raise the role's --max-session-duration and set duration_seconds.
  6. Role chaining is capped at one hour whatever you configure.
  7. RequestExpired is clock skew. Check NTP.
  8. In CI, replace static keys with OIDC.

Frequently Asked Questions

Why does the error persist after I log in again?

Because stale environment variables are overriding your refreshed profile. AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN sit at the top of the credential chain, so an AWS_SESSION_TOKEN exported hours ago shadows whatever aws sso login just wrote. aws configure list shows the source of each value in its Type column. Unset all three and try again; this single step resolves most cases where a re-login appears to have had no effect.

What is the difference between ExpiredToken and RequestExpired?

ExpiredToken means the STS session behind your credentials has passed its lifetime and you need new credentials. RequestExpired means the signed request's timestamp is more than five minutes away from AWS's clock, so it is a time synchronisation problem on your machine rather than a credential one. Check with timedatectl status and restart your NTP service. Laptops resumed from long suspends and VMs restored from snapshots are the usual causes.

How do I get sessions that last longer than an hour?

Raise the role's maximum with aws iam update-role --role-name <name> --max-session-duration 14400, then request it via duration_seconds in the profile or --duration-seconds on the CLI call. The important exception is role chaining: if you assume one role and then assume a second from it, the second session is capped at one hour regardless of the role's maximum, and asking for more is rejected outright. Restructuring to assume the target role directly avoids that cap.

Why do my EC2 instances never hit this?

Because an instance profile's credentials are delivered by the instance metadata service and the SDK refreshes them automatically before they expire. The same is true of ECS task roles and IRSA on EKS. If you do see ExpiredToken on an instance, something is bypassing that mechanism, almost always hardcoded environment variables or a credentials file baked into an AMI. Remove them and let the metadata service supply credentials.

What is the best way to handle AWS credentials in CI?

OIDC federation, which every major CI platform now supports. The platform issues a signed identity token for the job, AWS exchanges it for a short-lived role session, and no long-lived access key is stored anywhere. Credentials are minted per job and expire when it finishes, so there is nothing to rotate and nothing useful to leak. It also gives you per-workflow conditions in the trust policy, so a role can be restricted to one repository and even one branch.

Reference and practice

Learn the underlying concept

Other AWS errors