Skip to content

AWS CLI Cheatsheet#

🔐 Auth & Profile#

# Configure
aws configure                                      # default profile
aws configure --profile prod
aws configure list
aws configure list-profiles

# SSO (modern, more secure)
aws configure sso
aws sso login --profile prod
aws sso logout

# Use profile
export AWS_PROFILE=prod
aws s3 ls --profile prod
aws sts get-caller-identity --profile prod        # who am I?

# Assume role
aws sts assume-role \
  --role-arn arn:aws:iam::<ACCOUNT_ID>:role/<ROLE> \
  --role-session-name my-session

# OIDC token (for CI/CD)
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::<ACCOUNT_ID>:role/gh-actions-role \
  --role-session-name gh-action \
  --web-identity-token $GITHUB_OIDC_TOKEN

🌐 General Pattern#

# Output filtering with JMESPath
aws ec2 describe-instances \
  --query 'Reservations[].Instances[].[InstanceId,InstanceType,State.Name]' \
  --output table

# Output formats
--output json | text | table | yaml

# Pagination
aws s3api list-objects-v2 --bucket <BUCKET> --max-items 100 --starting-token <TOKEN>

# Region override
aws ec2 describe-instances --region eu-west-1
export AWS_DEFAULT_REGION=eu-west-1

💻 EC2#

# List (table)
aws ec2 describe-instances \
  --query 'Reservations[].Instances[].[InstanceId,Tags[?Key==`Name`].Value|[0],State.Name,InstanceType,PrivateIpAddress]' \
  --output table

# Get only the running ones
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[].Instances[].InstanceId'

# Filter by tag
aws ec2 describe-instances --filters "Name=tag:Environment,Values=prod"

# Start / stop / terminate
aws ec2 start-instances --instance-ids i-1234567890abcdef0
aws ec2 stop-instances --instance-ids i-1234567890abcdef0
aws ec2 terminate-instances --instance-ids i-1234567890abcdef0

# Console screenshot (debug stuck instance)
aws ec2 get-console-screenshot --instance-id i-1234567890abcdef0
aws ec2 get-console-output --instance-id i-1234567890abcdef0 --output text

# AMI list
aws ec2 describe-images --owners amazon --filters "Name=name,Values=al2023-ami-*x86_64*" \
  --query 'sort_by(Images, &CreationDate)[-5:].[ImageId,Name,CreationDate]'

# Volume
aws ec2 describe-volumes --filters Name=status,Values=available    # idle ones
aws ec2 describe-snapshots --owner-ids self --query 'sort_by(Snapshots, &StartTime)[].[SnapshotId,StartTime,VolumeSize,Description]'

🗄️ S3#

# Bucket list
aws s3 ls
aws s3api list-buckets --query 'Buckets[].Name'

# List contents
aws s3 ls s3://<BUCKET>/path/
aws s3 ls s3://<BUCKET>/path/ --recursive --human-readable --summarize

# Copy / sync
aws s3 cp file.txt s3://<BUCKET>/path/
aws s3 cp s3://<BUCKET>/path/file.txt .
aws s3 cp ./dir s3://<BUCKET>/path/ --recursive
aws s3 sync ./dist s3://<BUCKET>/path/ --delete   # delete = remove what's in the target but not in the source

# Storage class
aws s3 cp big.tar.gz s3://<BUCKET>/archive/ --storage-class GLACIER_IR

# Pre-signed URL
aws s3 presign s3://<BUCKET>/path/file.txt --expires-in 3600

# Bucket policy
aws s3api get-bucket-policy --bucket <BUCKET> | jq -r '.Policy' | jq

# Block public access (already default on new buckets)
aws s3api put-public-access-block --bucket <BUCKET> --public-access-block-configuration \
  "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# Encryption (SSE-S3 default)
aws s3api put-bucket-encryption --bucket <BUCKET> --server-side-encryption-configuration \
  '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'

# Lifecycle (IA after 30 days, Glacier after 90 days, delete after 365 days)
aws s3api put-bucket-lifecycle-configuration --bucket <BUCKET> --lifecycle-configuration file://lifecycle.json

👤 IAM#

# User list
aws iam list-users

# A user's policies
aws iam list-attached-user-policies --user-name <USER>
aws iam list-user-policies --user-name <USER>          # inline

# Roles
aws iam list-roles --query 'Roles[].RoleName'
aws iam get-role --role-name <ROLE>
aws iam list-attached-role-policies --role-name <ROLE>
aws iam list-role-policies --role-name <ROLE>

# Read policy
aws iam get-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam get-policy-version \
  --policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/<POLICY> \
  --version-id v1

# Credential report (gold for audits)
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d > report.csv

# Access analyzer (find over-permissive policies)
aws accessanalyzer list-analyzers
aws accessanalyzer list-findings --analyzer-arn <ARN>

🔐 Secrets Manager#

# Secret list
aws secretsmanager list-secrets --query 'SecretList[].Name'

# Read secret
aws secretsmanager get-secret-value --secret-id <NAME> --query SecretString --output text
aws secretsmanager get-secret-value --secret-id <NAME> | jq -r '.SecretString' | jq

# Create / update
aws secretsmanager create-secret --name <NAME> --secret-string '<VALUE>'
aws secretsmanager update-secret --secret-id <NAME> --secret-string '<VALUE>'
aws secretsmanager put-secret-value --secret-id <NAME> --secret-string file://creds.json

# Rotate
aws secretsmanager rotate-secret --secret-id <NAME>

🪣 Parameter Store (SSM)#

# Create
aws ssm put-parameter --name "/app/db/host" --value "<HOST>" --type String
aws ssm put-parameter --name "/app/db/password" --value "<PWD>" --type SecureString --key-id alias/aws/ssm

# Read
aws ssm get-parameter --name "/app/db/host" --query 'Parameter.Value' --output text
aws ssm get-parameter --name "/app/db/password" --with-decryption --query 'Parameter.Value' --output text

# All under a path
aws ssm get-parameters-by-path --path "/app/" --recursive --with-decryption \
  --query 'Parameters[].[Name,Value]' --output text

🚪 SSM Session Manager (EC2 access without SSH)#

# Plugin installation (one time)
# https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html

# Connect
aws ssm start-session --target i-1234567890abcdef0

# Port forward (to DB via bastion)
aws ssm start-session --target i-1234567890abcdef0 \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"host":["<DB_HOST>"],"portNumber":["5432"],"localPortNumber":["5432"]}'

# Run command (remotely without ssh)
aws ssm send-command \
  --instance-ids i-1234567890abcdef0 \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["uname -a", "df -h"]' \
  --output json

🌐 Route 53#

# Hosted zone list
aws route53 list-hosted-zones

# See records
aws route53 list-resource-record-sets --hosted-zone-id <ZONE_ID>

# Add/update record (with json)
aws route53 change-resource-record-sets --hosted-zone-id <ZONE_ID> --change-batch file://record.json

# Verify DNS propagation
aws route53 get-change --id <CHANGE_ID>

📊 CloudWatch#

# Log groups
aws logs describe-log-groups --query 'logGroups[].logGroupName'

# Tail (real-time)
aws logs tail /aws/lambda/my-fn --follow
aws logs tail /aws/lambda/my-fn --since 10m --filter-pattern "ERROR"

# Metric query
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time $(date -u -d '1 hour ago' +%FT%TZ) \
  --end-time $(date -u +%FT%TZ) \
  --period 300 \
  --statistics Average

💸 Cost Explorer#

# This month
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '1 month ago' +%F),End=$(date +%F) \
  --granularity DAILY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --query 'ResultsByTime[].Total.UnblendedCost.Amount' --output text

# Top 10 service
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '30 days ago' +%F),End=$(date +%F) \
  --granularity MONTHLY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --query 'ResultsByTime[0].Groups[?Metrics.UnblendedCost.Amount>`100`].[Keys[0],Metrics.UnblendedCost.Amount]' \
  --output table

🔧 EKS#

# Cluster list
aws eks list-clusters

# Get kubeconfig
aws eks update-kubeconfig --name <CLUSTER> --region <REGION>
aws eks update-kubeconfig --name <CLUSTER> --alias <SHORT_NAME>

# Version and update
aws eks describe-cluster --name <CLUSTER> --query 'cluster.version'
aws eks update-cluster-version --name <CLUSTER> --version 1.30

⚡ Useful one-liners#

# Which role am I running as?
aws sts get-caller-identity

# List all regions
aws ec2 describe-regions --query 'Regions[].RegionName' --output text

# Running EC2s across all regions
for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
  echo "=== $r ==="
  aws ec2 describe-instances --region $r \
    --filters "Name=instance-state-name,Values=running" \
    --query 'Reservations[].Instances[].[InstanceId,InstanceType]' \
    --output table
done

# All available (untagged) EBS volumes
aws ec2 describe-volumes --filters Name=status,Values=available \
  --query 'Volumes[].[VolumeId,Size,CreateTime]' --output table

# Idle Elastic IPs ($$$)
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null].[PublicIp,AllocationId]' --output table

# Script the whole cleanup
aws ec2 describe-snapshots --owner-ids self \
  --query "Snapshots[?StartTime<='$(date -d '90 days ago' +%F)'].SnapshotId" \
  --output text | xargs -n1 aws ec2 delete-snapshot --snapshot-id

🆘 Emergency scenarios#

Issue Solution
Unable to locate credentials aws configure list; AWS_PROFILE set; aws sso login
AccessDenied but IAM is correct Check trust policy, MFA, session length, condition keys
High bill at month's end Cost Explorer + Anomaly Detector + tagging audit
No EC2 SSH access Use SSM Session Manager (instance role required)
S3 bucket accidentally public Close with aws s3api put-public-access-block
Not seeing resources due to region mismatch Add --region or set AWS_DEFAULT_REGION

🎓 Learning Path: This document is used as the "Read first" resource in the C4 module.