AWS CLI Setup
Install and configure the AWS CLI properly. SSO instead of access keys, named profiles, the credential chain and safe multi-account use.
The AWS CLI is how you check what is actually true in an account, and it is the tool every other AWS workflow falls back on. Setting it up well, particularly the credentials part, takes about ten minutes and avoids a category of problems later.
Install
Use v2. Version 1 is still installable and lacks SSO support, auto-prompt and several output improvements.
# macOS
brew install awscli
# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscliv2.zip
unzip awscliv2.zip && sudo ./aws/install
# Windows
winget install Amazon.AWSCLI
aws --version
aws-cli/2.17.42 Python/3.12.6 darwin/24.0.0
If that reports aws-cli/1.x, you have v1. Uninstall it first, since having both on the PATH causes confusing behaviour.
Configure with SSO, not access keys
aws configure prompts for an access key and secret. Prefer not to use it. Long-lived access keys sitting in ~/.aws/credentials are the credential most likely to end up somewhere it should not.
If your organisation uses IAM Identity Center, and it should, configure SSO instead:
aws configure sso
SSO session name: acme
SSO start URL: https://acme.awsapps.com/start
SSO region: eu-west-1
Registered client... opening browser
CLI default client Region: eu-west-1
CLI default output format: json
CLI profile name: acme-prod
This writes a profile that fetches temporary credentials through your browser session. Nothing long-lived is stored. When the session expires:
aws sso login --profile acme-prod
For personal accounts with no SSO, aws configure with an access key is the fallback, but create the key on a dedicated IAM user with narrow permissions, and delete it when you stop using it.
Named profiles
One profile per account or role. This is what makes multi-account work manageable:
# ~/.aws/config
[default]
region = eu-west-1
output = json
[profile acme-staging]
sso_session = acme
sso_account_id = 111122223333
sso_role_name = PowerUserAccess
region = eu-west-1
[profile acme-prod]
sso_session = acme
sso_account_id = 444455556666
sso_role_name = ReadOnlyAccess # read-only by default is a good habit
region = eu-west-1
[profile acme-prod-admin]
sso_session = acme
sso_account_id = 444455556666
sso_role_name = AdministratorAccess
[sso-session acme]
sso_start_url = https://acme.awsapps.com/start
sso_region = eu-west-1
sso_registration_scopes = sso:account:access
Note the pattern in those last two: production defaults to read-only, with a separate admin profile used deliberately. That means a mistyped command in the wrong terminal fails at the IAM layer rather than changing production.
Select a profile per command or per shell:
aws s3 ls --profile acme-prod
export AWS_PROFILE=acme-staging
For assuming a role from a base profile:
[profile deploy]
role_arn = arn:aws:iam::111122223333:role/DeployRole
source_profile = acme-staging
mfa_serial = arn:aws:iam::111122223333:mfa/alice
The CLI handles the AssumeRole call and prompts for MFA automatically.
The credential chain
The CLI looks for credentials in this order, and knowing it resolves most "wrong account" confusion:
- Command-line flags,
--profile - Environment variables,
AWS_ACCESS_KEY_ID,AWS_PROFILE ~/.aws/credentials~/.aws/config- Container credentials (ECS task role)
- Instance metadata (EC2 instance profile)
A stale AWS_ACCESS_KEY_ID exported in one terminal overrides your carefully configured profile, which produces genuinely baffling behaviour. When something is wrong, check the environment first:
env | grep AWS
Always confirm your identity
aws sts get-caller-identity
{
"UserId": "AROAX...:alice",
"Account": "444455556666",
"Arn": "arn:aws:sts::444455556666:assumed-role/ReadOnlyAccess/alice"
}
This is the single most useful AWS command. It answers which account and which identity, and nothing else in the CLI output tells you. Run it before anything destructive. Pointing at the wrong account is the most expensive AWS mistake available.
Showing the active profile in your shell prompt is worth setting up if you work across accounts.
Output and querying
aws ec2 describe-instances --output table
aws ec2 describe-instances --output yaml
The --query flag takes a JMESPath expression and filters server-side output before it reaches you, which beats piping gigabytes into jq:
# Running instances, just the useful columns
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=running \
--query 'Reservations[].Instances[].{ID:InstanceId,Type:InstanceType,IP:PrivateIpAddress,Name:Tags[?Key==`Name`]|[0].Value}' \
--output table
# A single value, unquoted. For shell interpolation
aws sts get-caller-identity --query Account --output text
--filters is applied by the AWS API; --query is applied locally by the CLI. Prefer --filters where available, since it reduces what is transferred.
Settings worth enabling
aws configure set cli_pager "" # stop paging every output
aws configure set cli_auto_prompt on-partial # suggest commands as you type
Shell completion:
complete -C '/usr/local/bin/aws_completer' aws # add to ~/.bashrc or ~/.zshrc
The pager setting in particular removes a persistent annoyance, by default v2 pipes output into less, which breaks scripting expectations.
Quick reference
| Task | Command |
|---|---|
| Version | aws --version |
| Configure SSO | aws configure sso |
| Sign in | aws sso login --profile <p> |
| Who am I | aws sts get-caller-identity |
| List profiles | aws configure list-profiles |
| Show current config | aws configure list |
| Use a profile | aws <cmd> --profile <p> or export AWS_PROFILE=<p> |
| Filter output | --query 'Reservations[].Instances[].InstanceId' |
| Raw single value | --query Account --output text |
| Disable the pager | aws configure set cli_pager "" |
Key takeaways
- Use CLI v2, and do not leave v1 on the PATH alongside it.
- Configure SSO rather than access keys. Temporary credentials, nothing long-lived stored.
- One profile per account or role, and make production read-only by default with a separate admin profile.
- Run
aws sts get-caller-identitybefore anything destructive. Nothing else tells you which account you are in. - Environment variables override profiles: a stale export is the usual cause of "wrong account" confusion.
--queryuses JMESPath and filters before output;--filtersfilters server-side and is cheaper still.- Disable the pager with
aws configure set cli_pager "".
Check your understanding
The profile that is being ignored
A command run with --profile acme-staging keeps acting on the production account. What is the most likely cause?
Two profiles for one account
A team configures acme-prod with ReadOnlyAccess and a separate acme-prod-admin with AdministratorAccess for the same account. Why?
Frequently Asked Questions
Should I use aws configure or aws configure sso?
Use aws configure sso if your organisation has IAM Identity Center, which it should. It stores no long-lived credentials. You authenticate through a browser and the CLI receives temporary credentials that expire, refreshed with aws sso login. Plain aws configure writes a permanent access key and secret to ~/.aws/credentials, which is the credential most likely to leak into a repository, a screenshot or a former laptop. Reserve it for personal accounts with no SSO available.
How do I work with multiple AWS accounts?
Define one named profile per account or role in ~/.aws/config, then select with --profile per command or export AWS_PROFILE per shell. A worthwhile pattern is making the default production profile read-only and creating a separate admin profile used deliberately, so an accidental command fails at the IAM layer rather than changing production. Always confirm with aws sts get-caller-identity before anything destructive.
Why is the AWS CLI using the wrong account?
Almost always the credential chain. Command-line flags come first, then environment variables, then the credentials file, then config, then container and instance metadata. A stale AWS_ACCESS_KEY_ID or AWS_PROFILE exported earlier in the shell overrides your profile silently. Run env | grep AWS to see what is set, and aws sts get-caller-identity to confirm which identity is actually being used.
What is the difference between --query and --filters?
--filters is applied by the AWS API before the response is sent, so it reduces the data transferred and is supported only on commands that offer it. --query is a JMESPath expression applied locally by the CLI after the full response arrives, and works on every command. Use --filters where available for efficiency, then --query to shape the output, often combined, as in filtering to running instances server-side and selecting specific columns locally.
How do I get a single value out for a shell script?
Combine --query with --output text, as in aws sts get-caller-identity --query Account --output text, which returns the bare value with no quotes or JSON structure. This is the idiomatic way to capture an AWS value into a shell variable. Using --output json and piping through jq -r works too but adds a dependency the CLI does not need.
Why does the AWS CLI page its output?
CLI v2 sends output through a pager by default, which is helpful interactively and unhelpful everywhere else. It breaks scripts and requires a keypress to dismiss. Disable it permanently with aws configure set cli_pager "", or set the AWS_PAGER environment variable to an empty string. This is one of the first settings most people change after installing v2.