AWS Debugging & Operational Scenarios
1. EC2 Instance Unreachable (SSH Timeout)
flowchart TD
A[SSH timeout] --> B{"Instance<br/>running?"}
B -- No --> B1[Start instance]
B -- Yes --> C{"SG allows<br/>port 22 from IP?"}
C -- No --> C1["Add inbound rule<br/>port 22 your IP"]
C -- Yes --> D{"NACL allows<br/>port 22?"}
D -- No --> D1["Update NACL<br/>inbound + ephemeral"]
D -- Yes --> E{"Public IP<br/>assigned?"}
E -- No --> E1["Allocate & associate<br/>Elastic IP"]
E -- Yes --> F{"Route table<br/>has IGW?"}
F -- No --> F1["Add 0.0.0.0/0 --> IGW"]
F -- Yes --> G{"OS firewall<br/>blocking?"}
G -- Yes --> G1["Use EC2 Serial Console<br/>or SSM to disable"]
G -- No --> H["Check system log<br/>for boot errors"]
Commands
# Check instance state
aws ec2 describe-instances --instance-ids i-xxxx \
--query 'Reservations[].Instances[].[InstanceId,State.Name,PublicIpAddress]'
# Check security group rules
aws ec2 describe-security-groups --group-ids sg-xxxx \
--query 'SecurityGroups[].IpPermissions'
# Check NACLs for the subnet
SUBNET=$(aws ec2 describe-instances --instance-ids i-xxxx \
--query 'Reservations[].Instances[].SubnetId' --output text)
aws ec2 describe-network-acls \
--filters "Name=association.subnet-id,Values=$SUBNET"
# Check route table
aws ec2 describe-route-tables \
--filters "Name=association.subnet-id,Values=$SUBNET"
# Get system log (boot errors)
aws ec2 get-console-output --instance-id i-xxxx --latest
# Connect via SSM (no SSH needed)
aws ssm start-session --target i-xxxx
Prevention: Never rely on SSH for production access — use SSM Session Manager instead (no port 22, no key management, full audit trail in CloudTrail). Enforce this with an SCP: Deny ec2:AuthorizeSecurityGroupIngress where port=22 from 0.0.0.0/0. Keep an EC2 Systems Manager baseline policy on every instance role.
2. Lambda Function Timing Out
flowchart TD
A[Lambda timeout] --> B{"Timeout setting<br/>too low?"}
B -- Yes --> B1["Increase timeout<br/>max 15 min"]
B -- No --> C{"DB connection<br/>pool exhausted?"}
C -- Yes --> C1["Use RDS Proxy<br/>or reduce pool size"]
C -- No --> D{Lambda in VPC?}
D -- Yes --> E{"Reaches NAT<br/>or VPC endpoint?"}
E -- No --> E1["Add NAT GW<br/>or VPC endpoint"]
E -- Yes --> F["Check CloudWatch Logs<br/>find hang point"]
D -- No --> F
F --> G{"Downstream<br/>service slow?"}
G -- Yes --> G1["Add timeout on<br/>HTTP client / SDK"]
G -- No --> H["Add X-Ray tracing<br/>find bottleneck"]
Commands
# Check current timeout
aws lambda get-function-configuration --function-name my-fn \
--query '[Timeout,MemorySize,VpcConfig]'
# Update timeout
aws lambda update-function-configuration \
--function-name my-fn --timeout 30
# Tail live logs
aws logs tail /aws/lambda/my-fn --follow
# Last 20 min of logs
aws logs filter-log-events \
--log-group-name /aws/lambda/my-fn \
--start-time $(date -v-20M +%s000) \
--filter-pattern "Task timed out"
# Check Lambda concurrency
aws lambda get-function-concurrency --function-name my-fn
# Enable X-Ray tracing
aws lambda update-function-configuration \
--function-name my-fn \
--tracing-config Mode=Active
Prevention: Set timeout to 2× your p99 execution time, never the max (15 min). Add a CloudWatch alarm on Errors + Throttles for every Lambda. Enable X-Ray tracing from day one — retrofitting tracing after a production timeout incident is painful. Use provisioned concurrency for latency-sensitive functions to eliminate cold start timeouts.
3. ALB Returns 502 Bad Gateway
flowchart TD
A[ALB 502] --> B{"Target group<br/>targets healthy?"}
B -- No --> C{"SG on target<br/>allows ALB SG?"}
C -- No --> C1["Add inbound rule:<br/>ALB SG --> app port"]
C -- Yes --> D{"App listening<br/>on correct port?"}
D -- No --> D1["Fix app config<br/>or target group port"]
D -- Yes --> E{"Health check path<br/>returns 200?"}
E -- No --> E1["Fix health check<br/>path or response"]
B -- Yes --> F{"Response timeout<br/>exceeded?"}
F -- Yes --> F1["Increase ALB idle<br/>timeout or fix app"]
F -- No --> G["Check app logs<br/>for 5xx errors"]
Commands
# List target group health
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:...
# Describe load balancer attributes (idle timeout)
aws elbv2 describe-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:...
# Modify idle timeout
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:... \
--attributes Key=idle_timeout.timeout_seconds,Value=120
# Check ALB access logs (if enabled)
aws s3 cp s3://my-alb-logs/AWSLogs/... ./alb-logs/ --recursive
# Check security group of target
aws ec2 describe-security-groups --group-ids sg-target-xxxx \
--query 'SecurityGroups[].IpPermissions'
# Describe target group health check settings
aws elbv2 describe-target-groups \
--target-group-arns arn:aws:elasticloadbalancing:... \
--query 'TargetGroups[].[HealthCheckPath,HealthCheckPort,Matcher]'
Prevention: Set deregistration_delay to 30s (down from 300s default) so deployments drain fast. Set slow_start.duration_seconds = 30 so new targets warm up before receiving full traffic. Alert on ALB HTTPCode_Target_5XX_Count > 0 in CloudWatch. Test health check endpoint independently in staging with the exact ALB matcher config.
4. S3 Access Denied
flowchart TD
A[S3 AccessDenied] --> B{"IAM policy allows<br/>s3:GetObject?"}
B -- No --> B1["Add s3:GetObject<br/>to IAM policy"]
B -- Yes --> C{"Bucket policy<br/>explicitly denies?"}
C -- Yes --> C1["Remove Deny<br/>from bucket policy"]
C -- No --> D{"Block Public Access<br/>overriding?"}
D -- Yes --> D1["Disable block<br/>or use IAM auth"]
D -- No --> E{"Cross-account<br/>access?"}
E -- Yes --> F{"Both IAM + bucket<br/>policy allow?"}
F -- No --> F1["Add bucket policy<br/>to trust account"]
F -- Yes --> G{"Bucket ACL<br/>blocking? legacy"}
G -- Yes --> G1["Update or disable<br/>legacy ACL"]
G -- No --> H["Enable S3 server<br/>access logging"]
Commands
# Simulate IAM policy evaluation
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789:role/my-role \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::my-bucket/my-key
# Get bucket policy
aws s3api get-bucket-policy --bucket my-bucket
# Check Block Public Access settings
aws s3api get-public-access-block --bucket my-bucket
# Check bucket ACL
aws s3api get-bucket-acl --bucket my-bucket
# Check object ACL
aws s3api get-object-acl --bucket my-bucket --key my-key
# List bucket ownership controls
aws s3api get-bucket-ownership-controls --bucket my-bucket
Prevention: Use IAM Access Analyzer to continuously audit S3 bucket policies and flag public or cross-account access. Enable S3 Block Public Access at the account level. Test IAM policies with aws iam simulate-principal-policy in CI before deploying policy changes.
5. RDS Connection Refused from ECS/Lambda
flowchart TD
A["Connection refused<br/>to RDS"] --> B{"RDS SG allows<br/>ECS/Lambda SG<br/>on port 5432?"}
B -- No --> B1["Add inbound rule<br/>source: task SG"]
B -- Yes --> C{"Lambda in VPC<br/>same as RDS?"}
C -- No --> C1["Attach Lambda<br/>to RDS VPC"]
C -- Yes --> D{"RDS in private<br/>subnet only?"}
D -- Yes --> E{"Subnets + route<br/>reachable from task?"}
E -- No --> E1["Fix subnet routing<br/>or peering"]
E -- Yes --> F{"max_connections<br/>exhausted?"}
F -- Yes --> F1["Use RDS Proxy<br/>to pool connections"]
F -- No --> G{"RDS publicly<br/>accessible=false?"}
G -- No-public/correct --> H["Check RDS logs<br/>for auth errors"]
Commands
# Describe RDS instance network config
aws rds describe-db-instances --db-instance-identifier my-db \
--query 'DBInstances[].[DBInstanceStatus,PubliclyAccessible,VpcSecurityGroups,Endpoint]'
# Check SG rules on RDS
aws ec2 describe-security-groups --group-ids sg-rds-xxxx
# Check RDS parameter max_connections
aws rds describe-db-parameters \
--db-parameter-group-name my-pg \
--query 'Parameters[?ParameterName==`max_connections`]'
# Check RDS enhanced monitoring / CloudWatch
aws cloudwatch get-metric-statistics \
--namespace AWS/RDS \
--metric-name DatabaseConnections \
--dimensions Name=DBInstanceIdentifier,Value=my-db \
--start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 60 --statistics Maximum
# Create RDS Proxy
aws rds create-db-proxy \
--db-proxy-name my-proxy \
--engine-family POSTGRESQL \
--auth '[{"AuthScheme":"SECRETS","SecretArn":"arn:...","IAMAuth":"DISABLED"}]' \
--role-arn arn:aws:iam::...role/rds-proxy-role \
--vpc-subnet-ids subnet-a subnet-b \
--vpc-security-group-ids sg-rds-xxxx
Prevention: Use RDS Proxy to pool connections — eliminates connection exhaustion as services scale. Set max_connections parameter group value explicitly. Alert on DatabaseConnections CloudWatch metric > 80% of max_connections. Use IAM auth for RDS so credentials never expire or get leaked.
6. ECS Task Keeps Restarting (Exit Code 1)
flowchart TD
A["ECS task restarts<br/>exit code 1"] --> B["Check CloudWatch Logs<br/>/ecs/task-def-name"]
B --> C{"Missing env var<br/>or secret?"}
C -- Yes --> D{"Task role can<br/>read secret?"}
D -- No --> D1["Add secretsmanager:GetSecretValue<br/>to task role"]
D -- Yes --> E["Verify secret ARN<br/>in task definition"]
C -- No --> F{"Health check<br/>failing?"}
F -- Yes --> F1["Fix health check<br/>path or grace period"]
F -- No --> G{"Wrong entrypoint<br/>or command?"}
G -- Yes --> G1["Fix CMD/ENTRYPOINT<br/>in task definition"]
G -- No --> H["Run container locally<br/>docker run same image"]
Commands
# List stopped tasks with reason
aws ecs list-tasks --cluster my-cluster --desired-status STOPPED
aws ecs describe-tasks --cluster my-cluster \
--tasks task-id-xxxx \
--query 'tasks[].[taskArn,stoppedReason,containers[].{name:name,exitCode:exitCode,reason:reason}]'
# Get CloudWatch log group for task
aws ecs describe-task-definition --task-definition my-task:5 \
--query 'taskDefinition.containerDefinitions[].logConfiguration'
# Tail ECS logs
aws logs tail /ecs/my-task --follow
# Check task IAM role policies
aws iam list-role-policies --role-name ecsTaskRole
aws iam get-role-policy --role-name ecsTaskRole --policy-name MyPolicy
# Verify secret exists
aws secretsmanager describe-secret --secret-id my-secret
# Increase health check grace period
aws ecs update-service --cluster my-cluster --service my-svc \
--health-check-grace-period-seconds 120
Prevention: Use ECS Exec (SSM-based) instead of SSH for debugging running containers. Set healthCheckGracePeriodSeconds equal to your app's p99 startup time. Alert on ECS ServiceTaskCount < desired. Use task role + Secrets Manager for credentials — never bake secrets into environment variables.
7. CloudFormation Stack Stuck in UPDATE_ROLLBACK_FAILED
flowchart TD
A[UPDATE_ROLLBACK_FAILED] --> B["Open Events tab<br/>find failed resource"]
B --> C{"Resource manually<br/>modified outside CF?"}
C -- Yes --> D["Delete or restore<br/>resource manually"]
D --> E["Continue rollback<br/>via console or CLI"]
C -- No --> F{"Resource already<br/>deleted?"}
F -- Yes --> G["Skip resource via<br/>--resources-to-skip"]
F -- No --> H{"Dependency<br/>conflict?"}
H -- Yes --> H1["Resolve dependency<br/>then continue rollback"]
H -- No --> I["Contact AWS Support<br/>or import resource"]
Commands
# See stack events to find failed resource
aws cloudformation describe-stack-events \
--stack-name my-stack \
--query 'StackEvents[?ResourceStatus==`UPDATE_ROLLBACK_FAILED`]'
# Continue rollback (no skips)
aws cloudformation continue-update-rollback \
--stack-name my-stack
# Continue rollback skipping a specific resource
aws cloudformation continue-update-rollback \
--stack-name my-stack \
--resources-to-skip MyBucketLogicalId
# Describe current stack status
aws cloudformation describe-stacks \
--stack-name my-stack \
--query 'Stacks[].[StackStatus,StackStatusReason]'
# Import existing resource into stack (alternative)
aws cloudformation create-change-set \
--stack-name my-stack \
--change-set-name import-fix \
--change-set-type IMPORT \
--resources-to-import '[{"ResourceType":"AWS::S3::Bucket","LogicalResourceId":"MyBucket","ResourceIdentifier":{"BucketName":"my-bucket"}}]' \
--template-body file://template.yaml
Prevention: Always use Change Sets before any update to a production stack — never run aws cloudformation update-stack directly. Enable stack termination protection on all production stacks. Use DeletionPolicy: Retain on stateful resources (RDS, S3) so accidental stack deletion doesn't destroy data.
8. API Gateway Returns 429 Too Many Requests
flowchart TD
A[API GW 429] --> B{"Account-level<br/>throttle hit?<br/>10000 RPS default"}
B -- Yes --> B1["Request limit increase<br/>via Service Quotas"]
B -- No --> C{"Stage-level throttle<br/>configured?"}
C -- Yes --> C1["Increase stage<br/>default throttle"]
C -- No --> D{"Per-route throttle<br/>configured?"}
D -- Yes --> D1["Increase method<br/>throttle setting"]
D -- No --> E{"Usage Plan<br/>quota exhausted?"}
E -- Yes --> E1["Increase quota<br/>or add API key tier"]
E -- No --> F{"Lambda reserved<br/>concurrency limit?"}
F -- Yes --> F1["Increase Lambda<br/>concurrency limit"]
F -- No --> G["Enable API GW<br/>CloudWatch metrics"]
Commands
# Get stage throttle settings
aws apigateway get-stage \
--rest-api-id abc123 \
--stage-name prod \
--query '[defaultRouteSettings,throttlingBurstLimit,throttlingRateLimit]'
# Update stage-level throttle
aws apigateway update-stage \
--rest-api-id abc123 \
--stage-name prod \
--patch-operations \
op=replace,path=/defaultRouteSettings/throttlingRateLimit,value=5000 \
op=replace,path=/defaultRouteSettings/throttlingBurstLimit,value=10000
# List usage plans
aws apigateway get-usage-plans
# Check current account throttle limits
aws service-quotas list-service-quotas \
--service-code apigateway \
--query 'Quotas[?contains(QuotaName,`throttle`)]'
# Request quota increase
aws service-quotas request-service-quota-increase \
--service-code apigateway \
--quota-code L-310E2D3C \
--desired-value 20000
# Check Lambda concurrency
aws lambda get-function-concurrency --function-name my-fn
# CloudWatch 429 metric
aws cloudwatch get-metric-statistics \
--namespace AWS/ApiGateway \
--metric-name 4XXError \
--dimensions Name=ApiName,Value=my-api Name=Stage,Value=prod \
--start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 60 --statistics Sum
Prevention: Set Usage Plans and API Keys for all external-facing APIs. Use Lambda reserved concurrency to prevent a burst from one API consuming all account concurrency. Add a WAF rate-based rule as an additional layer. Alert on 4XXError rate > 5% in CloudWatch — catches throttling before it becomes an outage.
9. EKS Nodes Not Joining the Cluster
flowchart TD
A["Node missing or<br/>NotReady in kubectl"] --> B{"Node IAM role in<br/>aws-auth ConfigMap?"}
B -- No --> B1["Add role ARN to<br/>aws-auth configmap"]
B -- Yes --> C{"VPC CNI pods<br/>running?"}
C -- No --> C1["kubectl logs<br/>aws-node pod"]
C -- Yes --> D{"SG allows 443<br/>node --> control plane?"}
D -- No --> D1["Add SG rule:<br/>node SG --> cluster SG 443"]
D -- Yes --> E{"SG allows 10250<br/>control plane --> node?"}
E -- No --> E1["Add SG rule:<br/>cluster SG --> node SG 10250"]
E -- Yes --> F["SSH to node<br/>check kubelet logs"]
F --> G[journalctl -u kubelet -f]
Commands
# Check node status
kubectl get nodes -o wide
# View aws-auth ConfigMap
kubectl get configmap aws-auth -n kube-system -o yaml
# Add node IAM role to aws-auth (edit directly)
kubectl edit configmap aws-auth -n kube-system
# Add under mapRoles:
# - rolearn: arn:aws:iam::123456789:role/eks-node-role
# username: system:node:{{EC2PrivateDNSName}}
# groups: [system:bootstrappers, system:nodes]
# Check VPC CNI pods
kubectl get pods -n kube-system -l k8s-app=aws-node
kubectl logs -n kube-system daemonset/aws-node
# Check node SG rules (cluster SG)
CLUSTER_SG=$(aws eks describe-cluster --name my-cluster \
--query 'cluster.resourcesVpcConfig.clusterSecurityGroupId' --output text)
aws ec2 describe-security-groups --group-ids $CLUSTER_SG
# Check kubelet logs on node (via SSM)
aws ssm start-session --target i-nodexxxx
# Then: journalctl -u kubelet -f
# Describe node for events
kubectl describe node ip-10-0-x-x.ec2.internal
Prevention: Use managed node groups — AWS handles the bootstrap script and AMI versioning. Pin AMI IDs in Terraform and test node-join in a staging cluster before rolling to prod. Use Launch Template user data validation: add a curl to the EKS cluster endpoint as the last line of bootstrap — it fails fast if networking is wrong. Alert on cluster_failed_node_count > 0.
10. High AWS Bill — Finding the Source
flowchart TD
A[Unexpected high bill] --> B["Cost Explorer:<br/>group by Service"]
B --> C{Top service?}
C -- EC2/VPC --> D{"NAT GW data<br/>transfer high?"}
D -- Yes --> D1["Add VPC endpoints<br/>for S3/DynamoDB"]
D -- No --> D2{"Inter-AZ<br/>transfer?"}
D2 -- Yes --> D3["Co-locate services<br/>same AZ or use AZ-aware LB"]
C -- EC2 --> E{"Orphaned EBS<br/>volumes?"}
E -- Yes --> E1["Delete unattached<br/>EBS volumes"]
C -- Any --> F{"Forgotten resources?<br/>NAT GW, EIP, snapshots"}
F -- Yes --> F1[Delete idle resources]
F -- No --> G["Enable Cost Anomaly<br/>Detection alerts"]
G --> H["Set budget alert<br/>aws budgets"]
Commands
# Cost Explorer: top services last 30 days
aws ce get-cost-and-usage \
--time-period Start=$(date -v-30d +%Y-%m-%d),End=$(date +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[].Groups[] | sort_by(@, &Metrics.BlendedCost.Amount) | reverse(@) | [:10]'
# Find unattached EBS volumes
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[].[VolumeId,Size,CreateTime]'
# Find unassociated Elastic IPs
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==null].[AllocationId,PublicIp]'
# Find NAT Gateways (cost ~$0.045/hr each)
aws ec2 describe-nat-gateways \
--filter Name=state,Values=available \
--query 'NatGateways[].[NatGatewayId,SubnetId,CreateTime]'
# Check data transfer via NAT GW (CloudWatch)
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway \
--metric-name BytesOutToDestination \
--dimensions Name=NatGatewayId,Value=nat-xxxx \
--start-time $(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 3600 --statistics Sum
# Create Cost Anomaly Detection monitor
aws ce create-anomaly-monitor \
--anomaly-monitor '{"MonitorName":"AllServices","MonitorType":"DIMENSIONAL","MonitorDimension":"SERVICE"}'
# Create budget alert at $200/month
aws budgets create-budget \
--account-id 123456789012 \
--budget '{"BudgetName":"monthly-limit","BudgetLimit":{"Amount":"200","Unit":"USD"},"TimeUnit":"MONTHLY","BudgetType":"COST"}' \
--notifications-with-subscribers '[{"Notification":{"NotificationType":"ACTUAL","ComparisonOperator":"GREATER_THAN","Threshold":80},"Subscribers":[{"SubscriptionType":"EMAIL","Address":"you@example.com"}]}]'
Prevention: Set AWS Budgets alerts at 50%, 80%, and 100% of monthly forecast from day one. Enable Cost Anomaly Detection with a $50 absolute threshold — it alerts within hours of a runaway resource. Tag every resource with team, env, service tags enforced by an AWS Config rule. Review Cost Explorer weekly during growth phases.
Last updated: 2026-06-16