Home / AWS

AWSHow-toBeginner

AWS Budgets basics: set up your first cost alert and see what is costing money

Create a monthly AWS budget with email alerts from the CLI, add a forecast alert, use Cost Explorer to see which service is spending the money and fix the access errors you may hit.

Deepak Bhardwaj4 min read

Applies toAWS Budgets, AWS Cost Explorer, AWS CLI v2

The goal

Nobody should learn about an AWS overspend from the invoice. In about ten minutes you can get an email when spending reaches 80% of a limit you set, and another when AWS forecasts you'll go past it.

Warning

A basic budget only alerts you. It doesn't stop any resources, so the spending carries on until someone acts.

Step 1: check your access

Budgets and Cost Explorer need billing permissions. In a member account, the management account may also control what you can see.

aws sts get-caller-identity --query Account --output text
aws budgets describe-budgets --account-id 111122223333
Error Meaning Fix
AccessDeniedException on budgets:ViewBudget Your role lacks permissions Ask for budgets:ViewBudget and budgets:ModifyBudget
User not enabled for cost explorer access Cost Explorer isn't turned on yet Open Cost Explorer once in the console; data can take up to a day to appear
Billing pages show access denied for an IAM user, even with permissions IAM access to billing is turned off The root user enables it in Account settings

Step 2: create the budget

Save this as budget.json:

{
  "BudgetName": "monthly-total",
  "BudgetLimit": { "Amount": "500", "Unit": "USD" },
  "TimeUnit": "MONTHLY",
  "BudgetType": "COST"
}

Save the alerts as notifications.json. This defines one alert at 80% of actual spend and one for a forecast above 100%:

[
  {
    "Notification": {
      "NotificationType": "ACTUAL",
      "ComparisonOperator": "GREATER_THAN",
      "Threshold": 80,
      "ThresholdType": "PERCENTAGE"
    },
    "Subscribers": [
      { "SubscriptionType": "EMAIL", "Address": "cloud-team@contoso.com" }
    ]
  },
  {
    "Notification": {
      "NotificationType": "FORECASTED",
      "ComparisonOperator": "GREATER_THAN",
      "Threshold": 100,
      "ThresholdType": "PERCENTAGE"
    },
    "Subscribers": [
      { "SubscriptionType": "EMAIL", "Address": "cloud-team@contoso.com" }
    ]
  }
]

Create it:

ACCOUNT=$(aws sts get-caller-identity --query Account --output text)

aws budgets create-budget \
  --account-id "$ACCOUNT" \
  --budget file://budget.json \
  --notifications-with-subscribers file://notifications.json

Check that it exists:

aws budgets describe-budgets --account-id "$ACCOUNT" \
  --query "Budgets[].{Name:BudgetName,Limit:BudgetLimit.Amount,Actual:CalculatedSpend.ActualSpend.Amount,Forecast:CalculatedSpend.ForecastedSpend.Amount}" \
  --output table

aws budgets describe-notifications-for-budget \
  --account-id "$ACCOUNT" --budget-name monthly-total

Use a team mailbox rather than one person's email. People move on and alerts get missed.

Tip

Billing data isn't real time. It's refreshed several times a day, so a sudden spike can take hours to reach your budget. A new account may also need some spending history before forecast alerts work.

Step 3: see what is costing money

When an alert arrives, this answers "which service?". Set the dates to suit (the end date isn't included):

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

To see when it started, look at it day by day:

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

To break down one service by usage type, such as finding out whether EC2 spending is compute, EBS or data transfer:

aws ce get-cost-and-usage \
  --time-period Start=2026-11-01,End=2026-11-06 \
  --granularity MONTHLY \
  --metrics UnblendedCost \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Elastic Compute Cloud - Compute"]}}' \
  --group-by Type=DIMENSION,Key=USAGE_TYPE \
  --query "ResultsByTime[0].Groups[].[Keys[0],Metrics.UnblendedCost.Amount]" \
  --output table

Service names must match exactly. Take them from the first command's output. Each Cost Explorer API request has a small charge, so don't run these in a tight loop.

Common surprises

Cost you see Usual reason
EC2 - Other EBS volumes, snapshots, NAT gateways and data transfer
NAT gateway charges Private subnets pulling a lot of data through NAT
Elastic IP charges Public IPv4 addresses are charged, including idle ones
Costs after stopping an instance EBS volumes still bill while stopped
A region you don't use Forgotten test resources

Find idle items with the non-production scheduling guide and the NAT gateway cost guide.

Budgets per team

A budget can be limited to resources with a specific tag. The tag must first be activated as a cost allocation tag in the Billing console, and it only applies to costs from that point on:

{
  "BudgetName": "team-payments",
  "BudgetLimit": { "Amount": "200", "Unit": "USD" },
  "TimeUnit": "MONTHLY",
  "BudgetType": "COST",
  "CostFilters": { "TagKeyValue": ["user:team$payments"] }
}

Prevent it next time

  • Create a budget in every account the day it's created.
  • Use both actual and forecast alerts, because a forecast warns you sooner.
  • Keep a consistent tagging standard, so spending has an owner.
  • When a real spike happens, follow the runaway cost runbook.