CubeAPM
CubeAPM CubeAPM

How to Reduce AWS CloudWatch Costs: Step-by-Step Guide 2026

How to Reduce AWS CloudWatch Costs: Step-by-Step Guide 2026

Table of Contents

AWS CloudWatch costs can spiral fast. A 50-node cluster generating 500 GB of logs daily at standard CloudWatch rates ($0.50/GB ingestion + $0.03/GB storage) costs $7,950/month in log ingestion alone before you add custom metrics, alarms, or dashboards. Beyond advertised rates, CloudWatch bills separately for metric storage, API calls, alarm evaluations, dashboard usage, and data scanned by Logs Insights. According to the 2025 State of Cloud Cost Intelligence Report, observability costs account for 8–12% of total AWS spend for engineering-heavy organizations, with CloudWatch representing the largest single line item.

This guide walks through eight concrete steps to reduce CloudWatch costs without losing visibility. Each step includes commands, configuration examples, and cost impact estimates based on real usage patterns.

Prerequisites

Before starting, ensure you have:

  • AWS CLI installed and configured with administrator or billing read access
  • Access to AWS Cost Explorer and your CloudWatch console
  • Basic familiarity with CloudWatch Logs, Metrics, Alarms, and Dashboards
  • Permission to modify log group retention policies and delete unused resources
  • A spreadsheet or cost tracking tool to document current usage and measure improvements

Step 1: Audit Current CloudWatch Usage and Identify Cost Drivers

Start by understanding where your CloudWatch costs come from. AWS Cost Explorer breaks down CloudWatch charges by usage type, making it easy to identify the largest cost drivers.

Open AWS Cost Explorer and filter by service: CloudWatch. Group results by Usage Type to see which CloudWatch features generate the most cost. Common high-cost usage types include:

  • DataProcessing-Bytes — log ingestion charges
  • TimedStorage-ByteHrs — log storage charges
  • DataScanned-Bytes — Logs Insights query charges
  • GMD-Metrics — custom metric storage via bulk API requests
  • AlarmMonitorUsage — standard alarm evaluations
  • DashboardsUsageHour — dashboard usage beyond free tier

For deeper analysis, enable AWS Cost and Usage Reports (CUR) and query them with Athena. This surfaces resource-level cost attribution that Cost Explorer does not show. To enable CUR, follow the AWS CUR setup guide. Once enabled, you can run Athena queries to see which specific log groups, metric namespaces, or alarms drive the highest costs.

Example Athena query to identify top CloudWatch cost drivers by resource:

SELECT 
  line_item_resource_id,
  line_item_usage_type,
  SUM(line_item_unblended_cost) AS total_cost
FROM costandusagereport
WHERE 
  line_item_product_code = 'AmazonCloudWatch'
  AND year = '2026'
  AND month = '04'
GROUP BY line_item_resource_id, line_item_usage_type
ORDER BY total_cost DESC
LIMIT 50;

This query returns the 50 most expensive CloudWatch resources in April 2026. Focus your optimization efforts on the top 10 resources, which typically account for 60–80% of total CloudWatch spend.

Document your findings in a spreadsheet with columns for resource ID, usage type, monthly cost, and optimization action. This baseline lets you measure cost reductions after implementing each step.

Step 2: Reduce Log Ingestion with Subscription Filters and Sampling

CloudWatch log ingestion is billed at $0.50/GB. A microservices application logging at INFO level across 100 services can easily generate 1 TB/day, costing $15,000/month in ingestion alone. The fastest way to cut log costs is to reduce the volume of logs sent to CloudWatch.

Use CloudWatch subscription filters to route logs selectively. Instead of sending all logs to CloudWatch, filter at the source and send only ERROR and WARN level logs to CloudWatch while routing verbose INFO and DEBUG logs to S3 for archival at $0.023/GB.

Example: Configure a Lambda function with a subscription filter that forwards only ERROR logs to CloudWatch:

aws logs put-subscription-filter \
  --log-group-name /aws/lambda/my-function \
  --filter-name error-only-filter \
  --filter-pattern "[level=ERROR]" \
  --destination-arn arn:aws:lambda:us-east-1:123456789012:function:error-log-processor

For high-volume services, implement sampling at the application level before logs reach CloudWatch. Instead of logging every request, log 1 in 100 successful requests and all errors. This reduces ingestion by 99% for steady-state traffic while preserving full error visibility.

Example sampling configuration in Python using structlog:

import structlog
import random
def sample_processor(logger, method_name, event_dict):
    if event_dict.get('level') == 'info' and random.random() > 0.01:
        raise structlog.DropEvent
    return event_dict
structlog.configure(processors=[sample_processor, structlog.stdlib.ProcessorFormatter.wrap_for_formatter])

Expected cost impact: Reducing log volume by 70% on a 500 GB/day workload saves $5,565/month in ingestion costs alone.

Step 3: Optimize Log Retention Policies to Minimize Storage Costs

CloudWatch charges $0.03/GB/month for log storage. The default retention setting is “Never Expire,” meaning logs accumulate indefinitely. A service generating 10 GB of logs daily with no retention policy costs $109.50/month in storage after one year.

Set retention policies based on compliance requirements and operational need. Most applications do not need logs older than 30 days in CloudWatch. For compliance, archive older logs to S3 or S3 Glacier at a fraction of the cost.

List all log groups with no retention policy set:

aws logs describe-log-groups --query 'logGroups[?!retentionInDays].logGroupName' --output table

Set a 30-day retention policy on all log groups that do not require longer retention:

for log_group in $(aws logs describe-log-groups --query 'logGroups[?!retentionInDays].logGroupName' --output text); do
  aws logs put-retention-policy --log-group-name $log_group --retention-in-days 30
done

For log groups that require longer retention for compliance, export older logs to S3 using a scheduled Lambda function or CloudWatch Logs export task. S3 Standard storage costs $0.023/GB/month, reducing storage costs by 87% compared to CloudWatch.

Example export task to move logs older than 30 days to S3:

aws logs create-export-task \
  --log-group-name /aws/lambda/my-function \
  --from $(date -d '60 days ago' +%s)000 \
  --to $(date -d '30 days ago' +%s)000 \
  --destination s3-bucket-name \
  --destination-prefix cloudwatch-logs-archive/

Expected cost impact: Setting 30-day retention on 1 TB of logs stored indefinitely saves $328/month after the first 30 days.

Step 4: Batch Custom Metrics and Use StatisticSets to Reduce Metric API Costs

CloudWatch charges $0.30 per 1,000 PutMetricData API requests. Applications that send individual metrics per request rack up API costs quickly. An application publishing 100 custom metrics every 60 seconds makes 144,000 API calls per day, costing $43.20/day or $1,296/month in API charges alone.

Batch multiple metrics into a single PutMetricData request. CloudWatch supports up to 1,000 metrics per request. Batching 100 metrics per request reduces API calls by 99%.

Example batched PutMetricData call:

aws cloudwatch put-metric-data --namespace MyApp/Performance --metric-data \
  file://metrics-batch.json

Where metrics-batch.json contains:

[
  {
    "MetricName": "RequestLatency",
    "Value": 245,
    "Unit": "Milliseconds",
    "Timestamp": "2026-04-15T12:00:00Z"
  },
  {
    "MetricName": "ErrorCount",
    "Value": 3,
    "Unit": "Count",
    "Timestamp": "2026-04-15T12:00:00Z"
  }
]

For high-frequency metrics, use StatisticSets instead of individual values. A StatisticSet aggregates multiple observations into a single API call by sending min, max, sum, and sample count. This is especially effective for metrics like request latency or queue depth that are measured many times per minute.

Example StatisticSet API call:

aws cloudwatch put-metric-data --namespace MyApp/Performance \
  --metric-name RequestLatency \
  --statistic-values Sum=12450,Minimum=120,Maximum=580,SampleCount=50 \
  --timestamp 2026-04-15T12:00:00Z

Expected cost impact: Batching 100 metrics into single requests reduces API costs by 99%. A workload making 144,000 API calls/day drops to 1,440 calls/day, reducing costs from $1,296/month to $13/month.

Step 5: Remove Unused Alarms and Dashboards

CloudWatch charges $0.10/month per standard alarm and $3/month per dashboard beyond the free tier (10 alarms and 3 dashboards). Many AWS accounts accumulate alarms for resources that no longer exist or were created during testing and never deleted. Datadog’s State of Cloud Monitoring 2025 found that 34% of CloudWatch alarms in surveyed accounts were inactive or monitoring deleted resources.

List all CloudWatch alarms and identify those in INSUFFICIENT_DATA state for more than 7 days — these likely monitor deleted resources:

aws cloudwatch describe-alarms --state-value INSUFFICIENT_DATA \
  --query 'MetricAlarms[?StateUpdatedTimestamp<`2026-04-08`].AlarmName' --output table

Delete alarms that monitor non-existent resources:

aws cloudwatch delete-alarms --alarm-names alarm-name-1 alarm-name-2

Review all dashboards and delete those not accessed in the past 90 days. CloudWatch does not surface dashboard access logs, so coordinate with your team to identify unused dashboards before deletion.

List all dashboards:

aws cloudwatch list-dashboards --output table

Delete unused dashboards:

aws cloudwatch delete-dashboards --dashboard-names dashboard-name-1 dashboard-name-2

Expected cost impact: Deleting 50 unused alarms saves $5/month. Deleting 5 unused dashboards saves $15/month. While individually small, these add up across large AWS accounts with hundreds of alarms and dozens of dashboards.

Step 6: Optimize Logs Insights Queries to Reduce Data Scanned Charges

CloudWatch Logs Insights charges $0.005 per GB of data scanned. A single query scanning 1 TB of logs costs $5. Teams running frequent queries for debugging or dashboards can rack up hundreds of dollars in scan charges monthly.

Reduce scan volume by narrowing the time range in every query. Scanning 7 days of logs instead of 30 days reduces scanned data by 75%. Use the fields command to return only the columns you need instead of scanning entire log lines.

Example inefficient query that scans all log data:

fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc

Optimized query that scans only the @message and @timestamp fields and limits the time range:

fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 100

For frequently run queries, use CloudWatch Logs Insights query result caching or export results to S3 and query with Athena. Athena charges $5 per TB scanned but supports Parquet format, reducing scanned data by 80% compared to raw JSON logs.

Expected cost impact: Reducing query time range from 30 days to 7 days on a 500 GB log group cuts scan charges from $2.50 per query to $0.625 per query. Running 100 queries per month saves $187.50/month.

Step 7: Use Metric Filters Sparingly and Consolidate When Possible

Metric filters extract custom metrics from log data, but they add cost in two ways: they increase log ingestion processing time and generate additional custom metrics billed at $0.30 per metric per month. Each metric filter creates one custom metric. An account with 200 log-derived metrics pays $60/month just for metric storage.

Audit existing metric filters and delete those no longer used:

aws logs describe-metric-filters --log-group-name /aws/lambda/my-function

Delete unused metric filters:

aws logs delete-metric-filter --log-group-name /aws/lambda/my-function --filter-name unused-filter

Consolidate multiple metric filters into a single filter that publishes multiple dimensions instead of separate metrics. For example, instead of creating one metric filter per HTTP status code (200, 404, 500), create a single filter that publishes an HTTPStatusCode metric with a dimension for each status code.

Example consolidated metric filter pattern:

{
  "filterPattern": "[time, request_id, status_code, latency]",
  "metricTransformations": [
    {
      "metricName": "HTTPRequests",
      "metricNamespace": "MyApp/API",
      "metricValue": "1",
      "defaultValue": 0,
      "dimensions": {
        "StatusCode": "$status_code"
      }
    }
  ]
}

Expected cost impact: Consolidating 50 metric filters into 10 dimensional metrics reduces custom metric costs from $15/month to $3/month.

Step 8: Consider CloudWatch Alternatives for High-Volume Workloads

If you have implemented every optimization above and CloudWatch costs remain prohibitively high, evaluate purpose-built log management and APM platforms designed for cost efficiency. CloudWatch is tightly integrated with AWS services and works well at moderate scale, but its pricing model makes it expensive for high-volume telemetry workloads.

For example, a 100-node Kubernetes cluster generating 2 TB of logs and 500,000 custom metrics per month costs approximately:

  • Logs: 2,000 GB × $0.50/GB ingestion + 2,000 GB × $0.03/GB storage = $1,060/month
  • Metrics: 500,000 metrics × $0.30/1,000 = $150/month
  • Alarms: 200 alarms × $0.10 = $20/month
  • Total: $1,230/month

This estimate models a production-ready Kubernetes logging and metrics setup with 30-day log retention and standard alarm coverage. Costs increase significantly with longer retention, frequent Logs Insights queries, or high-resolution metrics.

Alternatives like CubeAPM, Grafana Loki, or Elastic offer ingestion-based pricing with unlimited retention at a fraction of CloudWatch’s cost. CubeAPM runs on-premises or in your VPC, keeping telemetry data inside your infrastructure and eliminating AWS data transfer costs entirely. At $0.15/GB for logs, traces, and metrics combined, the same 2 TB workload costs $300/month with CubeAPM.

CubeAPM integrates with CloudWatch via OpenTelemetry, so you can migrate incrementally without a hard cutover. For teams with strict data residency requirements or those looking to consolidate APM, logs, and infrastructure monitoring into a single platform, platforms like CubeAPM and other modern observability tools provide a cost-effective alternative.

Comparing costs for the same 100-node Kubernetes workload:

PlatformMonthly CostDeploymentRetention
AWS CloudWatch$1,230SaaS30 days standard
CubeAPM$300Self-hostedUnlimited
Grafana Cloud$450SaaS30 days
Elastic Cloud$850SaaS30 days

Expected cost impact: Migrating a high-volume workload from CloudWatch to a cost-optimized alternative like CubeAPM can reduce observability costs by 60–80% while improving query performance and retention.

Troubleshooting Common Issues

Issue: Cost Explorer shows high `DataProcessing-Bytes` charges, but I cannot identify which log groups are responsible.

Enable AWS Cost and Usage Reports and query with Athena to surface resource-level cost attribution. The query in Step 1 returns the exact log group ARNs responsible for the highest costs. Alternatively, use the CloudWatch Logs console to sort log groups by ingested bytes over the past 30 days.

Issue: Log retention policy changes do not immediately reduce storage costs.

CloudWatch only deletes logs after the retention period expires. If you change retention from “Never Expire” to 30 days today, logs older than 30 days are deleted starting tomorrow, but you continue paying for existing stored logs until they age out. Storage cost reductions appear gradually over the next 30 days.

Issue: Metric filter consolidation breaks existing dashboards and alarms.

Before consolidating metric filters, audit all dashboards and alarms that reference the old metrics. Update dashboard widgets and alarm metric queries to use the new dimensional metrics. Test all alarms in a non-production account before applying changes to production.

Issue: After batching PutMetricData requests, metrics no longer appear in CloudWatch.

Verify that the Timestamp field in each metric is within the past two weeks and not in the future. CloudWatch rejects metrics with timestamps outside this range. Check CloudWatch API error responses for InvalidParameterValue errors indicating timestamp issues.

Issue: Logs Insights queries time out after optimization.

If queries time out after narrowing the time range, the query may be scanning a high-cardinality field. Use the fields command to return only the specific fields you need instead of @message, which scans the entire log line. For queries that must scan large volumes, consider exporting logs to S3 and querying with Athena.

AWS CloudWatch costs escalate fast without active management. The eight steps above address the most common cost drivers: log ingestion, retention, custom metrics, alarms, and query usage. Implementing even a subset of these steps typically reduces CloudWatch costs by 40–60% within the first month.

For teams generating more than 1 TB of logs or 500,000 custom metrics per month, the operational and financial overhead of managing CloudWatch at scale often justifies evaluating purpose-built observability platforms. Modern alternatives designed for high-volume workloads offer better query performance, unlimited retention, and simpler pricing while maintaining full compatibility with AWS services.

Disclaimer: The information in this article reflects the latest details available at the time of publication and may change as technologies and products evolve. Features, pricing, and plan limits can change over time. Always verify the latest information directly with the vendor before making purchasing or deployment decisions.

Frequently Asked Questions

How much can I save by optimizing CloudWatch costs?

Most teams reduce CloudWatch costs by 40–60% in the first month by setting log retention policies, removing unused alarms, and batching custom metrics. High-volume workloads can save 60–80% by migrating to cost-optimized observability platforms.

What is the fastest way to reduce CloudWatch log costs?

Set 30-day retention policies on all log groups and implement subscription filters to route verbose INFO and DEBUG logs to S3 instead of CloudWatch. These two actions typically reduce log costs by 50–70% within 30 days.

How do I identify which log groups cost the most?

Use AWS Cost Explorer filtered by CloudWatch and grouped by Usage Type, or enable AWS Cost and Usage Reports and query with Athena to surface resource-level cost attribution. The Athena query in Step 1 returns the exact log groups driving the highest costs.

Can I reduce CloudWatch costs without losing visibility?

Yes. Most cost reductions come from eliminating waste like unused alarms, overly verbose logging, and indefinite log retention rather than reducing useful telemetry. Sampling and selective filtering preserve error visibility while cutting routine log volume by 70–90%.

What is the difference between CloudWatch and dedicated observability platforms?

CloudWatch is AWS-native and tightly integrated with AWS services but becomes expensive at high data volumes. Dedicated platforms like CubeAPM, Grafana, and Elastic offer ingestion-based pricing, unlimited retention, and better query performance for high-volume workloads, often at 60–80% lower cost.

How do I migrate from CloudWatch to an alternative platform?

Most modern observability platforms support OpenTelemetry and CloudWatch-compatible agents, allowing incremental migration without downtime. Start by routing a subset of logs or metrics to the new platform, validate dashboards and alerts, then gradually shift the remaining workloads.

Does CloudWatch charge for data transfer?

CloudWatch itself does not charge for data transfer, but sending logs or metrics to CloudWatch from resources in different AWS regions or outside AWS incurs standard AWS data transfer charges. For resources outside AWS, this can add $0.09/GB in transfer costs on top of CloudWatch ingestion fees.

×
×