Back to all articles
Cloud & FinOps2026-10-01

AWS FinOps: Audit Cloud Costs with the AWS CLI

AWS FinOps cost audit with the AWS CLI: monthly cost by service, unattached EBS volumes, idle Elastic IPs, and old snapshots.

AWS cost management with FinOps starts from a list: what the account paid this month, and what is still running without serving an application. This guide uses AWS CLI v2 for that audit. Do not delete anything until someone confirms the ID is orphaned.

The framing, without the commands, is in how to reduce AWS costs. Reading the bill together with the architecture is the work of FinOps consulting.

1. Check the account and the region

Cost Explorer answers in us-east-1. EC2 is regional: the same command has to run in every region where the account creates resources.

aws sts get-caller-identity
export AWS_DEFAULT_REGION=us-east-1

The minimum policy for the commands below is ce:GetCostAndUsage, ec2:DescribeVolumes, ec2:DescribeAddresses, and ec2:DescribeSnapshots. Without ce:GetCostAndUsage, the first block returns AccessDenied.

2. This month's cost by service

The Cost Explorer end date is exclusive. For September 2026, End is October 1.

aws ce get-cost-and-usage \
  --time-period Start=2026-09-01,End=2026-10-01 \
  --granularity MONTHLY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --output table

What matters in the table is the service that was not expected: NAT Gateway, Elastic IP, and snapshots often grow without a new deploy. Note the top three before you look at instances one by one.

3. EBS volumes with no instance

A volume in available is not attached. The account still pays for the gigabytes.

for region in us-east-1 sa-east-1 eu-west-1; do
  echo "== $region =="
  aws ec2 describe-volumes \
    --region "$region" \
    --filters Name=status,Values=available \
    --query 'Volumes[].{ID:VolumeId,GiB:Size,AZ:AvailabilityZone}' \
    --output table
done

Include the regions the company actually uses. A 100 GB volume left available for weeks is a candidate for a snapshot and deletion, after the team confirms it.

4. Elastic IP with no association

AWS charges for an Elastic IP that is allocated and not attached to an instance or an in-use NAT gateway.

aws ec2 describe-addresses \
  --region us-east-1 \
  --query 'Addresses[?AssociationId==`null`].[PublicIp,AllocationId]' \
  --output table

Repeat with --region for each region. An empty list is the healthy result. An IP on the list is an orphaned resource, not an address to reuse without an owner.

5. Old snapshots owned by the account

aws ec2 describe-snapshots \
  --region us-east-1 \
  --owner-ids self \
  --query 'sort_by(Snapshots, &StartTime)[].{ID:SnapshotId,Started:StartTime,GiB:VolumeSize}' \
  --output table

Daily snapshots with no retention policy accumulate gigabytes. A practical rule is to keep what you need to restore (for example 7 daily and 4 weekly) and delete the rest with aws ec2 delete-snapshot --snapshot-id snap-... only after naming what each one protects.

Conclusion

These four commands split the bill into service, loose disk, loose IP, and old backup. That is the first hour of a FinOps audit. Instance right-sizing and Graviton stay in reducing AWS costs. Reading the bill together with an architecture change is FinOps consulting.

FinOps and cloud consulting

Bill audit and multicloud architecture with cost governance on AWS, GCP, and Azure.

Talk about FinOps