Fix AWS CLI ExpiredToken Error: Session Credentials Guide
The AWS CLI throws ExpiredToken — "The security token included in the request is expired" when the temporary session credentials it's using (an access key, a secret key, and a session token, bundled together) have passed their expiration timestamp. You fix it in the same place those credentials came from: run aws sso login again, re-run aws sts assume-role, or, on an EC2 instance, just wait a few minutes because the instance role refreshes itself. Here's the part almost nobody expects: fixing the real source often does not fix the error, because a stale AWS_SESSION_TOKEN environment variable sitting in your terminal keeps overriding the fresh credentials you just generated — environment variables always beat profile files, no matter how recently you updated the file.
Jake locked himself out of his own trade-in tracker on a Saturday morning — the one script that pulls the day's phone trade-in values from an S3 bucket so his till doesn't undersell a customer's old iPhone. It had worked all week. Then it threw a wall of red text and just stopped.
"It says my token expired," he told Ethan over the phone. "I didn't touch anything. I didn't even open my laptop yesterday." That's the thing about this error — it rarely means you did something wrong. It usually means time did something to you, quietly, in the background, while you were doing something else entirely.
What "ExpiredToken" actually means (it's not a bug)
Every request the AWS CLI sends is signed with credentials, and AWS gives you two very different flavors of them. Understanding which flavor you're using is the whole ballgame, so let's slow down here.
Long-term credentials are the classic access key ID and secret access key you get from an IAM user. IAM stands for Identity and Access Management — it's the AWS service that keeps track of who's allowed to do what in your account, the same way a shop's staff list says who's allowed to open the till and who isn't. An IAM user is basically one named staff member on that list. They don't expire on their own — they sit there until someone deactivates or deletes them. If you're using only these two values, "ExpiredToken" should never happen, and if it does, something else is going on (we'll get to that in the section on deactivated keys).
Temporary credentials are a three-part set: an access key ID, a secret access key, and a session token — a long string that looks like gibberish but is really a timestamped, signed proof that says "this access is valid until X." AWS Security Token Service (STS) is the service that hands these out, through operations like AssumeRole, GetSessionToken, and the sign-in flow behind aws sso login. The session token is the part that carries an expiration. Once the clock passes that timestamp, every single request signed with it comes back with the exact error: "An error occurred (ExpiredToken) when calling the [Operation] operation: The security token included in the request is expired."
♂️ Jake's Reality Check
"So is my access key broken? Do I need a new one?"
Almost never. If you're seeing ExpiredToken, in the overwhelming majority of cases your long-term access key is fine. What expired was a temporary session token layered on top of it — and that layer gets created fresh every time you log in or assume a role. You don't repair it, you regenerate it.
Ethan put it to Jake this way: "Think of your access key as your house key — it doesn't expire. The session token is more like a visitor's day-pass a security guard hands you at the front desk. It says 'valid until 4pm.' At 4:01, the guard doesn't care that your house key still works. The pass is done. You don't argue with the pass, you just walk back up to the desk and ask for a new one."
Step 1: Find out which credentials the CLI is actually using
This is the step almost every guide skips, and it's the one that actually matters, because the AWS CLI can pull credentials from five different places, and it checks them in a strict order. If you fix the wrong one, nothing changes — the CLI never even looks at the place you just fixed.
The AWS CLI checks these sources in this order, and the first one it finds wins completely:
| Priority | Source | Why it silently wins |
|---|---|---|
| 1 (highest) | Command-line options (--profile, explicit flags) |
You typed it directly on this exact command |
| 2 | AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN environment variables |
Overrides every profile file, even a profile named on the command line |
| 3 | CLI credentials file (~/.aws/credentials) |
Only checked once nothing above it exists |
| 4 | CLI config file (~/.aws/config) — SSO or role profiles |
Where sso_session and role_arn profiles live |
| 5 (lowest) | EC2 instance metadata / ECS task role / container credentials | Auto-managed, only used if nothing else is set anywhere |
That row #2 is the one that eats Saturdays. If you ever ran export AWS_SESSION_TOKEN=... in a terminal — maybe following a tutorial, maybe pasting output from an assume-role call three weeks ago — that variable stays alive in that shell session until you close the terminal, log out, or unset it by hand. Every AWS CLI command in that window keeps using it, silently, forever, ignoring your profile file completely. You can fix your SSO login, refresh your profile, do everything right, and still get ExpiredToken, because the CLI never even glanced at the fix. It found the environment variable first and stopped looking.
Check for this before anything else. Run these three commands and read the output carefully:
- On macOS or Linux, run
echo $AWS_SESSION_TOKEN. On Windows PowerShell, runecho $env:AWS_SESSION_TOKEN. If anything at all prints out — even a short string — that is the problem for most people who reach this step. - Clear it: on macOS/Linux,
unset AWS_SESSION_TOKEN AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY. On Windows PowerShell,Remove-Item Env:\AWS_SESSION_TOKEN,Remove-Item Env:\AWS_ACCESS_KEY_ID,Remove-Item Env:\AWS_SECRET_ACCESS_KEY. - Run
aws sts get-caller-identityagain. If it now works, your profile file was fine the entire time and the environment variable was the actual bug.
✅ Why this is the fix to try first
It costs nothing, it can't break anything, and it explains the single most common version of this exact complaint: "I already fixed my credentials and it's still broken." Rule it out before you touch anything involving roles, SSO, or IAM policy — those are real fixes for a different, less common cause.
Fix it when you're using IAM Identity Center (aws sso login)
If your ~/.aws/config has a profile block with an sso_session entry, you're using AWS IAM Identity Center (the service that used to just be called AWS SSO). This is the setup most organizations push their whole team onto now, because nobody has to email around long-term access keys.
Here's how the two layers work, because it explains the two different "expired" experiences you can have:
- The SSO session token (cached in
~/.aws/sso/cache) is what proves you signed in through your browser at all. This is the layer your organization's administrator controls, and it commonly lasts around 8 hours by default before you have to open a browser tab again. - The role credentials your CLI actually signs requests with are generated from that SSO session, and they're shorter-lived — often an hour, sometimes less — and the CLI is supposed to refresh those automatically behind the scenes as long as the underlying SSO session token is still valid.
If only the short role credentials expired, the CLI usually renews them without bothering you at all. When you get ExpiredToken with an SSO profile, it almost always means the longer SSO session itself has run out, and there's no silent way past that — a human has to click through the browser prompt again.
- Run
aws sso login --profile your-profile-name(or justaws sso loginif it's your default profile). - A browser window opens, or a URL and a device code print to your terminal if you're on a headless machine. Sign in and approve the request.
- Once you see confirmation in the terminal, retry your original command. Don't retry mid-login — the credentials aren't written to the cache until the browser step actually completes.
Two profile-file details trip people up here. First, an SSO profile needs a matching [sso-session your-session-name] block above it in ~/.aws/config — if you copy just the profile block without the session block, the CLI can't find where to refresh from. Second, if you have multiple SSO profiles pointing at different accounts or roles under the same sign-in, logging in once refreshes the shared session token for all of them; you don't need to run aws sso login once per profile, only once per sso-session name.
What changed with SSO logins
- Before: the original AWS SSO login flow (what people typed as
aws2 sso loginin the early AWS CLI v2 days) had no automatic refresh built in — once the cached session token expired, the CLI would just fail, with no attempt to renew anything on its own, and the person had to notice and re-run the login command by hand. - Now: the
sso-sessiontoken provider configuration lets the CLI automatically retrieve refreshed authentication tokens and generate new short-term role credentials for you, as long as your longer IAM Identity Center session hasn't itself expired. - What that means for you: a browser prompt now (mostly) only interrupts you once the whole SSO session dies, not every time the shorter role credentials underneath it do.
⚠️ What actually breaks people here
If you're chaining a second role assumption on top of your SSO session (SSO gives you Role A, and a script then calls assume-role to become Role B), that second hop is role chaining, and AWS caps role-chained CLI/API sessions at exactly one hour, no matter what duration you request and no matter how generous your administrator's permission-set settings are. We cover this trap in detail further down — it's a completely different fix from a plain SSO re-login.
Fix it when you're using assume-role or manually exported temp keys
This is the path a lot of scripts and CI pipelines use: an admin, or another automated job, calls aws sts assume-role against a specific role ARN — ARN stands for Amazon Resource Name, and it's simply AWS's way of writing a unique address for one specific thing in your account, the same way a street address points at one specific house and no other. A role ARN just means "this exact role, in this exact account, and no other role that happens to share its name." Calling assume-role gets back three values (an access key, a secret key, and a session token), and exports them into the shell or pastes them into a credentials file profile.
The whole set expires together, at the moment written in the Expiration field of the response — by default one hour after you asked for it, unless you passed a longer DurationSeconds value and your role's maximum session duration setting allows it. When that timestamp passes, every one of the three values is dead simultaneously; there's no partial expiry.
The fix is to run the exact same assume-role call again and replace all three values, not just one of them:
aws sts assume-role \ --role-arn arn:aws:iam::123456789012:role/YourRoleName \ --role-session-name jakes-trade-in-script
That returns a JSON block with AccessKeyId, SecretAccessKey, and SessionToken. All three go together into either your environment variables or a named profile in ~/.aws/credentials — swapping in only the new session token while leaving an old access key sitting there produces a mismatched signature error, not a clean fix.
If you're pasting these by hand more than once a day, that's the actual problem, and the honest fix isn't a better paste routine — it's automating the call so a script does the assume-role and re-export for you, which we cover a few sections down.
The role-chaining hour cap nobody can raise
Here's the sharpest, least-documented fact in this whole topic, and it deserves its own section because it wastes hours of debugging time when nobody knows about it.
Role chaining means you used one set of temporary credentials to assume a second role. Maybe your SSO login gives you a base role, and a script then calls assume-role from that base role into a more specific one. AWS's own documentation is blunt about the limit: when you assume a role using role chaining, your AWS CLI or AWS API session is capped at one hour, period — even if you explicitly pass a DurationSeconds of 12 hours, and even if the role's own maximum-session-duration setting is configured for 12 hours. The request doesn't get quietly clamped down to one hour either; if the requested duration is over an hour on a chained assumption, the call fails outright.
This limit applies specifically to CLI and API calls — a console "switch role" click defaults to one hour too, but that's a separate mechanism. And it doesn't apply to a single, non-chained assumption: an ordinary IAM user or root credentials assuming one role directly can still get up to the role's configured maximum (up to 12 hours) in a single hop.
| Credential type | Default duration | Maximum you can request |
|---|---|---|
Single AssumeRole (not chained) |
1 hour | Up to the role's max session duration setting, itself capped at 12 hours |
Chained AssumeRole (role assuming a role) |
1 hour | Hard cap of 1 hour — requesting more fails the call |
GetSessionToken with an IAM user |
12 hours | 36 hours |
GetSessionToken with root account credentials |
1 hour | 1 hour — anything higher silently drops to 1 hour |
| EC2 instance profile role | Managed automatically | Rotated for you; new set available at least 5 minutes before old one expires |
Ethan doesn't hide his opinion on this one: "If your pipeline is built around role chaining and needs sessions longer than an hour, you've picked the wrong shape for the job. Restructure the trust policy so the automation assumes the target role directly, in one hop, from a base identity with a longer max session duration. Fighting the one-hour cap with retries and workarounds is treating a documented limit like a bug."
Fix it on an EC2 instance, ECS task, or Lambda function
If your script runs on an EC2 instance with an IAM role attached, the AWS CLI pulls temporary credentials from the instance metadata service automatically, and AWS documents that it rotates those credentials for you, making new ones available at least five minutes before the old set expires. In the normal case, you should never see ExpiredToken here at all — the SDK and CLI are supposed to fetch fresh metadata credentials before the old ones die.
When it happens anyway, the cause is almost always one of these three things, in order of how often they actually occur:
- Something cached the credentials outside the SDK's control. A script read the metadata endpoint once, saved the values to a file or a variable, and kept reusing that saved copy long after the official rotation happened. The instance role itself is fine; the stale copy isn't.
- You're inside a Docker container and the metadata hop limit is too low. By default, the instance metadata service has a hop limit that can prevent requests from a containerized process from reaching it at all once they cross the container network boundary, which can produce credential failures that look identical to expiry. Raising it fixes containers reaching the host's metadata service:
aws ec2 modify-instance-metadata-options --instance-id your-instance-id --http-put-response-hop-limit 2 --http-endpoint enabled. - Environment variables again. The same ghost-variable problem from earlier applies just as much on a server as on a laptop — if a deploy script or a
.bashrcexported static AWS keys once, they permanently shadow the instance role, and once those old keys are deactivated, or their own temp token dies, the instance's live, working, auto-rotating role credentials never get a chance to be used.
♂️ Jake's Reality Check
"My server ran fine for two weeks and then just started failing every few hours. Nothing changed on my end."
Ethan's answer: "Something changed on AWS's end — the automatic rotation kicked in for the first time once the original credentials issued at launch finally reached expiry, and whatever pulled them originally only fetched once instead of re-checking. The instance role itself almost certainly isn't broken. Whatever code reads it is reading it once and holding on too long."
When you're using only long-term access keys and this still happens
This is the confusing case, because a plain access key and secret key have no session token and shouldn't expire on their own. If you're genuinely only using those two values — no SSO, no assume-role, no exported session token — and you're still seeing ExpiredToken, work through these in order:
Check your system clock
AWS requires every signed request to carry an accurate timestamp, and if your computer's clock is wrong, requests can be rejected because the signature doesn't match what AWS expects for that moment in time. AWS's own guidance on this exact family of errors is direct: make sure your machine's clock is accurate, because credential and signature checks are time-based, and a wrong local clock produces failures that read like an expiry even when the credentials themselves are fine. On Windows, right-click the clock in the taskbar, choose "Adjust date/time," and turn on "Set time automatically." On Linux, check with timedatectl status and enable NTP sync if it's off.
Worth knowing so you don't chase the wrong lead: a wrong clock more often shows up under a different error name entirely, not ExpiredToken. If you see something wrong-clock-shaped, it's usually labeled a bit differently — a signature mismatch, or a complaint that your request time is too far from the current time — because that's a different check than the one that reads a credential's own expiration timestamp. Either way, the fix is identical: get the clock right, then retry.
Rule out a deactivated or deleted key
A deactivated access key doesn't usually surface as ExpiredToken — more often you'll see InvalidClientTokenId or an access-denied style message instead — but it's worth ruling out, because the underlying feeling ("this used to work and now it doesn't") is identical. Open IAM > Users > your user > Security credentials in the console, and confirm the access key ID you're using still shows as Active, not Inactive.
Check whether something layered temporary credentials on top without telling you
Some setups mix the two credential types without the person running the command realizing it — a credentials file profile with aws_session_token left over from a previous assume-role paste, sitting right below a perfectly good aws_access_key_id and aws_secret_access_key. Open ~/.aws/credentials in a text editor and look for a leftover aws_session_token line under the profile you're using. If it's there and stale, delete just that line; the access key and secret key underneath it will keep working fine on their own.
How to see exactly which credentials and expiry the CLI is using, right now
Instead of guessing, ask the CLI directly. Two commands do this:
aws sts get-caller-identity
If this succeeds, whatever credentials you're currently using are valid right now — it's the single fastest health check, and it tells you the account ID and IAM identity behind the credentials, which is often enough to spot that you're signed in as the wrong role entirely.
aws configure export-credentials --profile your-profile-name
This prints the exact access key, secret key, session token (if any), and expiration timestamp the CLI would use for that profile, resolved through every layer — SSO, assumed role, or plain static keys — without actually running an API call. It's the most direct way to confirm the expiry time you're fighting against, and to see plainly whether a session token exists at all for that profile.
Stop fighting this by automating the refresh
If you're re-running the same login or assume-role command more than once a day, the honest advice isn't a faster way to type it — it's removing the human from the loop entirely.
For interactive work, the automatic token refresh built into the SSO token provider configuration is the intended fix: once you've run aws configure sso and set up an sso-session block, the AWS CLI is designed to refresh the shorter role credentials on its own for as long as the underlying SSO session token is still valid, so you only see the browser prompt when that longer session itself has actually run out — not every time the shorter-lived role credentials expire.
For scripted or scheduled work that needs to assume a role without a human present, credential_process is the documented mechanism: a line in your profile that points at a script or command, and the CLI runs it automatically whenever it needs credentials, expecting that script to return fresh values in a specific JSON shape including the expiration timestamp. This is the right shape for a cron job or a long-running service that needs to keep assuming the same role without anyone pasting values by hand.
✅ Why this is the one to use for anything recurring
Every manual paste is a future ExpiredToken waiting to happen at an inconvenient moment. If you're touching this more than once, automate it once instead of debugging it forever.
ECS tasks, Lambda, and containers — the edge cases
An ECS task with a task role, or a Lambda function with an execution role, both get temporary credentials through a container credentials endpoint rather than the raw EC2 instance metadata address, and both are meant to be refreshed automatically by the AWS SDK layer underneath your code — you generally shouldn't be touching a session token by hand in either environment at all. If you're seeing ExpiredToken inside a container, check first whether your image or your code has any hardcoded, exported, or cached credentials baked in from a build step or a base image, because that's the same shadowing problem as the environment-variable case, just harder to spot because it's inside a container instead of a terminal.
If you're running the AWS CLI inside a Docker container on top of an EC2 host and relying on the host's instance role, remember the hop-limit point from the EC2 section above — a container is, by default, one extra network hop away from the metadata service compared to a process running directly on the host, and the default hop limit was set with that boundary in mind. If credentials work fine when you SSH into the instance directly but fail inside the container, that hop limit is the first thing to check.
One more detail worth knowing if you're troubleshooting IMDSv2 specifically: the token you request from the metadata service (the one you pass as the X-aws-ec2-metadata-token header) has its own separate time-to-live, set with the X-aws-ec2-metadata-token-ttl-seconds header when you first request it. That TTL is unrelated to the expiry on the actual IAM role credentials you fetch afterward — it's just how long the metadata-service token itself stays valid for making further metadata requests. If a script requests that token once with a short TTL and then sits idle for a long time before trying to use it again, it can fail for a completely separate reason that looks, from the outside, exactly like the credentials themselves expiring.
♂️ Jake's Reality Check
"I don't even know if my little price-checker function on Lambda counts as 'a container.' Do I need to worry about any of this?"
Ethan: "No. Lambda manages all of this for you — you're not calling the metadata service yourself, and you shouldn't try to. If a Lambda function throws ExpiredToken, look at your own code first: are you creating an AWS SDK client once and holding onto it, then pasting in an access key and session token you grabbed manually at some point? That's the only way this usually shows up on Lambda. Left alone, the execution role just works."
Running the AWS CLI from Windows, WSL, or a VM — what's different
Everything above applies identically whether you're on Windows, macOS, or Linux — the AWS CLI's credential resolution logic doesn't change by operating system. The practical wrinkle is that Windows PowerShell, Windows Command Prompt, and WSL (Windows Subsystem for Linux) each keep their own separate environment variables and, depending on how you set things up, their own separate ~/.aws folder. If you set AWS_SESSION_TOKEN in a PowerShell window and then open a WSL terminal to run the same script, WSL won't see that PowerShell variable at all — but it might have its own leftover one from a previous session, exported in its own .bashrc. Jake hit exactly this: his trade-in script worked from WSL but failed from a plain Windows PowerShell window on the same laptop, because only the PowerShell session still had a year-old setx-persisted access key sitting in its environment. setx writes environment variables permanently for future sessions on Windows, which is exactly why a value set with it once can keep quietly overriding your profile file for months until someone thinks to check.
If you used setx at some point to set AWS credentials permanently on Windows, and you no longer need them there, remove them through System Properties > Environment Variables, not just by closing the terminal — closing the window does nothing to a value set with setx.
Decision table: match your symptom to the fix
By the time Jake had heard all six causes explained one at a time, he asked the obvious question: "Okay, but which one is my one?" Fair complaint. Here's the whole article flattened into a single table you can scan in ten seconds instead of re-reading six sections to find yourself in them.
| Symptom | Likely cause | Fix |
|---|---|---|
| Fails every command after roughly an hour, every day | Plain SSO or single assume-role, default duration | aws sso login or re-run assume-role |
| Fixed the profile, still fails identically | Stale AWS_SESSION_TOKEN env var overriding the profile | Unset the env vars, retry |
| Chained role, requested 12-hour duration, call fails to even start | Role chaining 1-hour hard cap | Restructure to a single-hop assumption, or accept the 1-hour cap |
| EC2 instance, intermittent, no pattern | Cached metadata credentials reused past rotation | Stop caching; let the SDK re-fetch each time |
| Works on host, fails identically inside a container | IMDS hop limit too low for the container network | modify-instance-metadata-options --http-put-response-hop-limit 2 |
| Only static access keys in use, no SSO, no assume-role | Wrong system clock, or a leftover session token line in the credentials file | Sync the clock; check for a stray aws_session_token line |
When none of this works
Be honest with yourself about two situations this article can't fix from the outside.
First: if aws sts get-caller-identity fails no matter what you try, with an error other than ExpiredToken — something like AccessDenied, InvalidClientTokenId, or a message about your role trust policy — that's not a token-expiry problem anymore, it's a permissions or configuration problem, and no amount of re-logging-in will touch it. At that point the honest next step is checking the IAM policy attached to the role or user, and the role's trust policy if you're assuming into it, not repeating the login flow.
Second: if you don't have access to change the SSO session duration, the role's maximum session duration, or the trust policy causing role chaining, and you're stuck re-authenticating every hour against your will — that's a decision your organization's AWS administrator made, deliberately, usually for a real security reason. You are not going to work around it from the CLI side, and trying tends to produce exactly the confusing, intermittent failures this whole article is about. The right move is asking whoever manages your organization's IAM Identity Center or permission sets whether the session duration can reasonably be extended for your use case, not hunting for a client-side trick.
♂️ Jake's Reality Check
"What if I just don't know if I even have SSO or a regular access key? I inherited this laptop from the last guy who did the books."
The straight answer: open ~/.aws/config in any text editor. If you see a line starting with sso_session or sso_start_url, you're on IAM Identity Center. If you see role_arn without any sso lines, you're on an assumed role. If neither file has anything unusual and ~/.aws/credentials just has a plain access key and secret, you're on static long-term keys and clock skew or a stray session token line is your most likely cause.
The five-minute checklist, in order
Print this bit out, tape it above your desk, whatever it takes. This is the order Jake now runs through every single time, before he lets himself get frustrated about it:
- Run
echo $AWS_SESSION_TOKEN(orecho $env:AWS_SESSION_TOKENon PowerShell). If anything prints, unset all three AWS environment variables and retry immediately. - Run
aws configure export-credentials --profile your-profileto see exactly which credential type and expiration the CLI is resolving to. - If it's an SSO profile, run
aws sso login --profile your-profile. - If it's an assumed role, re-run the same
aws sts assume-rolecall and replace all three exported values together. - If it's on EC2/ECS/Lambda, confirm nothing in your code or deploy scripts is caching credentials, and check the IMDS hop limit if you're inside a container.
- If it's plain static keys with no session token anywhere, sync your system clock and confirm the key is still Active in the IAM console.
- Run
aws sts get-caller-identityto confirm you're back to a healthy, valid identity before retrying your original command.
Jake ran through it in that order on the Saturday morning that started this article. Step one solved it in under thirty seconds — a session token from a tutorial he'd followed two weeks earlier was still sitting in that terminal window, quietly overriding the fresh SSO login he'd already done correctly. "I did everything right," he said, "and the terminal just wouldn't let me use it." Ethan's answer: "The terminal wasn't ignoring you. It was listening to something older that you forgot was still talking."
Frequently asked questions
What does ExpiredToken actually mean in the AWS CLI?
It means the temporary session token attached to the credentials you're using has passed its expiration timestamp. Temporary credentials come from STS operations like AssumeRole, GetSessionToken, or an SSO login, and always come as an access key, secret key, and session token bundled together; once the token's expiry passes, every one of the three stops working at once.
Why do I get ExpiredToken right after I just ran aws configure?
aws configure only ever sets a plain access key ID and secret access key — it never sets a session token field. If you're seeing ExpiredToken right after using it, something else in your environment (usually an environment variable, or a leftover session token line pasted into the credentials file by hand) is layering a temporary credential on top of the static keys you just configured.
I set new access keys and I'm still getting ExpiredToken — why?
Check your shell's environment variables first. AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN always take priority over anything in your credentials file, so a new key saved to a profile file does nothing if an old set is still exported in that terminal session.
How long do AWS CLI temporary credentials actually last?
It depends entirely on how they were issued. A single AssumeRole call defaults to one hour and can go up to the role's configured maximum (up to 12 hours). GetSessionToken with an IAM user defaults to 12 hours and can go up to 36 hours; the same call with root account credentials is capped at one hour no matter what you request. Role-chained sessions are capped at exactly one hour with no exceptions.
Why does aws sso login fix it for a while and then break again?
Because two layers of expiry are stacked: a longer SSO session (often around 8 hours by default) and shorter role credentials generated from it. The CLI is designed to refresh the shorter layer automatically while the longer SSO session is still alive. When you have to run aws sso login again, it means the longer underlying session itself finally ran out.
Can I make my SSO or assumed-role session last longer than an hour?
For a single, non-chained role assumption, yes, up to the role's configured maximum session duration setting, which an administrator can set anywhere from 1 to 12 hours. For a role-chained session, no — that's hard-capped at one hour regardless of any other setting. SSO session length itself is controlled by your organization's IAM Identity Center configuration, not by anything you can change from the CLI.
What is role chaining and why does it cap me at one hour no matter what?
Role chaining is using one set of temporary credentials (say, from an SSO login or a first AssumeRole call) to assume a second, different role. AWS documents this as a deliberate, fixed limit on CLI and API sessions: requesting a duration longer than one hour on a chained assumption causes the call to fail outright, rather than being silently shortened.
Why does ExpiredToken happen on an EC2 instance when I didn't touch anything?
EC2 instance profile credentials rotate automatically, with new credentials made available at least five minutes before the old ones expire, so you normally shouldn't see this at all. When it happens, the most common cause is something in your code or deploy scripts fetching the metadata credentials once and reusing that cached copy long after the rotation happened, instead of letting the SDK re-fetch as designed.
My Docker container on EC2 gets ExpiredToken but the host doesn't — why?
A container is one extra network hop away from the instance metadata service compared to a process on the host directly, and the default IMDS hop limit can prevent that request from completing. Raising the hop limit with aws ec2 modify-instance-metadata-options --http-put-response-hop-limit 2 --http-endpoint enabled resolves this for most containerized setups.
Does a wrong system clock cause ExpiredToken?
It can produce signature and authentication failures that feel identical, because every signed AWS request carries a timestamp and AWS checks that timestamp against its own clock. Keeping your system clock synced (Windows: automatic time setting; Linux: NTP via timedatectl) rules this out as a variable before you spend time on credential-specific fixes.
How do I know which credentials the AWS CLI is actually using right now?
Run aws configure export-credentials --profile your-profile-name. It resolves every layer — environment variables, SSO, assumed role, or static keys — and prints the exact access key, secret key, session token (if any), and expiration the CLI would use, without needing to guess.
Can I automate refreshing credentials so I stop hitting this error?
Yes. For interactive SSO use, setting up the sso-session token provider configuration lets the CLI refresh shorter role credentials on its own between logins. For scripted or scheduled automation that needs to assume a role without a human present, a credential_process entry in your profile lets the CLI call a script that returns fresh credentials automatically whenever they're needed.
Is it safe to just delete my ~/.aws/credentials file and start over?
It's safe in the sense that it won't affect anything on the AWS side — the file only stores local copies of credentials, not the credentials' actual existence in IAM or STS. Deleting it removes any stray or leftover profile entries (including old session token lines), and you simply re-run aws configure, aws configure sso, or your assume-role script afterward to rebuild it cleanly.
Why did deleting and recreating my access key not fix ExpiredToken?
Because a static access key has no expiration to begin with, so recreating one doesn't touch the actual cause. If ExpiredToken persists after a fresh key, the real issue is almost always a leftover session token — in an environment variable or in the credentials file — layered on top of whatever key you're currently using.
What's the difference between ExpiredToken and ExpiredTokenException?
They point at the same underlying cause — an expired temporary session token — but come from different AWS services' error formats; some services return the error code as ExpiredToken and others as ExpiredTokenException, depending on how that particular service's API reports STS-related failures. The fix is identical either way: refresh the session at its source.
Should I ever set a really long DurationSeconds to avoid this forever?
You can raise it up to your role's configured maximum (up to 12 hours for a single assumption, 36 hours for GetSessionToken with an IAM user), but there are hard ceilings you cannot exceed, and a role-chained session cannot go past one hour no matter what value you request. Beyond the technical ceiling, most security teams deliberately keep session durations short as a matter of policy, so the better long-term fix is usually automating the refresh rather than fighting for a longer-lived token.
Revision note. Written September 2026.It will need a look again if AWS changes the default SSO session length, the role-chaining duration cap, or the instance metadata rotation window. If you've been staring at a wall of red ExpiredToken text on a deadline, take a breath — nine times out of ten it's a thirty-second fix, not a broken account.Happy learning!