Skip to content

AWS โ€” Amazon Web Services

Last reviewed: 2026-06-16

Purpose: Comprehensive Knowledge Base reference for core AWS services โ€” CLI setup, EC2, S3, IAM, VPC, and cost management. Includes practical CLI commands and a full end-to-end example.


Table of Contents

  1. AWS CLI โ€” Install & Configure
  2. EC2 โ€” Elastic Compute Cloud
  3. S3 โ€” Simple Storage Service
  4. IAM โ€” Identity & Access Management
  5. VPC โ€” Virtual Private Cloud Basics
  6. Cost Management
  7. Complete Example โ€” Launch EC2 with S3 Backend
  8. Troubleshooting & Tips

1. AWS CLI โ€” Install & Configure

Installation

Linux (Debian/Ubuntu):

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
rm -rf aws awscliv2.zip

macOS:

curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

Verify:

aws --version
# aws-cli/2.x.x Python/3.x.x Linux/6.x.x source/x86_64

Configuration

Interactive:

aws configure
# AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
# AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Default region name [None]: us-east-1
# Default output format [None]: json

Non-interactive (scripts/CI):

aws configure set aws_access_key_id AKIAIOSFODNN7EXAMPLE
aws configure set aws_secret_access_key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
aws configure set region us-east-1
aws configure set output json

Multiple profiles:

aws configure --profile production
aws configure --profile staging
aws s3 ls --profile production

Configuration files live in ~/.aws/: - ~/.aws/config โ€” region, output format, profile settings - ~/.aws/credentials โ€” access keys (keep secure; chmod 600)

IMDSv2 (EC2 instance roles): When running on an EC2 instance with an IAM role, the CLI automatically obtains temporary credentials from the instance metadata service. No manual keys needed.

Useful CLI Flags

Flag Purpose
--profile Use a named profile
--region Override the default region
--output json, text, table, or yaml
--no-sign-request Access public S3 buckets without credentials
--dry-run Check permissions without executing (EC2, S3)
--cli-auto-prompt Interactive prompt mode (v2)

2. EC2 โ€” Elastic Compute Cloud

Instance Types โ€” Naming Convention

m5.large
โ”‚ โ”‚    โ””โ”€โ”€ Size (small, medium, large, xlarge, 2xlarge, โ€ฆ)
โ”‚ โ””โ”€โ”€ Generation number
โ””โ”€โ”€ Instance family
Family Use Case Example Types
t (burstable) General-purpose, dev/test, low-CPU t3.micro (free tier), t3.medium
m (general) Balanced compute/memory/networking m5.large, m6i.xlarge
c (compute) Compute-optimized, batch, gaming c5.2xlarge, c6g.large
r (memory) Memory-optimized, databases, caching r5.large, r6g.xlarge
i (storage) I/O-intensive, high-throughput storage i3.large
g (GPU) ML, rendering, CUDA workloads g4dn.xlarge

Free tier eligible: t2.micro or t3.micro (750 hours/month for 12 months).

Launching an EC2 Instance

# Create a key pair (if you don't have one)
aws ec2 create-key-pair --key-name my-key --query 'KeyMaterial' --output text > my-key.pem
chmod 400 my-key.pem

# Create a security group
aws ec2 create-security-group \
    --group-name web-sg \
    --description "Web server security group"

# Authorize inbound SSH (port 22) and HTTP (port 80)
aws ec2 authorize-security-group-ingress \
    --group-name web-sg \
    --protocol tcp --port 22 --cidr 0.0.0.0/0

aws ec2 authorize-security-group-ingress \
    --group-name web-sg \
    --protocol tcp --port 80 --cidr 0.0.0.0/0

# Launch an instance
aws ec2 run-instances \
    --image-id ami-0c7217cdde317cfec \
    --instance-type t3.micro \
    --key-name my-key \
    --security-groups web-sg \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=MyWebServer}]'

AMIs โ€” Amazon Machine Images

Find the latest Amazon Linux 2023 AMI:

aws ec2 describe-images \
    --owners amazon \
    --filters 'Name=name,Values=al2023-ami-*-kernel-6.1-x86_64' \
    --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' \
    --output text

Common AMI IDs per region (Amazon Linux 2023, x86_64, HVM, EBS SSD): | Region | AMI ID | |--------|--------| | us-east-1 | ami-0c7217cdde317cfec | | us-west-2 | ami-0f0c7f5e6f7a8b9c0 | | eu-west-1 | ami-0a1b2c3d4e5f6a7b8 |

AMI IDs differ by region and are updated frequently. Always query describe-images rather than hardcoding.

SSH Access

# Standard SSH
ssh -i my-key.pem ec2-user@<public-ip-or-dns>

# Using SSH config (~/.ssh/config)
Host my-server
    HostName ec2-1-2-3-4.compute-1.amazonaws.com
    User ec2-user
    IdentityFile ~/.ssh/my-key.pem

# Then simply:
ssh my-server

Default usernames by AMI: | AMI | Username | |-----|----------| | Amazon Linux 2023/2 | ec2-user | | Ubuntu | ubuntu | | RHEL | ec2-user or root | | Debian | admin | | CentOS | centos |

Security Groups โ€” Rules & Best Practices

# List all security groups
aws ec2 describe-security-groups

# Add HTTPS inbound (port 443) from a specific CIDR
aws ec2 authorize-security-group-ingress \
    --group-id sg-12345678 \
    --protocol tcp --port 443 \
    --cidr 10.0.0.0/16

# Remove a rule
aws ec2 revoke-security-group-ingress \
    --group-id sg-12345678 \
    --protocol tcp --port 22 \
    --cidr 0.0.0.0/0

# Reference another security group (instead of CIDR)
aws ec2 authorize-security-group-ingress \
    --group-id sg-12345678 \
    --protocol tcp --port 3306 \
    --source-group sg-87654321

Rules of thumb: - Least privilege โ€” only open ports you need - Use security group references instead of CIDR where possible (e.g., app โ†’ db) - Never open SSH (port 22) to 0.0.0.0/0 in production โ€” use a bastion host or VPN - Stateful โ€” return traffic is automatically allowed - Separate security groups per tier (web, app, db)

Managing Instances

# List instances with details
aws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId,State.Name,InstanceType,PublicIpAddress,Tags[?Key==`Name`].Value | [0]]' --output table

# Stop
aws ec2 stop-instances --instance-ids i-1234567890abcdef0

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

# Terminate (destructive โ€” data on instance store is lost)
aws ec2 terminate-instances --instance-ids i-1234567890abcdef0

# Tag an instance
aws ec2 create-tags --resources i-1234567890abcdef0 --tags Key=Environment,Value=Production

3. S3 โ€” Simple Storage Service

Bucket Operations

# Create a bucket (bucket names are globally unique)
aws s3 mb s3://my-unique-bucket-name --region us-east-1

# List all buckets
aws s3 ls

# List objects in a bucket
aws s3 ls s3://my-bucket/

# List with human-readable sizes and total count
aws s3 ls s3://my-bucket/ --summarize --human-readable

# Get bucket info
aws s3api get-bucket-location --bucket my-bucket

Upload, Download, and Sync

# Upload a single file
aws s3 cp index.html s3://my-bucket/www/

# Upload a directory (recursive)
aws s3 cp ./build/ s3://my-bucket/www/ --recursive

# Download a file
aws s3 cp s3://my-bucket/config.json ./

# Sync โ€” one-way, copies only newer/missing files
aws s3 sync ./local-dir/ s3://my-bucket/remote-dir/

# Sync from S3 to local
aws s3 sync s3://my-bucket/remote-dir/ ./local-dir/

# Sync with delete (removes files at destination that aren't at source)
aws s3 sync ./local-dir/ s3://my-bucket/remote-dir/ --delete

# Dry-run sync
aws s3 sync ./local-dir/ s3://my-bucket/remote-dir/ --dryrun

Presigned URLs

Generate time-limited URLs for private objects โ€” no IAM credentials needed by the consumer.

# Generate a presigned URL valid for 1 hour (default 3600 seconds)
aws s3 presign s3://my-bucket/private/report.pdf --expires-in 3600

# Using s3api for more control
aws s3api presign \
    --bucket my-bucket \
    --key private/report.pdf \
    --expires-in 86400  # 24 hours

# Use with curl to download
curl -o report.pdf "$(aws s3 presign s3://my-bucket/private/report.pdf --expires-in 300)"

Use cases: Temporary access to reports, user file downloads, image sharing.

Lifecycle Policies

Automate object transitions and expirations.

CLI โ€” apply a lifecycle policy:

# Save policy to JSON
cat > lifecycle.json << 'EOF'
{
  "Rules": [
    {
      "Id": "expire-logs",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "logs/"
      },
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 90,
          "StorageClass": "GLACIER"
        }
      ],
      "Expiration": {
        "Days": 365
      }
    },
    {
      "Id": "abort-incomplete-multipart",
      "Status": "Enabled",
      "Filter": {
        "Prefix": ""
      },
      "AbortIncompleteMultipartUpload": {
        "DaysAfterInitiation": 7
      }
    }
  ]
}
EOF

aws s3api put-bucket-lifecycle-configuration \
    --bucket my-bucket \
    --lifecycle-configuration file://lifecycle.json

Storage classes from warmest to coldest: | Class | Durability | Min Duration | Retrieval | |-------|-----------|-------------|-----------| | STANDARD | 99.999999999% | None | Instant | | STANDARD_IA | 99.999999999% | 30 days | Instant | | ONEZONE_IA | 99.999999999% | 30 days | Instant | | GLACIER_INSTANT | 99.999999999% | 90 days | Millisecond | | GLACIER_FLEXIBLE | 99.999999999% | 90 days | Minutesโ€“hours | | DEEP_ARCHIVE | 99.999999999% | 180 days | Hours |

Bucket Policies (Access Control)

# Make a bucket publicly readable (caution!)
cat > public-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-public-bucket/*"
    }
  ]
}
EOF

aws s3api put-bucket-policy \
    --bucket my-public-bucket \
    --policy file://public-policy.json

# Block public access (default best practice)
aws s3api put-public-access-block \
    --bucket my-bucket \
    --public-access-block-configuration 'BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true'

4. IAM โ€” Identity & Access Management

Users

# Create a user
aws iam create-user --user-name developer-1

# Create an access key pair
aws iam create-access-key --user-name developer-1

# Attach a managed policy directly
aws iam attach-user-policy \
    --user-name developer-1 \
    --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# List user's policies
aws iam list-attached-user-policies --user-name developer-1

Groups

# Create a group
aws iam create-group --group-name Developers

# Add user to group
aws iam add-user-to-group --user-name developer-1 --group-name Developers

# Attach policy to group (all members inherit)
aws iam attach-group-policy \
    --group-name Developers \
    --policy-arn arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess

Roles

Roles are assumed by AWS services (EC2, Lambda, etc.) or federated users.

# Create an IAM role for EC2 with a trust policy
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

aws iam create-role \
    --role-name EC2-S3-Access-Role \
    --assume-role-policy-document file://trust-policy.json

# Attach a permissions policy to the role
aws iam attach-role-policy \
    --role-name EC2-S3-Access-Role \
    --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

# Create an instance profile (needed for EC2)
aws iam create-instance-profile --instance-profile-name EC2-S3-Profile
aws iam add-role-to-instance-profile \
    --instance-profile-name EC2-S3-Profile \
    --role-name EC2-S3-Access-Role

# Launch EC2 with the role
aws ec2 run-instances \
    --image-id ami-0c7217cdde317cfec \
    --instance-type t3.micro \
    --iam-instance-profile Name=EC2-S3-Profile

Custom Policies (JSON)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::my-app-bucket/uploads/*"
    },
    {
      "Effect": "Deny",
      "Action": "s3:DeleteBucket",
      "Resource": "*"
    }
  ]
}
# Create a customer managed policy
aws iam create-policy \
    --policy-name AppUploadAccess \
    --policy-document file://app-policy.json

# Attach to user or role
aws iam attach-user-policy \
    --user-name developer-1 \
    --policy-arn arn:aws:iam::123456789012:policy/AppUploadAccess

IAM Best Practices

# Practice Why
1 No root user access keys Use the root account only for billing and account recovery
2 Enable MFA Required for root and strongly recommended for all users
3 Least privilege Start with minimum permissions, grant more only when needed
4 Use roles for EC2 Never store access keys on instances โ€” use instance profiles
5 Audit with IAM Access Analyzer Identifies unused permissions and external access
6 Use groups, not individual policies Attach policies to groups, add users to groups
7 Rotate keys regularly Max 90 days; use IAM Access Advisor for unused keys
8 Use conditions in policies Add aws:SourceIp, aws:RequestedRegion constraints

5. VPC โ€” Virtual Private Cloud Basics

VPC Components

Component Purpose
VPC Isolated virtual network (one per account per region)
Subnet IP range within a VPC (public or private)
Internet Gateway (IGW) Enables public internet access for public subnets
NAT Gateway Allows private subnets to reach the internet (outbound only)
Route Table Controls traffic routing between subnets and gateways
Security Group Instance-level firewall (stateful)
Network ACL Subnet-level firewall (stateless)
VPC Endpoint Private connection to AWS services (no internet needed)

Default vs. Custom VPC

When you create an AWS account, each region gets a default VPC with: - A /16 CIDR block (usually 172.31.0.0/16) - One public subnet per AZ - An internet gateway and main route table - Security group allowing all outbound traffic

Key CLI Commands

# List VPCs
aws ec2 describe-vpcs

# Create a VPC with a /16 CIDR
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --query 'Vpc.VpcId' --output text)

# Create a public subnet (/24)
aws ec2 create-subnet \
    --vpc-id $VPC_ID \
    --cidr-block 10.0.1.0/24 \
    --map-public-ip-on-launch

# Create a private subnet (/24)
aws ec2 create-subnet \
    --vpc-id $VPC_ID \
    --cidr-block 10.0.2.0/24

# Create an internet gateway and attach it
IGW_ID=$(aws ec2 create-internet-gateway --query 'InternetGateway.InternetGatewayId' --output text)
aws ec2 attach-internet-gateway --vpc-id $VPC_ID --internet-gateway-id $IGW_ID

# Create a route table and add a default route via IGW
RT_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID --query 'RouteTable.RouteTableId' --output text)
aws ec2 create-route --route-table-id $RT_ID --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID

# Associate the route table with the public subnet
aws ec2 associate-route-table --subnet-id subnet-xxxxx --route-table-id $RT_ID

Subnet Types

Type Route to IGW? Public IP on Launch? Use Case
Public Yes (0.0.0.0/0 โ†’ IGW) Yes Web servers, bastion hosts
Private No direct IGW route No Databases, app servers
VPN-only Routes via VPN/TGW No Internal services

6. Cost Management

AWS Budgets

# Create a monthly cost budget
aws budgets create-budget \
    --account-id 123456789012 \
    --budget '{
        "BudgetName": "Monthly-Dev-Budget",
        "BudgetLimit": {"Amount": "500", "Unit": "USD"},
        "TimePeriod": {"Start": "2026-01-01T00:00:00Z"},
        "TimeUnit": "MONTHLY",
        "BudgetType": "COST",
        "CostFilters": {"TagKeyValue": ["Environment:dev$"]},
        "CostTypes": {"IncludeTax": true, "IncludeSubscription": true}
    }' \
    --notifications-with-subscribers '[
        {
            "Notification": {
                "NotificationType": "ACTUAL",
                "ComparisonOperator": "GREATER_THAN",
                "Threshold": 80.0
            },
            "Subscribers": [
                {"SubscriptionType": "EMAIL", "Address": "team@example.com"}
            ]
        }
    ]'

Cost Explorer (CLI)

# Get last month's costs by service
aws ce get-cost-and-usage \
    --time-period Start=2026-05-01,End=2026-06-01 \
    --granularity MONTHLY \
    --metrics "BlendedCost" "UnblendedCost" \
    --group-by Type=DIMENSION,Key=SERVICE

Cost-Saving Strategies

Strategy Savings Effort
Reserved Instances / Savings Plans 30โ€“72% Low (commit 1โ€“3 yr)
Spot Instances 60โ€“90% Medium (fault-tolerant workloads)
S3 Lifecycle Policies 50โ€“80% Low (move cold data to Glacier)
Stop idle instances 100% of compute cost Medium (automate with Lambda)
Right-size instances 20โ€“50% Medium (use Compute Optimizer)
EBS gp3 instead of gp2 20%+ Low (same cost as gp2, better perf)
Delete unused EBS snapshots Varies Low
S3 Intelligent-Tiering 20โ€“40% Low (auto-optimize access patterns)

Cost Allocation Tags

# Activate cost allocation tags
aws ce update-cost-allocation-tags-status \
    --tags-status 'TagKey=Environment,Status=Active'

Recommended tag schema: Environment (dev/staging/prod), Project, Team, CostCenter, AutoStop.


7. Complete Example โ€” Launch EC2 with S3 Backend

This example provisions a web server that stores uploaded files in S3.

Prerequisites

# Verify AWS CLI is installed and configured
aws sts get-caller-identity

Step 1 โ€” Create an S3 Bucket

BUCKET="my-app-uploads-$(date +%s)"
aws s3 mb "s3://$BUCKET" --region us-east-1
echo "Bucket: $BUCKET"

Step 2 โ€” Create an IAM Role with S3 Access

cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {"Service": "ec2.amazonaws.com"},
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

aws iam create-role \
    --role-name WebServer-S3-Role \
    --assume-role-policy-document file://trust-policy.json

cat > s3-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::my-app-uploads-*",
        "arn:aws:s3:::my-app-uploads-*/*"
      ]
    }
  ]
}
EOF

aws iam put-role-policy \
    --role-name WebServer-S3-Role \
    --policy-name S3-Upload-Access \
    --policy-document file://s3-policy.json

aws iam create-instance-profile \
    --instance-profile-name WebServer-S3-Profile

aws iam add-role-to-instance-profile \
    --instance-profile-name WebServer-S3-Profile \
    --role-name WebServer-S3-Role

Step 3 โ€” Set Up Networking (Security Group + Key Pair)

# Create a key pair
aws ec2 create-key-pair \
    --key-name webserver-key \
    --query 'KeyMaterial' --output text > webserver-key.pem
chmod 400 webserver-key.pem

# Security group
SG_ID=$(aws ec2 create-security-group \
    --group-name webserver-sg \
    --description "Web server SG (SSH + HTTP)" \
    --query 'GroupId' --output text)

aws ec2 authorize-security-group-ingress \
    --group-id "$SG_ID" \
    --protocol tcp --port 22 --cidr $(curl -s ifconfig.me)/32

aws ec2 authorize-security-group-ingress \
    --group-id "$SG_ID" \
    --protocol tcp --port 80 --cidr 0.0.0.0/0

Step 4 โ€” Launch the EC2 Instance

# Get latest Amazon Linux 2023 AMI
AMI=$(aws ec2 describe-images \
    --owners amazon \
    --filters 'Name=name,Values=al2023-ami-*-kernel-6.1-x86_64' \
    --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' \
    --output text)

INSTANCE_ID=$(aws ec2 run-instances \
    --image-id "$AMI" \
    --instance-type t3.micro \
    --key-name webserver-key \
    --security-group-ids "$SG_ID" \
    --iam-instance-profile Name=WebServer-S3-Profile \
    --user-data '#!/bin/bash
dnf update -y
dnf install -y httpd
systemctl enable --now httpd
echo "<h1>EC2 + S3 Demo</h1><p>Bucket: '"$BUCKET"'</p>" > /var/www/html/index.html
echo "<?php putenv(\"BUCKET=$BUCKET\"); ?>" > /var/www/html/env.php
' \
    --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=WebServer-Demo}]' \
    --query 'Instances[0].InstanceId' --output text)

echo "Instance ID: $INSTANCE_ID"

# Wait for running state and get public IP
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"
PUBLIC_IP=$(aws ec2 describe-instances \
    --instance-ids "$INSTANCE_ID" \
    --query 'Reservations[0].Instances[0].PublicIpAddress' \
    --output text)

echo "Connect via: ssh -i webserver-key.pem ec2-user@$PUBLIC_IP"
echo "Visit: http://$PUBLIC_IP"

Step 5 โ€” Verify

# SSH to the server
ssh -i webserver-key.pem ec2-user@"$PUBLIC_IP"

# Test S3 access from the instance
aws s3 ls "s3://$BUCKET"

# Create a test file and upload
echo "Hello from EC2 $(date)" > test.txt
aws s3 cp test.txt "s3://$BUCKET/"

# Verify the file exists
aws s3 ls "s3://$BUCKET/"

# Generate a presigned URL for the file
aws s3 presign "s3://$BUCKET/test.txt" --expires-in 300

Step 6 โ€” Clean Up

# Clean up S3 (must empty bucket first)
aws s3 rm "s3://$BUCKET" --recursive
aws s3 rb "s3://$BUCKET"

# Terminate EC2
aws ec2 terminate-instances --instance-ids "$INSTANCE_ID"
aws ec2 wait instance-terminated --instance-ids "$INSTANCE_ID"

# Clean up IAM resources
aws iam remove-role-from-instance-profile \
    --instance-profile-name WebServer-S3-Profile \
    --role-name WebServer-S3-Role
aws iam delete-instance-profile --instance-profile-name WebServer-S3-Profile
aws iam delete-role-policy --role-name WebServer-S3-Role --policy-name S3-Upload-Access
aws iam delete-role --role-name WebServer-S3-Role

# Delete security group
aws ec2 delete-security-group --group-id "$SG_ID"

8. Troubleshooting & Tips

Common CLI Errors

Error Likely Cause Fix
An error occurred (AuthFailure) when calling ... Invalid/expired keys Run aws configure or check env vars AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
Unable to locate credentials No credentials configured Set AWS_PROFILE or configure ~/.aws/credentials
The config profile (...) could not be found Profile doesn't exist Check ~/.aws/config and ~/.aws/credentials
RequestExpired Clock skew Sync system clock: sudo ntpdate -s time.nist.gov
AccessDenied when calling S3 Bucket policy or IAM policy blocking Use IAM Policy Simulator in the AWS Console
InvalidPermission.Duplicate Security group rule already exists It's idempotent โ€” can be safely ignored

Environment Variables (Overrides CLI config)

export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_DEFAULT_REGION=us-west-2
export AWS_DEFAULT_OUTPUT=json
export AWS_PROFILE=production

Useful Debugging Flags

# Increase verbosity
aws s3 ls --debug

# Dry-run (test permissions without making changes)
aws ec2 run-instances --dry-run --image-id ami-xxx --instance-type t3.micro

# Simulate IAM policy
aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/developer-1 \
    --action-names s3:PutObject ec2:RunInstances \
    --resource-arns arn:aws:s3:::my-bucket/*

Quick Reference โ€” Most Common Commands

# Identity
aws sts get-caller-identity

# S3
aws s3 ls
aws s3 sync ./dir s3://bucket/dir --delete
aws s3 presign s3://bucket/key --expires-in 3600

# EC2
aws ec2 describe-instances
aws ec2 stop-instances --instance-ids i-xxx
aws ec2 start-instances --instance-ids i-xxx

# IAM
aws iam list-users
aws iam list-attached-user-policies --user-name xxx

# Cost
aws ce get-cost-and-usage --time-period Start=2026-05-01,End=2026-06-01 --granularity MONTHLY --metrics BlendedCost

This article is part of the DevOps & Infrastructure KB. For corrections or updates, submit a PR or open an issue.