AWS CLI Cheatsheet - S3, EC2, IAM & Cloud Commands

Anything you click once in the console you will eventually need to click a second time from a script, and this sheet covers exactly that surface: configuration and credentials, EC2 instances, S3 storage, IAM users and policies, CloudWatch logs and metrics, plus Lambda and other services. The credentials section exists because multi-account work breaks silently when environment variables and the default profile overwrite each other — naming every account with --profile is the cheapest fix available. The query section is where the real leverage is: --output table for a human, --output json piped to jq, and --query with JMESPath to lift one field straight into a shell variable, collapsing twenty lines of parsing into one. Debugging paths are covered too: CloudWatch Logs Insights aggregates by field instead of paginating, and an instance that will not start is traced from StateReason to the matching CloudTrail event. Know the boundary — this is a single-shot client, not an orchestrator; conditional loops and retry policy belong to CloudFormation or CI.

SysOps·41 commands·Last updated 2026-07-19
awscliClouddevops

Typical Use Case

For engineers working with cloud resources from a bastion host, a CI runner or a local terminal. The value is turning console clicks that do not scale into commands that are repeatable and reviewable: --filters to narrow scope, --query to project with JMESPath, --output to control presentation. Covers profiles, EC2, S3, IAM, CloudWatch and Lambda. One habit first: run sts get-caller-identity before acting, because a wrong profile means the command succeeds in the wrong account.

Configure & Auth 6

aws configure
Interactive configure access key and region
aws configure --profile production
Configure credentials for a profile
aws configure list
Show current config
aws sts get-caller-identity
Verify the current identity
export AWS_PROFILE=production
Switch the default profile
aws configure set region us-west-2
Set a single config value

EC2 Instances 6

aws ec2 describe-instances --filters "Name=instance-state-name,Values=running"
List running instances
aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId,State.Name]"
Query instance IDs and state
aws ec2 start-instances --instance-ids i-xxxxx
Start an instance
aws ec2 stop-instances --instance-ids i-xxxxx
Stop an instance
aws ec2 terminate-instances --instance-ids i-xxxxx
Terminate an instance (irreversible)
aws ec2 create-key-pair --key-name my-key --query "KeyMaterial" --output text > my-key.pem
Create an SSH key pair, save the private key

S3 Storage 8

aws s3 ls
List all buckets
aws s3 ls s3://my-bucket/
List bucket contents
aws s3 cp file.txt s3://my-bucket/
Upload a file
aws s3 cp s3://my-bucket/file.txt ./file.txt
Download a file
aws s3 sync ./local/ s3://my-bucket/remote/
Sync a directory (changed files only)
aws s3 sync s3://src-bucket/ s3://dst-bucket/ --delete
Sync buckets and delete extras
aws s3 rm s3://my-bucket/file.txt
Delete a file
aws s3 mb s3://new-bucket-name
Create a new bucket

IAM Users & Policies 6

aws iam list-users
List all IAM users
aws iam create-user --user-name newuser
Create an IAM user
aws iam create-access-key --user-name newuser
Create an access key for a user
aws iam attach-user-policy --user-name newuser --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
Attach a managed policy to a user
aws iam list-attached-user-policies --user-name newuser
List a user's attached policies
aws iam delete-user --user-name newuser
Delete an IAM user

CloudWatch Logs 5

aws logs describe-log-groups
List log groups
aws logs tail /app/production --follow
Tail logs in real time
aws logs filter-log-events --log-group-name mygroup --start-time 1234567890
Filter logs by time
aws logs tail /app/production --since 1h
Show logs from the last hour
aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization --start-time 2026-07-20T00:00:00Z --end-time 2026-07-21T00:00:00Z --period 3600 --statistics Average
Query EC2 CPU utilization metric

Lambda & Services 5

aws lambda list-functions
List all Lambda functions
aws lambda invoke --function-name my-function output.json
Invoke a Lambda function (sync)
aws lambda create-function --function-name my-fn --runtime python3.11 --role arn:aws:iam::123:role/role --handler index.handler --zip-file fileb://fn.zip
Create a Lambda function
aws rds describe-db-instances
List RDS DB instances
aws dynamodb list-tables
List DynamoDB tables

Query Tips 5

aws ec2 describe-instances --output table
Output as a table (readable)
aws s3api list-objects --bucket my-bucket --query "Contents[?Size>`1000000`].Key"
JMESPath query for files > 1MB
aws ec2 describe-instances --filters "Name=tag:Name,Values=web-*"
Filter instances by tag
aws ec2 describe-instances --query "Reservations[].Instances[].PublicIpAddress" --output text
Extract all instances' public IPs
aws s3 presign s3://my-bucket/file.zip --expires-in 3600
Generate a 1-hour presigned URL

Parameter matrix

ParameterEffectExample
aws configure --profile productionStores a separate profile in the config file, so switching accounts no longer means overwriting the default credentials.aws configure --profile production
aws configure listShows which credential source and settings are in effect. This is where to start when a command reaches the wrong account.aws configure list
aws sts get-caller-identityReturns the current identity and account. Running it before any change is far more reliable than reading the configuration back.aws sts get-caller-identity
export AWS_PROFILE=productionSelects a profile through an environment variable, which reads much better in scripts than repeating --profile everywhere.export AWS_PROFILE=production
aws configure set region us-west-2Updates a single setting and writes it back to the profile, skipping the interactive question-and-answer flow.aws configure set region us-west-2
--filters "Name=instance-state-name,Values=running"Filters server-side before anything is returned, which cuts response time noticeably in a large account.aws ec2 describe-instances --filters "Name=instance-state-name,Values=running"
--query "Reservations[].Instances[].[InstanceId,State.Name]"Projects the response client-side with JMESPath, collapsing nested structures down to the few columns needed.aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId,State.Name]"
--output tableRenders output as a table for humans; --output text is better for piping into another command and json suits jq.aws ec2 describe-instances --output table
--output text --query "Contents[?Size>`1000000`].Key"Wrapping a numeric literal in backticks inside a filter expression yields a single column of ready-to-use values.aws s3api list-objects --bucket my-bucket --query "Contents[?Size>`1000000`].Key"
aws ec2 start-instances --instance-ids i-xxxxxStarts instances in bulk by id; stop keeps the EBS volumes and configuration, while only terminate releases them.aws ec2 start-instances --instance-ids i-xxxxx
aws ec2 terminate-instances --instance-ids i-xxxxxDestroys the instance irreversibly, so confirm the id and the account before running it.aws ec2 terminate-instances --instance-ids i-xxxxx
aws ec2 create-key-pair --query "KeyMaterial" --output text > my-key.pemThe private key is returned exactly once at creation, so it has to be redirected to a file in the same step or it is gone for good.aws ec2 create-key-pair --key-name my-key --query "KeyMaterial" --output text > my-key.pem
aws s3 ls s3://my-bucket/Lists the contents of a bucket or prefix; the trailing slash is what decides between listing objects and listing bucket names.aws s3 ls s3://my-bucket/
aws s3 cp file.txt s3://my-bucket/Copies a single object up or down; large files are split and transferred concurrently.aws s3 cp s3://my-bucket/file.txt ./file.txt
aws s3 sync ./local/ s3://my-bucket/remote/Incremental sync driven by size and timestamp, transferring only differences. Keep the trailing slash on both the source and the destination.aws s3 sync ./local/ s3://my-bucket/remote/
aws s3 sync ... --deleteDeletes destination objects that no longer exist at the source, producing an exact mirror. Always rehearse with --dryrun first.aws s3 sync s3://src-bucket/ s3://dst-bucket/ --delete
aws s3 rm s3://my-bucket/file.txtDeletes an object; on a versioned bucket this only adds a delete marker.aws s3 rm s3://my-bucket/file.txt
aws s3 mb s3://new-bucket-nameCreates the bucket, after which region, encryption and public access policy still have to be set as required.aws s3 mb s3://new-bucket-name
aws iam create-user --user-name newuserCreating the IAM user is not the same as being able to use it; an access key or key pair has to be configured as well.aws iam create-user --user-name newuser
aws iam attach-user-policyAttaches an AWS managed policy directly. Least-privilege setups are better served by a custom policy than a managed one.aws iam attach-user-policy --user-name newuser --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam list-attached-user-policies --user-name newuserConfirms the policies actually in effect, which is the evidence to rely on when permissions seem to appear or vanish on their own.aws iam list-attached-user-policies --user-name newuser
aws logs tail /app/production --followStreams a log group live, the equivalent of tail -f without the awkward streaming API calls.aws logs tail /app/production --follow
aws logs tail /app/production --since 1hLimits how far back to look, which avoids pulling several gigabytes of logs at once.aws logs tail /app/production --since 1h
aws logs filter-log-events --log-group-name mygroup --start-time 1234567890Filters events by a millisecond timestamp, which is what you want when investigating a specific window.aws logs filter-log-events --log-group-name mygroup --start-time 1234567890
aws cloudwatch get-metric-statistics --period 3600 --statistics AverageFetches a metric at a chosen granularity and statistic. A period that does not line up with the stored granularity returns no data points.aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization --start-time 2026-07-20T00:00:00Z --end-time 2026-07-21T00:00:00Z --period 3600 --statistics Average
aws lambda invoke --function-name my-function output.jsonInvokes the function synchronously; the response body lands in output.json and its second line carries the function error.aws lambda invoke --function-name my-function output.json
aws lambda create-function --zip-file fileb://fn.zipCreating a function requires a zip upload or an S3 location, and a wrong execution role ARN only surfaces at invocation time.aws lambda create-function --function-name my-fn --runtime python3.11 --role arn:aws:iam::123:role/role --handler index.handler --zip-file fileb://fn.zip
aws s3 presign s3://my-bucket/file.zip --expires-in 3600Generates a time-limited direct URL, which avoids proxying large files and spreading broad permissions.aws s3 presign s3://my-bucket/file.zip --expires-in 3600
aws ec2 describe-instances --filters "Name=tag:Name,Values=web-*"Filtering by tag, the most reliable way to pick out your own resources inside a large shared account.aws ec2 describe-instances --filters "Name=tag:Name,Values=web-*"

Common pitfalls

SymptomA command succeeds against the wrong account and the change lands in the wrong place.

CauseProfiles and environment variables drift apart, and the precedence between AWS_PROFILE, the credentials file and an instance role is easy to overlook.

FixRun aws sts get-caller-identity before every mutating command, and pass --profile explicitly in scripts instead of trusting the global default.

SymptomAfter s3 sync the destination holds unexpected files, or files deleted at the source are still present.

Causesync does not delete by default, while --delete removes extra destination objects, and the trailing slash decides whether you sync a directory or a directory name.

FixRehearse with --dryrun first and read the deletion list carefully, and put a trailing slash on both the source and the destination.

Symptomcreate-key-pair finished, but the private key cannot be found anywhere.

CauseKeyMaterial is returned once in the creation response; without an output redirection it is lost permanently and the key pair has to be replaced.

FixRedirect it to a file and chmod 600 immediately; if the key is already lost, delete the key pair and create a new one.

Symptomget-metric-statistics returns no data points and no curve appears.

CauseThe period does not match the stored granularity of the metric (asking for one-minute points from five-minute storage, for example), or the time range was written in a non-UTC zone.

FixMake the period no smaller than the stored granularity and express all times in ISO8601 UTC with a trailing Z.

SymptomThe response body is enormous and the terminal grinds to a halt.

CauseA describe-style call returns thousands of objects with no filter and no projection applied.

FixFilter server-side with --filters first, project the columns you need with --query, and only then choose between table and text output.

Symptomsts get-caller-identity fails with ExpiredToken or InvalidClientTokenId.

CauseTemporary credentials from STS or an instance role have expired, or stale entries linger in the credentials file.

FixRefresh the credential source by re-running assume-role or switching profile, and check that the role_arn and source_profile in the config file are still valid.

SymptomAn IAM user is created but cannot reach any resource at all.

CauseCreating the user and granting permissions are two separate steps, and a user with no attached policy has an empty permission boundary.

FixReview attached policies with list-attached-user-policies and add a least-privilege policy with attach-user-policy where needed.

Symptomlambda invoke exits successfully, yet the function itself had failed.

CauseThe exit status only reflects whether the invocation was delivered; an error inside the function shows up in the second section of the output file and in the StatusCode field.

FixRead output.json afterwards: anything other than StatusCode 200 on the first line is a failure, and in bulk use --log-type Tail to inspect the logs.

Troubleshooting

  1. 1Confirm the current identity and account

    aws sts get-caller-identity

    The first check before any mutating operation.

  2. 2Inspect where the configuration comes from

    aws configure list

    An access_key reported as None means credentials arrive from the environment or an instance role.

  3. 3Reduce a large result to a readable table

    aws ec2 describe-instances --output table

    Start with table for the overview, then narrow to specific fields with --query.

  4. 4Rehearse a sync and confirm the blast radius of --delete

    aws s3 sync ./local/ s3://my-bucket/remote/ --delete --dryrun

    Every line the output marks as delete is an object about to be removed.

  5. 5Filter down to your own resources by tag

    aws ec2 describe-instances --filters "Name=tag:Name,Values=web-*"

    This avoids collateral damage in a large shared account.

  6. 6Project only the columns you need, one value per line

    aws ec2 describe-instances --query "Reservations[].Instances[].PublicIpAddress" --output text

    Text output can be piped straight into ssh, curl and similar commands.

Command Examples

Confirm which identity you are before switching accounts

aws configure list aws sts get-caller-identity export AWS_PROFILE=production aws sts get-caller-identity

Run sts get-caller-identity before and after the switch and compare the Account field. This single check prevents most "ran a test command against production" incidents, because the error often only surfaces minutes later, long after the data has already changed.

Output

"UserId": "AIDAEXAMPLE", "Account": "123456789012"

Narrow the instance list with filter plus query

aws ec2 describe-instances \ --filters "Name=tag:Name,Values=web-*" \ --query "Reservations[].Instances[].[InstanceId,State.Name]" \ --output table

Each option handles one layer: --filters narrows server-side (less data over the wire), --query extracts just the needed columns client-side with JMESPath, --output table decides presentation. Without --filters every instance in every region comes back, and on a busy account the output becomes unreadable.

Output

-------------------
|  InstanceId    | State.Name |
| i-0a1b2c3d4e   | running    |

Locate large objects in S3 before deciding what to delete

aws s3 ls s3://my-bucket/ --human-readable aws s3api list-objects --bucket my-bucket \ --query "Contents[?Size>`1000000`].Key"

List the bucket to gauge its size first, then use the s3api JMESPath filter to pull out keys over 1MB. Do not reverse the order: get the key list, confirm each one by eye, and only then run aws s3 rm. That way you never delete something whose name you have not read.

Run a directory sync without --delete first

aws s3 sync ./local/ s3://my-bucket/remote/ aws s3 sync ./local/ s3://my-bucket/remote/ --dryrun aws s3 sync ./local/ s3://my-bucket/remote/ --delete

--delete removes every object present in the destination but absent from the source, so a wrong path (./local typed as ./, or a missing trailing slash) deletes data in bulk. Run a plain sync first, then a --dryrun to see exactly which keys --delete would remove, and only then add the flag.

Output

delete: s3://my-bucket/remote/old-file.log

Hand out a time-limited download link without persisting credentials

aws s3 presign s3://my-bucket/file.zip --expires-in 3600

A presigned URL carries its own signature and expiry, so you can hand it over without writing access keys or temporary credentials into any file. Treat a sent link as equivalent to granting file access, so keep the expiry short and discard it after use.

Output

https://my-bucket.s3.amazonaws.com/file.zip?X-Amz-Algorithm=...

Common Pitfalls

  • An interactive aws configure writes credentials in plaintext to ~/.aws/credentials (mode 0644 by default), readable by any other process running as the same user on a CI box. Tighten it with chmod 600 afterwards, and never export AK/SK into the environment in a way that ends up committed to a repository.
  • When the same credentials live both in ~/.aws/credentials and in exported AWS_ACCESS_KEY_ID, the environment variable silently wins over the profile in the config file. You believe you are on the staging account while every command hits production. Run aws sts get-caller-identity at the start of any session to confirm the Account.
  • aws ec2 terminate-instances and stop-instances differ by one word: the first destroys the instance irrecoverably, the second can be started again. When instance IDs come from a --query output or a paste, stop easily becomes terminate. Before any bulk action, re-verify the ID list with aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" --output table.
  • Region mismatch is the hardest class of bug to diagnose: instead of InvalidClientTokenId or AccessDenied you get misleading errors like "bucket does not exist". It happens after aws configure set region us-west-2 without an export, or when S3 goes through a global endpoint while EC2 uses a regional one. Check aws configure list first to see whether region is empty.
  • aws s3 sync --delete and aws s3 rm are irreversible, and with S3 versioning off by default a deleted object is gone for good. Recovery then depends only on versioning or cross-region replication you enabled in advance. Verify keys one by one with aws s3 ls s3://bucket/prefix/ before cleanup, and do not over-broaden the prefix.
  • The Secret from aws iam create-access-key is returned exactly once. Once lost, the only path is delete-access-key and re-create, which breaks every service using that key. Never paste secrets into tickets, chat rooms, or CI logs; check status with aws iam list-access-keys instead of putting the secret on a command line.

Tips

  • aws configure --profile creates named profiles for multi-account use.
  • S3 sync transfers only changed files - good for backup/deploy.
  • describe-instances --query filters output with JMESPath.
  • Logs Insights queries are more powerful than filter-log-events.
  • --output json/table/text: text for scripts, table for humans.
  • --query with JMESPath + --output text suits shell scripts.

FAQ

How do I work across several AWS accounts on one machine without touching production by mistake?

Isolate them with named profiles: aws configure --profile production stores a separate set of access keys and region, after which you either export AWS_PROFILE=production to switch the default identity or pass --profile production on each command. Run aws sts get-caller-identity to confirm which credentials are active at any moment, and aws configure list to review what is configured. Dev and production no longer overwrite each other, and you stop rewriting ~/.aws/credentials by hand.

When should I reach for aws s3 cp versus aws s3 sync?

It depends on whether you are moving one thing or a tree. cp is a one-off transfer with explicit endpoints: aws s3 cp file.txt s3://my-bucket/ uploads, aws s3 cp s3://my-bucket/file.txt ./file.txt downloads, and aws s3 rm s3://my-bucket/file.txt removes a single key. sync operates on whole directories and transfers only what changed, which suits backups and deployments: aws s3 sync ./local/ s3://my-bucket/remote/. Be aware that --delete removes extra files at the destination, so double-check the direction before running a two-way sync.

How do I extract just the fields I need from pages of CLI output?

Filter with a JMESPath expression via --query, then pick the rendering with --output. For example aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId,State.Name]" pulls out instance IDs and states; aws ec2 describe-instances --query "Reservations[].Instances[].PublicIpAddress" --output text prints one public IP per line, which is what shell scripts want; --output table is for humans. The same idea works on S3: aws s3api list-objects --bucket my-bucket --query "Contents[?Size>1000000].Key" lists keys larger than 1MB.

How do I get into a keyless production box without leaving a hole behind?

Three steps. Generate and save a key with aws ec2 create-key-pair --key-name my-key --query "KeyMaterial" --output text > my-key.pem. Locate the target host by tag using aws ec2 describe-instances --filters "Name=tag:Name,Values=web-*", then bring it up with aws ec2 start-instances --instance-ids i-xxxxx and log in. Remove the temporary key once you are done. Also keep in mind that terminate-instances destroys the instance irrecoverably, which is a different thing from stop-instances.

I need live logs while troubleshooting production without drowning in output — how do I narrow it?

Three ways to scope it. Tail one log group live with aws logs tail /app/production --follow. Limit to the last hour with aws logs tail /app/production --since 1h. Or hit an explicit window with aws logs filter-log-events --log-group-name mygroup --start-time 1234567890. Start by listing candidates with aws logs describe-log-groups. Note that filter-log-events only filters by time; for multi-condition investigations reach for CloudWatch Logs Insights query syntax instead.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 19, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us