Why Is CloudWatch So Expensive?

CloudWatch bills ingestion, storage, queries, and custom metrics as four separate meters. Here is where the money actually goes, and how to cut each one.

Published August 12, 2026 · Last updated August 12, 2026

CloudWatch is the line item people notice once it is already large. Nobody provisions it, nobody sizes it, and there is no console page that says "this is what your observability costs." It just grows — quietly, in proportion to how much your applications talk — until one month it is the third or fourth biggest number on the bill and somebody asks what it is. The usual first guess is that all those logs are piling up in storage. That guess is almost always wrong, and it sends people to fix the cheap half of the problem.

Where does the CloudWatch money actually go?

CloudWatch is not one service with one price. It is four separate meters that happen to share a console, and they behave completely differently:

  • Ingestion — what you pay to put log data in, charged once per GB as events arrive.
  • Storage — what you pay to keep it, charged per GB every month for as long as it exists.
  • Queries — Logs Insights, charged per GB scanned, every time you run a query.
  • Metrics, alarms and dashboards — charged per custom metric, per alarm, per dashboard, per month.

The mental model that gets people into trouble is thinking of CloudWatch as a hard drive, where the bill is a function of how much is sitting there. It is much closer to a toll road: you pay most of the money at the moment data crosses the boundary, and only a small residual for parking it afterwards.

That matters because it flips the fix. If storage were the problem, deleting old logs would solve it. Storage is usually not the problem, so deleting old logs saves you a fraction of what you expected, and the meter that is actually running keeps running.

What does CloudWatch Logs cost per GB?

Here are the numbers that drive most bills, at standard us-east-1 on-demand rates:

What you pay forRate
Log ingestion (Standard log class)$0.50 per GB
Log ingestion (Infrequent Access class)$0.25 per GB
Log storage (archival, compressed)$0.03 per GB-month
Logs Insights queries$0.005 per GB scanned
Custom metrics$0.30 per metric/month
Standard alarms$0.10 per alarm/month

Look at the ratio between the first row and the third. Ingesting a gigabyte costs about sixteen times what storing that gigabyte costs for a month. A service writing 200 GB of logs a month is paying $100 in ingestion every single month, before anyone has stored, queried, or looked at anything.

Now do the storage side of the same account. AWS compresses archived log data, so the "stored bytes" figure it reports grows more slowly than your raw volume — a log group reporting 40 GB stored costs about $1.20 a month. Even a very neglected group holding 400 GB is $12 a month. Real money, but an order of magnitude below the ingestion charge that produced it.

To see the split on your own numbers, put them into the free CloudWatch cost calculator — it itemises ingestion, storage, queries, and custom metrics separately, with no signup.

Why does my log retention cost keep growing?

Because of a default nobody chose. A CloudWatch log group created without an explicit retention setting keeps its data forever. Not 30 days, not a year — Never expire. Every framework, every Lambda function, every ECS task definition that creates a log group on your behalf creates it that way unless you said otherwise, and almost nobody says otherwise.

This is the "charged for something you forgot" shape, and it has a specific signature: the cost is small, it is invisible, and it is a monthly annuity that only ever goes up. A group holding three years of DEBUG output from a service you decommissioned last spring is still billing you, and it will still be billing you in 2030.

Two things follow from this that surprise people:

Setting retention does not refund your ingestion. Retention prunes old events going forward. The $0.50/GB you paid to get them in is spent and gone. Retention fixes the annuity, not the meter.

Setting retention does not immediately shrink your bill either. AWS deletes events past the window over the following days, and storage billing follows the actual deletion. If you want the archival line to drop this week, you are waiting on that background job.

How do I find the log groups doing the damage?

Start with the meter that matters. describe-log-groups tells you what is stored, which is the cheap half — to find what is being ingested, you want the IncomingBytes metric:

# Which groups are ingesting the most over the last 7 days
aws cloudwatch get-metric-statistics \
  --namespace AWS/Logs \
  --metric-name IncomingBytes \
  --dimensions Name=LogGroupName,Value=/aws/lambda/checkout \
  --start-time "$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --period 86400 \
  --statistics Sum \
  --region us-east-1 \
  --output table

Multiply the weekly sum by roughly 4.3 and then by $0.50 per GB, and you have that group's monthly ingestion cost. Run it against your top few dozen groups and the distribution is usually brutal: two or three chatty services account for most of the bill.

For the storage side, and to spot the never-expire groups, one call covers it:

aws logs describe-log-groups \
  --query 'reverse(sort_by(logGroups[?retentionInDays == `null`], &storedBytes))[:20].[logGroupName,storedBytes]' \
  --output table

retentionInDays is absent, not zero, when a group never expires — which is why the filter compares against null rather than 0. In the console the same view is CloudWatch → Log groups, with the Stored bytes column added and sorted descending.

Finally, check Cost Explorer with Group by → Usage type and the service filtered to CloudWatch. You will see the meters split out by name — DataProcessing-Bytes for ingestion, TimedStorage-ByteHrs for storage, CW:MetricMonitorUsage for custom metrics — which tells you in thirty seconds which of the four problems you actually have.

How do I reduce CloudWatch costs?

In rough order of savings per unit of effort:

Set retention on every log group. It is one call per group, it is reversible in seconds, and there is no performance cost. Ninety days suits most application logs.

Log less. This is the only change that touches the dominant meter. DEBUG left on in production, full request and response bodies, health-check chatter, and per-row ORM logging are the usual culprits. Dropping a service's log level is often a 60–80% cut to its ingestion line and takes one config change.

Move noisy-but-rarely-read groups to the Infrequent Access log class. It halves ingestion to $0.25/GB. The trade is that IA groups do not support metric filters, subscription filters, or Live Tail — fine for archival-ish audit trails, wrong for anything you alert on.

Send high-volume vended logs somewhere else. VPC Flow Logs and similar are billed on delivery, and delivering them to S3 costs roughly half what delivering them to CloudWatch Logs does. If nobody runs Logs Insights against your flow logs, they do not belong in CloudWatch.

Audit your custom metrics. At $0.30 per metric per month, 10,000 metrics is $3,000 a month, and it is astonishingly easy to get there by accident: every unique combination of dimension values is a separate billable metric. One well-meaning PutMetricData call that includes a request ID, a customer ID, or a container ID as a dimension turns a single metric into thousands. Publishing via the Embedded Metric Format instead of direct PutMetricData calls also cuts the API request charges.

Query with a time range. Logs Insights bills per GB scanned. A query over "all time" against a never-expire group scans years of data and charges you for every byte of it.

Before you pick one of these, price them: the CloudWatch cost calculator shows what the Infrequent Access class and a shorter retention window are each worth per month at your ingestion volume, so you spend the afternoon on the meter that is actually costing you.

What else should I check while you are in here?

CloudWatch waste travels with its cause. A log group ingesting steadily for a service nobody calls anymore is two findings, not one — see Idle Lambda function for the zero-invocation case, which frequently sits right next to a never-expire log group holding years of output from a function that stopped mattering in 2024. Functions that are invoked are worth a second look too: Lambda bills GB-seconds, so one provisioned at 1,024 MB that never peaks above 180 MB pays roughly five times what it needs to on every invocation (Over-provisioned Lambda memory).

The same "still running, nobody calls it" story explains a lot of ingestion. An access-log firehose from a load balancer with no healthy targets behind it is pure CloudWatch spend on requests that all return 503 — worth reading what an idle load balancer costs if your top log groups are ELB access logs.

More broadly, CloudWatch belongs to the same family as orphaned snapshots and unreleased Elastic IPs: the storage-and-hygiene long tail that AWS's own right-sizing tools barely look at. The broader guide to where AWS money leaks works through that list in order of savings-per-minute. And because a CloudWatch bill usually creeps rather than spikes, it is also a good illustration of what AWS Cost Anomaly Detection will and will not catch — a monitor tuned to spot sudden jumps sails straight past a line item that grows 4% a month for two years.

How do I stop CloudWatch costs creeping back up?

Fixing this once is an afternoon. Keeping it fixed is the hard part, because every new Lambda function, ECS service, and API Gateway stage creates a fresh log group with no retention policy, and the next chatty deploy adds ingestion nobody budgeted for. That is what a scan is for. Parsivex checks each region, flags log groups set to never expire with real data accumulating behind them, and then keeps watching — so the groups created after your cleanup get caught too, instead of quietly starting the same annuity over again.

For what this finding means once it appears in your report, see CloudWatch Logs with no retention policy, or read how scans work before you connect an account.