The CloudWatch surprise bill: why monitoring costs more than you think
CloudWatch can quietly become a large line item through custom metrics, high-cardinality dimensions, verbose logs, and frequent API calls. Here is what drives a CloudWatch surprise bill and how to keep observability cost under control.
Quick answer
A CloudWatch surprise bill usually comes from custom metrics, high-cardinality dimensions, verbose log ingestion, and frequent GetMetricData or PutMetricData API calls, none of which show up as an obvious resource you provisioned. Custom metrics bill per metric per month, and a metric is counted per unique dimension combination, so high cardinality multiplies fast. Logs bill per GB ingested plus storage. To control it, drop unnecessary custom metrics and dimensions, set log retention and filter noisy logs, and batch API calls. Observability is worth paying for, but it should be a deliberate cost, not an accident.
CloudWatch is easy to turn on and easy to overspend on, because most of its cost comes from things you did not obviously provision: metrics emitted by code, dimensions that multiply into thousands of unique metrics, logs streamed verbosely, and API calls made on a tight polling loop. The bill can climb quietly until it is a meaningful fraction of your AWS spend. Understanding the drivers is how you avoid the surprise.
Where the cost hides
| Component | How it bills |
|---|---|
| Custom metrics | Per metric per month; one per unique dimension combination |
| Log ingestion | Per GB ingested |
| Log storage | Per GB per month retained |
| API requests | PutMetricData, GetMetricData, and queries, per call or per metric scanned |
| Dashboards and alarms | Per dashboard and per alarm, smaller but additive |
The trap is custom metrics and cardinality. A single metric name with a dimension that takes many values (a user ID, a request ID) becomes one billable custom metric per unique value, so a handful of metric names can explode into thousands of billable metrics. Logs are the other big one: verbose application logging ingests GBs that bill on the way in and again for retention.
Custom metrics and cardinality
Before emitting a custom metric, ask whether you will actually alarm or dashboard on it. Avoid high-cardinality dimensions (never dimension on unbounded values like user or request IDs); aggregate instead. Use metric filters on logs rather than emitting a separate custom metric for everything. Because each unique dimension combination is a separate billable metric, controlling cardinality is the highest-leverage CloudWatch optimization, much like controlling data transfer out is for networking.
Taming logs and API calls
For logs, set retention (logs default to never expire, so storage grows forever), filter or sample noisy debug logs before ingestion, and route high-volume logs that you rarely query to cheaper storage. For API calls, batch PutMetricData rather than one call per data point, and avoid tight GetMetricData polling loops from dashboards or scripts, which scan metrics on every call. These two, log volume and API frequency, are common silent drivers.
Keeping observability a deliberate cost
Observability is worth paying for, the goal is not to gut monitoring but to make its cost intentional. Audit your custom metrics and drop ones nobody uses, cap cardinality, set log retention everywhere, and review the CloudWatch line on your cost dashboard so it does not drift. If you run multi-cloud, compare against third-party observability platforms, which sometimes price high-volume logging more favorably. Treat monitoring like any resource: know what it costs before you scale it, and model it against the resource catalog.
FAQ
Why is my CloudWatch bill so high?
Usually custom metrics with high-cardinality dimensions, verbose log ingestion, and frequent API calls. Custom metrics bill per metric per month, and a metric is counted per unique dimension combination, so dimensioning on unbounded values like user or request IDs explodes into thousands of billable metrics. Logs bill per GB ingested plus retention, and tight GetMetricData polling adds request charges. None of these look like a provisioned resource.
How do CloudWatch custom metrics cost so much?
Because each unique dimension combination is a separate billable custom metric charged per month. A single metric name with a high-cardinality dimension (a user ID, request ID, or session ID) becomes one billable metric per unique value, so a few metric names can multiply into thousands. Controlling cardinality, aggregating instead of dimensioning on unbounded values, is the biggest lever on custom-metric cost.
How do I reduce CloudWatch logs cost?
Set retention (logs default to never expire, so storage grows forever), filter or sample noisy debug logs before ingestion since you pay per GB ingested, and route high-volume logs you rarely query to cheaper storage. Ingestion and retention are the two log charges, so cutting volume on the way in and expiring old logs both help directly.
Do CloudWatch API calls cost money?
Yes. PutMetricData, GetMetricData, and metric queries bill per call or per metric scanned. Emitting one PutMetricData call per data point instead of batching, or polling GetMetricData in a tight loop from dashboards and scripts, can add a surprising amount. Batch metric writes and avoid frequent polling loops to keep API charges down.
How do I avoid a CloudWatch surprise bill?
Audit custom metrics and drop unused ones, cap dimension cardinality (never dimension on unbounded values), set log retention everywhere, filter noisy logs before ingestion, batch API calls, and review the CloudWatch line on your cost dashboard regularly so it does not drift. The goal is deliberate observability cost, not gutting monitoring, so make each metric and log stream an intentional choice.
Does C3X help with CloudWatch cost?
C3X prices infrastructure from your Terraform, so alarms, dashboards, and log groups defined there are visible before deploy. Much CloudWatch cost comes from runtime metric and log volume rather than provisioned resources, so C3X complements runtime observability-cost analysis by pricing the configured pieces upfront and surfacing monitoring resources you are about to add.
What to do next
Surface monitoring resources before they surprise you. C3X reads your Terraform and prices your resources against a live catalog. Start with the quickstart.
Share this post
Try C3X on your own Terraform
Free and open source. No API key required. One command to install, one command to estimate.