Skip to content

Understanding CloudWatch Costs: Why Your Bill Jumped, What's Driving It, and How to Cut It

14 minute read
Content level: Advanced
0

Where CloudWatch spend concentrates, how to attribute an increase to a specific line item before touching anything, and the levers for the three areas that drive most bills.

CloudWatch usage tends to grow quietly. You add an EKS cluster, turn on DEBUG while chasing an incident and forget to turn it back off, roll out a service that publishes its own metrics. Each decision is reasonable on its own. Over a few months they push CloudWatch up the list of line items on your bill, and because CloudWatch meters several things independently, it isn't obvious which one grew.

All prices below are US East (N. Virginia) on-demand rates at the time of writing. They're here to show the shape of the pricing, not as a quote. Confirm your Region's rates on the CloudWatch pricing page. AWS also has an official guide, Analyzing, optimizing, and reducing CloudWatch costs.

Where CloudWatch costs come from

CloudWatch is a set of roughly a dozen capabilities, each metered independently. Your invoice shows one number, but the breakdown by capability tells you which capability moved. You can't optimize what you can't name, so getting to that breakdown is the first step.

Here are the dimensions you'll be looking at:

DimensionHow it's billedExample rate (us-east-1)
Log ingestion (Standard)Per GB ingested$0.50/GB
Log ingestion (Infrequent Access)Per GB ingested$0.25/GB (50% of Standard)
Log storage / archivePer GB per month (compressed)$0.03/GB
Logs Insights queriesPer GB of data scanned$0.005/GB
Vended logs (VPC Flow, WAF, CloudFront, etc.)Tiered per GB delivered$0.50/GB down to $0.05/GB at volume
Custom metricsPer metric per month, tiered$0.30 / $0.10 / $0.05 / $0.02
Metric API requestsPer 1,000 requests$0.01/1,000 (PutMetricData, etc.)
Alarms (standard res)Per alarm-metric per month$0.10
Alarms (high res / composite)Per alarm / per unit$0.30 / $0.50
Container Insights (enhanced, EKS)Per 1M observations$0.21/1M
DashboardsPer dashboard per month$3.00 (3 free)

The top contributors for most accounts are log ingestion, custom metrics, and Container Insights, but the order depends on your workload. You need to measure yours before you decide anything.

Small accounts might see no charge at all. CloudWatch has a perpetual free tier of 5 GB of logs across ingestion, storage, and scanning; 10 custom metrics; 10 alarms; and 3 dashboards. This article assumes you're past that point.

Step 1: attribute the cost before changing anything

Don't start deleting log groups because the bill went up. Spend twenty minutes finding what actually grew, then pull one lever. Two tools do that work.

Break the bill down by usage type

Open Cost Explorer, filter Service to CloudWatch, and set Group by to Usage Type. Usage types describe which capability you're paying for. The common ones:

Usage typeWhat it represents
DataProcessing-BytesStandard log ingestion
DataProcessingIA-BytesInfrequent Access log ingestion
TimedStorage-ByteHrsLog storage
VendedLog-BytesVended logs (VPC Flow, WAF, CloudFront, etc.)
CW:MetricMonitorUsageCustom metrics
CW:AlarmMonitorUsageAlarms
CW:RequestsMetric API requests

Regions other than N. Virginia carry a Region prefix, for example USE1-DataProcessing-Bytes in us-east-1 or USW2-DataProcessing-Bytes in us-west-2. Switch the graph to daily granularity around the date the bill changed. The spike will line up with one usage type, and that's your entry point into the rest of the article.

Find the log group responsible for ingestion

If ingestion is the driver, the next question is which log group. CloudWatch Logs publishes an IncomingBytes metric in the AWS/Logs namespace with a LogGroupName dimension, so you can rank log groups directly on a metric graph. Use a metric math expression combining SORT and SEARCH, as shown in the AWS knowledge center article Optimize CloudWatch logs for high-volume data:

SORT(SEARCH('{AWS/Logs,LogGroupName} MetricName="IncomingBytes"', 'Sum', 86400), MAX, 10)

This returns your top 10 log groups by daily ingested bytes, sorted highest first. A few notes so it works on the first try:

  • 86400 is the period in seconds (one day). Keep it at a day for a monthly view, or lower it for finer granularity.
  • 10 is how many log groups to return. Raise it to see more.
  • Set the graph's time range to the period you're investigating.

The top one or two groups will typically account for most of the ingestion, which points you at the source. If you'd rather not use metric math, Determine which log group is causing a bill increase reaches the same result through the console's Graphed metrics tab.

Logs

For many workloads logs are the biggest line item. Log charges split into three independent buckets: ingestion, storage, and Logs Insights queries. You optimize each one separately.

Ingestion is where the money is

Ingestion runs $0.50/GB. Storage runs $0.03/GB per month. Keeping a GB around for a year costs less than ingesting it once, so reducing ingest volume is where the wins are.

  • Check the log level first. A service that got switched to DEBUG or TRACE during an incident and never switched back is the single most common cause of a sudden increase. Start with the top group from the query above.
  • Sample high-volume, low-value logs. Access logs and health-check logs rarely need 100% capture.
  • Don't log what you can measure. If you're emitting a log line per request just to count requests, publish a metric instead.

Retention that matches how long you actually use the data

By default log groups have no expiration, so they accrue storage forever. Set a retention period on each log group that matches how long you actually use the data. 30, 90, or 365 days covers most operational logs. You can do this with the CLI:

aws logs put-retention-policy \
  --log-group-name "YOUR_LOG_GROUP_NAME" \
  --retention-in-days 90

--retention-in-days only accepts specific values: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, and 3653. Any other number returns InvalidParameterException. See PutRetentionPolicy.

Some logs need to be kept for years for audit or compliance reasons. That's a real requirement, and the cheaper home is usually Amazon S3 rather than CloudWatch Logs storage. Export the data, lifecycle it into S3 Glacier or a similar class, and query it with Athena when you need to.

Queries: scan less data

Logs Insights bills $0.005 per GB scanned, and a query scans every byte in the log groups and time range you point at. Narrow the time range instead of defaulting to something wide. Use field indexing on Standard log groups so filters on indexed fields scan less data. If you have saved queries running on a schedule over a broad range, check them.

Standard vs. Infrequent Access log class

The Infrequent Access (IA) log class ingests at half the price of Standard: $0.25 vs. $0.50/GB. Storage and query charges are identical between the two. Ingestion price and feature set are the only differences.

IA fits logs you keep for ad-hoc querying but don't monitor in real time. In exchange for the lower ingestion price it supports a subset of CloudWatch Logs features. These features are not available on IA:

Not available in Infrequent Access
Subscription filters
Metric filters
Live Tail
Embedded Metric Format (EMF)
Container Insights / Lambda Insights log ingestion
Field indexing, Anomaly Detection, Facets
GetLogEvents / FilterLogEvents APIs (use Logs Insights instead)

IA still supports Logs Insights (most commands), cross-account access, KMS encryption, export to S3, data masking, and scheduled queries. Good home for centralized or archival logs, bad home for anything driving alarms, metric filters, or real-time pipelines.

A log group's class is fixed at creation. To move data to IA, create a new IA log group and point the source at it.

Vended logs

Vended logs are logs AWS services publish on your behalf. They sit on their own tiered scale ($0.50/GB down to $0.05/GB at high volume), separate from the standard ingestion bucket. Destinations are CloudWatch Logs, S3, or Data Firehose. For high-volume flow logs you only query occasionally, S3 is usually cheaper.

Some services include a free delivery allowance: each AWS WAF request includes 500 bytes and each CloudFront request 750 bytes of log delivery at no extra charge. Overages bill at vended-log rates. If VendedLog-Bytes is high in your usage breakdown, check whether the volume is justified and whether S3 would work better as a destination.

PutLogEvents volume on EKS with OpenTelemetry

A specific case I see often: an EKS cluster runs an OpenTelemetry (ADOT) or Fluent Bit collector that ships everything to CloudWatch through PutLogEvents, and ingestion climbs with it. The controls live in the collector config:

  • Filter before export. Use the OpenTelemetry filter processor (or Fluent Bit grep/modify) to drop health checks, readiness probes, and noisy namespaces before they leave the cluster.
  • Batch records. The batch processor sends fewer, larger PutLogEvents calls, which cuts API request charges along with overhead.
  • Route by value. Send high-value application logs to a Standard log group and bulk or forensic logs to an IA log group.
  • Account for Container Insights metadata. When Container Insights collects container logs it adds about 700 bytes per line of metadata for filtering. On a high-throughput cluster that adds up.

Metrics

How custom metrics are billed

AWS bills a custom metric per month, and a "metric" is every unique combination of namespace, metric name, and dimension values. Pricing is tiered:

TierPrice per metric / month
First 10,000$0.30
10,001 – 250,000$0.10
250,001 – 1,000,000$0.05
Over 1,000,000$0.02

Dimension cardinality is what surprises people. Publish a latency metric with a RequestId dimension and a million unique request IDs, and you have a million billed metrics. Same for user IDs, full URLs, container IDs, or any dimension whose values are unbounded. High-cardinality dimensions are the number one reason custom-metric charges climb faster than expected.

Use dimensions for attributes you group or filter by (service, Region, an endpoint pattern), not for unique identifiers. Aggregate at the source where you can.

"Am I charged for the metric, or only when I alarm on it?"

Standard AWS service metrics — the basic ones from EC2, S3, Lambda, and the rest — are free, alarm or no alarm.

Custom metrics (anything from PutMetricData, metrics extracted from EMF logs, or metrics from the ADOT CloudWatch EMF exporter) are billed per metric per month regardless of whether an alarm reads them. The metric existing is what's billable, not the alarm.

Detailed Monitoring (1-minute EC2 metrics, high-resolution ECS metrics) bills at custom-metric rates.

Alarms are a separate charge: $0.10 per standard-resolution alarm-metric per month. An alarm on a free AWS metric still costs $0.10 for the alarm.

One subtlety with EMF: it publishes metrics by writing structured log events with PutLogEvents, not PutMetricData (it needs logs:PutLogEvents, not cloudwatch:PutMetricData). So EMF incurs log ingestion charges for the underlying log events, plus custom-metric charges for the metrics CloudWatch extracts from them.

OpenTelemetry metrics pricing

CloudWatch added native OpenTelemetry metrics support, which went GA in June 2026 in all commercial Regions except Middle East (UAE), Middle East (Bahrain), and Israel (Tel Aviv). If you publish metrics through the OTLP endpoint, you're billed on a volume-based model: $0.50/GB ingested, 15 months of storage included, no separate per-metric or API charges. You query the data with PromQL.

For high-cardinality workloads this can cost far less than the classic per-metric model. AWS's own pricing example puts 5,000 unique series at about $54/month under the GB model versus $1,500/month under classic per-metric pricing. If a large share of your bill is custom metrics from an auto-instrumented fleet, evaluate the OTLP path.

Note this only applies to metrics sent over OTLP. The ADOT CloudWatch EMF exporter still produces standard custom metrics billed per metric.

Container Insights

Container Insights generates a lot of metrics and logs, so understand how it's billed before enabling it across every cluster.

There are two pricing models. On original Container Insights, metrics are billed as custom metrics ($0.30/metric and down the tiers) plus standard log ingestion. Metric count scales with cluster components — clusters, nodes, pods, services, namespaces.

On Container Insights with enhanced observability for EKS, you're billed per observation ($0.21 per 1M observations for the first 1B per month) rather than per stored metric. This is more granular and more predictable at scale, though observation counts add up fast. A modest cluster can produce hundreds of millions of observations a month. ECS enhanced observability is billed per metric at $0.07.

Either way, container logs bill separately at standard log ingestion and storage rates, plus about 700 bytes per line of Container Insights metadata.

AWS also launched OTel Container Insights for Amazon EKS in preview in April 2026 (in US East N. Virginia, US West Oregon, Asia Pacific Sydney, Asia Pacific Singapore, and Europe Ireland). It uses a GB-based ingestion model. The published pricing examples list $0.08/GB, though there's no charge while it's in preview. For large clusters this could come in well below the observation model. Because it's still in preview, don't build production cost assumptions around it yet.

A few practical steps:

  • Enable Container Insights on the clusters that need it, not everywhere by default.
  • On the original model, if metric count is the issue, customize the metrics emitted through the ADOT collector instead of accepting the default set.
  • Keep container application logs separate from Insights performance logs, so you can apply retention and log-class decisions to each independently.

Alarms, dashboards, and the smaller line items

These rarely dominate a bill but they're easy wins:

  • Alarms: $0.10 per standard-resolution alarm-metric, $0.30 high-resolution, $0.50 per composite alarm. Anomaly-detection alarms bill three metrics each: the metric plus its upper and lower bounds. Delete alarms pointing at resources you've removed.
  • Dashboards: first 3 are free, then $3 per dashboard per month. Automatic dashboards are free.
  • API requests: GetMetricData, GetInsightRuleReport, and GetMetricWidgetImage are always billed, with no free tier. Polling-heavy integrations and third-party tools that call GetMetricData on a tight interval can rack up real request charges. Pull less often or batch.

Checklist

AreaActionEffortImpact
LogsRank log groups with the IncomingBytes expression; fix verbose log levelsLowHigh
LogsSet a retention period on every log groupLowMedium
LogsMove archival or compliance logs to S3 with lifecycle rulesMediumMedium
LogsUse Infrequent Access for logs you don't monitor in real timeMediumHigh
Logs (EKS)Filter and batch in the OTel / Fluent Bit collectorMediumHigh
MetricsRemove high-cardinality dimensions (request/user/container IDs)MediumHigh
MetricsEvaluate OTel GB-based pricing for large custom-metric fleetsMediumHigh
Container InsightsEnable per cluster; customize emitted metricsMediumMedium
Alarms/DashboardsDelete stale alarms and unused dashboardsLowLow
APIReduce GetMetricData polling frequencyLowMedium

Questions welcome in the comments. If it's an urgent production bill spike and you have an AWS Support plan, open a support case.