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.
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 configureaws configure --profile productionaws configure listaws sts get-caller-identityexport AWS_PROFILE=productionaws configure set region us-west-2EC2 Instances 6
aws ec2 describe-instances --filters "Name=instance-state-name,Values=running"aws ec2 describe-instances --query "Reservations[].Instances[].[InstanceId,State.Name]"aws ec2 start-instances --instance-ids i-xxxxxaws ec2 stop-instances --instance-ids i-xxxxxaws ec2 terminate-instances --instance-ids i-xxxxxaws ec2 create-key-pair --key-name my-key --query "KeyMaterial" --output text > my-key.pemS3 Storage 8
aws s3 lsaws s3 ls s3://my-bucket/aws s3 cp file.txt s3://my-bucket/aws s3 cp s3://my-bucket/file.txt ./file.txtaws s3 sync ./local/ s3://my-bucket/remote/aws s3 sync s3://src-bucket/ s3://dst-bucket/ --deleteaws s3 rm s3://my-bucket/file.txtaws s3 mb s3://new-bucket-nameIAM Users & Policies 6
aws iam list-usersaws iam create-user --user-name newuseraws iam create-access-key --user-name newuseraws iam attach-user-policy --user-name newuser --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccessaws iam list-attached-user-policies --user-name newuseraws iam delete-user --user-name newuserCloudWatch Logs 5
aws logs describe-log-groupsaws logs tail /app/production --followaws logs filter-log-events --log-group-name mygroup --start-time 1234567890aws logs tail /app/production --since 1haws 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 AverageLambda & Services 5
aws lambda list-functionsaws lambda invoke --function-name my-function output.jsonaws lambda create-function --function-name my-fn --runtime python3.11 --role arn:aws:iam::123:role/role --handler index.handler --zip-file fileb://fn.zipaws rds describe-db-instancesaws dynamodb list-tablesQuery Tips 5
aws ec2 describe-instances --output tableaws s3api list-objects --bucket my-bucket --query "Contents[?Size>`1000000`].Key"aws ec2 describe-instances --filters "Name=tag:Name,Values=web-*"aws ec2 describe-instances --query "Reservations[].Instances[].PublicIpAddress" --output textaws s3 presign s3://my-bucket/file.zip --expires-in 3600Parameter matrix
| Parameter | Effect | Example |
|---|---|---|
aws configure --profile production | Stores a separate profile in the config file, so switching accounts no longer means overwriting the default credentials. | aws configure --profile production |
aws configure list | Shows 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-identity | Returns 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=production | Selects 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-2 | Updates 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 table | Renders 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-xxxxx | Starts 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-xxxxx | Destroys 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.pem | The 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 ... --delete | Deletes 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.txt | Deletes 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-name | Creates 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 newuser | Creating 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-policy | Attaches 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 newuser | Confirms 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 --follow | Streams 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 1h | Limits 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 1234567890 | Filters 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 Average | Fetches 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.json | Invokes 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.zip | Creating 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 3600 | Generates 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
1Confirm the current identity and account
aws sts get-caller-identityThe first check before any mutating operation.
2Inspect where the configuration comes from
aws configure listAn access_key reported as None means credentials arrive from the environment or an instance role.
3Reduce a large result to a readable table
aws ec2 describe-instances --output tableStart with table for the overview, then narrow to specific fields with --query.
4Rehearse a sync and confirm the blast radius of --delete
aws s3 sync ./local/ s3://my-bucket/remote/ --delete --dryrunEvery line the output marks as delete is an object about to be removed.
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.
6Project only the columns you need, one value per line
aws ec2 describe-instances --query "Reservations[].Instances[].PublicIpAddress" --output textText 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-identityRun 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 tableEach 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 3600A 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