Omni FinOps

Why did my AWS bill go up? How to find the cause

A step-by-step way to trace an AWS bill increase to the service, the usage type and the resource behind it, with Cost Explorer and the CLI.

Mateo VargasUpdated · 5 min read
Contents
  1. Look at daily spend, not the monthly total
  2. Group by service
  3. Then by usage type
  4. Narrow to an account, a Region or a team
  5. Find the resource
  6. The usual causes
  7. Get told next time

In short

  • Compare daily spend, not the monthly total: a jump has a first day.
  • Group by service, then by usage type, then by resource or tag. Each step narrows it.
  • Most jumps are something that scaled out and never scaled back, new data transfer, or a forgotten resource.

A bigger AWS bill is never a mystery for long. Something started on a specific day, in a specific service, under a specific usage type, and usually on one resource. The work is narrowing it down in that order, without guessing.

This guide uses Cost Explorer in the console and the same queries from the AWS CLI. Each step cuts the search space, so you get from “the bill is up” to a named resource quickly.

Look at daily spend, not the monthly total

A monthly total hides the shape of the change. A jump has a first day, and the first day tells you what to look for: a deploy, a scaling event, a new team’s workload, a migration.

Cost Explorer keeps daily and monthly data for the current month plus the previous 13 months (AWS docs). Set granularity to Daily, pick the last 30 to 60 days, and look for the step. A step that stays up is a new steady cost. A single spike is a one-off, such as a large data transfer or a backfill.

From the CLI, the daily total looks like this. The end date is exclusive:

aws ce get-cost-and-usage \
  --time-period Start=2026-09-01,End=2026-10-11 \
  --granularity DAILY \
  --metrics UnblendedCost \
  --query 'ResultsByTime[].[TimePeriod.Start, Total.UnblendedCost.Amount]' \
  --output table

Every aws ce call in this guide is a Cost Explorer API request, billed at $0.01 per paginated request as of October 2026 (pricing). A handful of queries costs cents, but don’t loop them in a script that runs every minute.

Group by service

Once you know the first day, compare it with the day before, grouped by service. Run the same query once for each day:

aws ce get-cost-and-usage \
  --time-period Start=2026-10-10,End=2026-10-11 \
  --granularity DAILY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE \
  --query 'ResultsByTime[].Groups[].[Keys[0], Metrics.UnblendedCost.Amount]' \
  --output table

The service whose number moved is your lead. Two names cause confusion here. Amazon Elastic Compute Cloud - Compute is instance hours. EC2 - Other is everything billed around instances: EBS volumes and snapshots, NAT gateways and data transfer. A jump in EC2 - Other is rarely about instances.

Then by usage type

A service is still too broad. Filter to it and group by usage type, which names the exact meter that moved:

aws ce get-cost-and-usage \
  --time-period Start=2026-10-09,End=2026-10-11 \
  --granularity DAILY \
  --metrics UnblendedCost UsageQuantity \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["EC2 - Other"]}}' \
  --group-by Type=DIMENSION,Key=USAGE_TYPE \
  --output json

Usage types read like BoxUsage:c5.4xlarge (instance hours of one type), EBS:VolumeUsage.gp2 (volume storage) or names ending in NatGateway-Bytes (data processed by NAT gateways). Look at UsageQuantity as well as cost: if hours doubled, something new is running; if gigabytes doubled, something is moving more data.

Narrow to an account, a Region or a team

You can group by up to two dimensions at once. Linked account and Region together usually point at one workload:

aws ce get-cost-and-usage \
  --time-period Start=2026-10-10,End=2026-10-11 \
  --granularity DAILY \
  --metrics UnblendedCost \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Elastic Compute Cloud - Compute"]}}' \
  --group-by Type=DIMENSION,Key=LINKED_ACCOUNT Type=DIMENSION,Key=REGION \
  --output table

If your teams tag resources and the tag is activated as a cost allocation tag, group by it instead with Type=TAG,Key=team. Untagged spend shows up with an empty value, which is useful on its own: it’s the spend nobody owns.

Find the resource

Cost Explorer can show resource-level data for EC2, but it’s opt-in, covers only the previous 14 days, and costs $0.01 per 1,000 usage records a month as of October 2026 (pricing). Often the inventory gets you there faster. If instance hours jumped on October 10, list what launched that day:

aws ec2 describe-instances \
  --filters Name=launch-time,Values='2026-10-10*' \
  --query 'Reservations[].Instances[].[InstanceId, InstanceType, LaunchTime,
      Tags[?Key==`Name`]|[0].Value]' \
  --output table

If the new instances belong to an Auto Scaling group, its activity history says when and why it scaled:

aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name web-asg \
  --max-items 20 \
  --query 'Activities[].[StartTime, Description]' \
  --output table

The usual causes

Most jumps come from a short list. Check these first.

What you see Usual cause Where to look
Instance hours up, steady since one day An Auto Scaling group scaled out and never scaled back in, or a new service launched Scaling activities, desired vs minimum capacity
EC2 - Other up, usage type NatGateway-Bytes More traffic through a NAT gateway, often to S3 or another AWS service VPC flow logs, gateway endpoints
EC2 - Other up, EBS: usage types New volumes, or volumes left behind by terminated instances Unattached EBS volumes
Data transfer up A new cross-Region or internet-facing flow Usage types containing DataTransfer
VPC up, public IPv4 usage More public IPv4 addresses, each billed by the hour Unused public IPs

NAT gateways deserve a note: on top of the hourly charge, they bill $0.045 per GB processed in US East as of October 2026 (VPC pricing). A workload that starts pulling a lot of data from S3 through a NAT gateway can move the bill noticeably on its own.

Get told next time

You don’t need to find the next jump by hand. AWS Cost Anomaly Detection is free (AWS). It runs about three times a day, can take up to 24 hours to flag an anomaly, needs about 10 days of history for a new service, and splits the root cause by service, account, Region and usage type (AWS docs). A monitor of AWS services is a good start. An account can have only one, and AWS has created one automatically for new Cost Explorer users since March 2023 (AWS), so check first:

aws ce get-anomaly-monitors \
  --query 'AnomalyMonitors[?MonitorDimension==`SERVICE`].[MonitorName,MonitorArn]'

Only if that prints nothing, create one:

aws ce create-anomaly-monitor --anomaly-monitor \
  '{"MonitorName":"by-service","MonitorType":"DIMENSIONAL","MonitorDimension":"SERVICE"}'

Then add an alert subscription in the console, with an email or SNS target and a dollar threshold you’d actually act on.

Omni FinOps does the same narrowing on every refresh: it marks the first day spend ran above typical and names the service, account and resource behind it in one sentence. Once you’ve found the cause, the fix is usually one of the other guides: an idle instance, an oversized one, or a dev environment running all night.

Mateo Vargas writes Omni FinOps’ cost guides, from the same rules the product runs on every refresh. Prices are list prices in us-east-1 or us-central1 as of October 11, 2026; check your region’s pricing page.

More guides