Your bill is higher than last month and nothing obvious changed. Traffic is flat, nobody launched anything big, and the invoice is one long list of line items.

The good news: AWS records exactly where every cent went. Finding the cause is a matter of narrowing it down in the right order — service, then usage type, then region.

Step 1: compare like with like

The most common mistake is comparing this month so far with the whole of last month. Ten days of spending will always look smaller than thirty, and a real spike hides behind it.

Compare the same days of each month instead: the 1st to the 10th of this month against the 1st to the 10th of last month.

Step 2: find the service that moved

In the console: Billing and Cost Management → Cost Explorer.

  1. Set the date range to the same days of this month and last month
  2. Granularity: Daily
  3. Group by: Service

The service whose bar grew is your answer to the first question. Daily granularity also shows you the day it started, which usually tells you what caused it — a deploy, a migration, a new feature going live.

From the command line, run this once for each period and compare the two:

aws ce get-cost-and-usage \ --time-period Start=2026-11-01,End=2026-11-11 \ --granularity MONTHLY \ --metrics UnblendedCost \ --group-by Type=DIMENSION,Key=SERVICE \ --query 'ResultsByTime[].Groups[].[Keys[0], Metrics.UnblendedCost.Amount]' \ --output text | sort -t$'\t' -k2 -rn 

The End date is exclusive, so End=2026-11-11 includes the 10th. Change the dates to 2026-10-01 and 2026-10-11 for the same window last month.

Each Cost Explorer API request costs $0.01. Running this a few times costs nothing worth worrying about — but don't put it in a loop that runs every minute.

Step 3: find what inside that service

"EC2" on its own isn't an answer. It covers instances, disks, snapshots, IP addresses and data transfer. Group the service you found by usage type:

aws ce get-cost-and-usage \ --time-period Start=2026-11-01,End=2026-11-11 \ --granularity MONTHLY \ --metrics UnblendedCost \ --filter '{"Dimensions":{"Key":"SERVICE","Values":["EC2 - Other"]}}' \ --group-by Type=DIMENSION,Key=USAGE_TYPE \ --query 'ResultsByTime[].Groups[].[Keys[0], Metrics.UnblendedCost.Amount]' \ --output text | sort -t$'\t' -k2 -rn | head -n 10 

Replace EC2 - Other with the service name exactly as it appeared in Step 2. The usage type tells you what is actually billing — NatGateway-Bytes, EBS:VolumeUsage.gp2, DataTransfer-Regional-Bytes and so on.

Then group by REGION instead of USAGE_TYPE. A cost in a region your team doesn't use is worth investigating straight away — that's where forgotten resources live, and it's where an attacker running up your bill would put them.

The usual culprits

Most unexplained increases come down to a short list:

  • NAT Gateway data processing. Traffic from private subnets to S3, ECR or other AWS services goes through the NAT and is charged per GB. A new service pulling container images or writing logs can quietly double it. A free gateway endpoint for S3 fixes most of it.
  • Data transfer. Traffic between availability zones and between regions is charged in both directions. A new replica or a chatty service in another AZ shows up here.
  • Auto Scaling that scaled up and never came down. A wrong metric or a missing maximum keeps adding instances.
  • Things left behind. Stopped instances still bill for their disks, deleted instances often leave their data volumes behind, and old snapshots pile up. See why a stopped EC2 instance still costs money and how to find unattached EBS volumes.
  • Public IPv4 addresses. AWS charges for every public IPv4 address, including ones attached to running instances — not only unused Elastic IPs.
  • CloudWatch Logs. Debug logging left on in production, with retention set to "never expire", grows every single day.
  • Free tier or credits ending. If nothing changed in usage but the bill appeared, check whether a free tier period or promotional credits ran out.
  • Leaked credentials. A sudden spend in an unfamiliar region, or on a service you've never used — often EC2 GPU instances or Bedrock — means someone else is using your account. Deactivate the keys and open a support case immediately.

How to stop finding out at month end

Finding the cause is the easy part. The expensive part is the three weeks before anyone looked.

Turn on AWS Cost Anomaly Detection. It's free, it's in Billing and Cost Management, and it learns your normal spend per service. Point its alerts at an email address someone actually reads.

Add a budget with daily alerts, not only monthly. A monthly budget tells you after the money has gone.

Set limits where you can. A maximum on every Auto Scaling group means a runaway config stops at a number you chose.

Getting told, with the cause attached

Devopsly checks your spending every day. Connect your AWS account read-only and it compares each service against the same days of the previous three months — like for like, so a spike shows up on day three, not day thirty. Anything up at least 20% and at least $5 is flagged with the service, what you spent and what you'd normally spend. On Starter and Growth you get it by email or Slack.

It also flags the leftovers from the list above — stopped instances, unattached volumes — in the same place, so a spike and its likely cause aren't in two different tools.

It ignores decreases and services that are brand new this month, because there's nothing to compare them against. It uses 15 read-only IAM actions and never touches your infrastructure or opens your files.

See what it finds in a real account →