Understanding EC2 Monitoring
Monitoring EC2 instances is the continuous process of collecting, analyzing, and acting upon operational data from your virtual servers. At its core, EC2 monitoring revolves around three interconnected components: Metrics (the raw numerical data points), Alarms (automated threshold-based alerting rules), and Dashboards (visual representations that unify metrics and alarms into a single pane of glass).
Amazon CloudWatch serves as the central nervous system for this monitoring ecosystem. It automatically collects metrics from EC2 instances at no additional cost, stores them for 15 months, and provides APIs to retrieve and act upon that data. Understanding how these three pillars interact is essential for maintaining resilient, performant, and cost-efficient infrastructure.
What Are EC2 Metrics?
Metrics are time-series data points that represent a specific measurement about your instance at a given moment. CloudWatch divides these into two categories:
- Default metrics – Collected at 5-minute intervals (standard monitoring) or 1-minute intervals (detailed monitoring, which incurs a small additional charge). These include CPU utilization, network traffic, disk read/write operations, and status checks.
- Custom metrics – Data you push to CloudWatch yourself, such as memory usage, disk space consumption, application-specific metrics, or business KPIs. These can be sent at any interval down to 1 second using high-resolution metrics.
Default EC2 Metrics Explained
Every EC2 instance emits the following metrics to CloudWatch automatically. Understanding each one helps you diagnose issues before they become outages.
- CPUUtilization – Percentage of allocated EC2 compute units currently in use. Spikes may indicate traffic surges, inefficient code, or pending background jobs.
- NetworkIn / NetworkOut – Bytes received and sent by the instance on all network interfaces. Useful for tracking bandwidth costs and identifying traffic anomalies.
- DiskReadOps / DiskWriteOps – Number of read and write operations to instance store volumes and EBS volumes. High values here can surface I/O bottlenecks.
- DiskReadBytes / DiskWriteBytes – Total bytes read from and written to disks. Combine with operation counts to compute average I/O size.
- StatusCheckFailed / StatusCheckFailed_Instance / StatusCheckFailed_System – Binary indicators (0 or 1) that signal whether the instance is reachable and whether the underlying host system is healthy. These are critical for detecting hardware degradation or kernel panics.
- EBSReadOps / EBSWriteOps – Read and write operations specifically on EBS volumes attached to the instance.
- CPUCreditBalance / CPUCreditUsage – Only for burstable instance families (T2, T3, T4g). Tracks how many CPU credits remain and how quickly they are being consumed. Exhaustion causes CPU throttling to baseline performance.
Why Monitoring Matters
Without monitoring, you are flying blind. Here are the concrete reasons why a robust monitoring strategy pays dividends:
- Early fault detection – A failing instance often shows warning signs—gradually rising CPU, increasing disk latency, intermittent status check failures—minutes or hours before total failure. Alarms catch these signals and notify you.
- Auto-scaling intelligence – Auto Scaling groups rely on CloudWatch metrics and alarms to decide when to launch or terminate instances. Well-tuned monitoring directly improves scaling responsiveness and cost efficiency.
- Cost optimization – Persistent low CPU utilization across instances may indicate opportunities to downsize instance types or consolidate workloads. Monitoring data provides the evidence needed for right-sizing decisions.
- Capacity planning – Historical metric data reveals long-term trends in resource consumption, helping you forecast when you'll need additional capacity.
- Compliance and auditing – Many regulatory frameworks require evidence of system monitoring and incident response. CloudWatch dashboards and alarm histories serve as auditable artifacts.
- Performance debugging – Correlating application latency with CPU spikes, network drops, or disk throttling pinpoints the root cause of slowdowns.
Working with CloudWatch Alarms
What CloudWatch Alarms Are
A CloudWatch alarm watches a single metric over a defined time window and triggers an action when the metric breaches a threshold. Alarms can exist in three states: OK (metric is within bounds), ALARM (threshold breached), and INSUFFICIENT_DATA (not enough data points to evaluate). When an alarm transitions to the ALARM state, it can send notifications via SNS, trigger Auto Scaling policies, or execute EC2 actions like rebooting, stopping, or terminating an instance.
Creating an Alarm via AWS CLI
The following example creates an alarm that triggers when average CPU utilization exceeds 80% for two consecutive 5-minute evaluation periods, and sends a notification to an existing SNS topic.
aws cloudwatch put-metric-alarm \
--alarm-name "HighCPUAlarm" \
--alarm-description "Triggers when CPU exceeds 80% for 10 minutes" \
--namespace "AWS/EC2" \
--metric-name "CPUUtilization" \
--dimensions "Name=InstanceId,Value=i-0abc1234def5678" \
--statistic "Average" \
--period 300 \
--evaluation-periods 2 \
--threshold 80.0 \
--comparison-operator "GreaterThanThreshold" \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:MyAlertTopic" \
--treat-missing-data "missing"
Let's break down each parameter:
--namespace– Always"AWS/EC2"for default EC2 metrics, or a custom namespace for your own metrics.--dimensions– Specifies which instance this alarm applies to. You can also use"Name=AutoScalingGroupName,Value=my-asg"to aggregate across an entire ASG.--statistic– The aggregation function:Average,Sum,Minimum,Maximum, orSampleCount. For percentile-based alarming, use extended statistics likep95.--period– The granularity in seconds. Must match how frequently data is being published (300 for standard monitoring, 60 for detailed).--evaluation-periods– How many consecutive periods must breach before the alarm fires. Higher values reduce false positives from transient spikes.--treat-missing-data– Options:missing(ignore gaps),notBreaching(treat gap as OK),breaching(treat gap as ALARM), orignore(maintain current state).
Creating a Composite Alarm
Composite alarms combine multiple metric alarms with logical AND/OR conditions. They reduce alert noise by requiring multiple conditions to be true simultaneously. For example, trigger only when both CPU is high AND status checks are failing.
aws cloudwatch put-composite-alarm \
--alarm-name "CriticalInstanceFailure" \
--alarm-description "CPU high AND status check failing" \
--alarm-rule "ALARM(HighCPUAlarm) AND ALARM(StatusCheckAlarm)" \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:CriticalAlertTopic" \
--treat-missing-data "missing"
The --alarm-rule parameter accepts a mini DSL. You can nest conditions: "(ALARM(A) OR ALARM(B)) AND OK(C)" is valid. This flexibility lets you model complex failure scenarios without creating dozens of overlapping alarms.
EC2 Action Alarms: Automatic Recovery
One of the most powerful alarm features is the ability to automatically recover a failing instance. When a StatusCheckFailed_System alarm fires, CloudWatch can initiate an instance recovery—stopping and restarting the instance on new underlying hardware while preserving its instance ID, private IP addresses, and Elastic IPs.
aws cloudwatch put-metric-alarm \
--alarm-name "AutoRecoverInstance" \
--namespace "AWS/EC2" \
--metric-name "StatusCheckFailed_System" \
--dimensions "Name=InstanceId,Value=i-0abc1234def5678" \
--statistic "Minimum" \
--period 60 \
--evaluation-periods 2 \
--threshold 1.0 \
--comparison-operator "GreaterThanThreshold" \
--alarm-actions "arn:aws:automate:us-east-1:ec2:recover" \
--treat-missing-data "missing"
The magic is in --alarm-actions "arn:aws:automate:us-east-1:ec2:recover". This special ARN instructs CloudWatch to execute the recovery workflow. Important prerequisites: the instance must use EBS-backed storage (not instance store), have a public or Elastic IP if that's required for connectivity, and be in a VPC where the subnet has sufficient capacity for a replacement instance.
Publishing Custom Metrics
Why Custom Metrics Are Essential
Default EC2 metrics give you host-level visibility, but they reveal nothing about what's happening inside the operating system or application. Memory pressure, disk space exhaustion, garbage collection pauses, request queue depths—these require custom instrumentation. CloudWatch allows you to publish any metric you want, and the process is straightforward.
Publishing a Custom Metric via AWS CLI
This example pushes a memory utilization metric to CloudWatch. You would typically run this from a cron job or daemon on the instance itself.
# Get memory usage percentage on a Linux instance
MEMORY_PERCENT=$(free | grep Mem | awk '{print ($3/$2) * 100.0}' | cut -d. -f1)
# Publish to CloudWatch
aws cloudwatch put-metric-data \
--namespace "Custom/EC2" \
--metric-name "MemoryUtilization" \
--dimensions "Name=InstanceId,Value=i-0abc1234def5678" \
--value ${MEMORY_PERCENT} \
--unit "Percent" \
--timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
Publishing High-Resolution Metrics with the SDK
For sub-minute granularity, use the --storage-resolution parameter or the equivalent SDK call. Here's an example using Python and boto3 to publish metrics every 10 seconds—useful for real-time latency monitoring.
import boto3
import time
import random
from datetime import datetime
cloudwatch = boto3.client('cloudwatch')
def publish_latency_metric(instance_id, value):
cloudwatch.put_metric_data(
Namespace='Custom/AppPerformance',
MetricData=[
{
'MetricName': 'ApiResponseLatency',
'Dimensions': [
{'Name': 'InstanceId', 'Value': instance_id},
{'Name': 'Endpoint', 'Value': '/api/checkout'}
],
'Value': value,
'Unit': 'Milliseconds',
'Timestamp': datetime.utcnow(),
'StorageResolution': 60 # 60 = 1-minute resolution, 1 = 1-second (high-res)
}
]
)
# Simulate publishing latency every 10 seconds for a demo
instance_id = 'i-0abc1234def5678'
while True:
latency = random.gauss(mu=45, sigma=10) # Simulated API latency ~45ms
publish_latency_metric(instance_id, latency)
time.sleep(10)
High-resolution metrics (StorageResolution: 1) allow you to set alarms with periods as low as 1 second, though they incur higher costs. Standard resolution metrics are free for the first 1 million API requests per month.
Using the CloudWatch Agent for System Metrics
Rather than scripting memory and disk metrics yourself, the unified CloudWatch Agent can collect a broad set of system-level metrics and logs from Linux and Windows instances. Install it and configure it with a JSON document.
# Install the CloudWatch agent on Amazon Linux 2
sudo yum install -y amazon-cloudwatch-agent
# Create the configuration file
sudo cat > /opt/aws/amazon-cloudwatch-agent/bin/config.json << 'EOF'
{
"metrics": {
"append_dimensions": {
"InstanceId": "${aws:InstanceId}",
"InstanceType": "${aws:InstanceType}"
},
"metrics_collected": {
"mem": {
"measurement": ["mem_used_percent", "mem_available", "swap_used_percent"],
"metrics_collection_interval": 60
},
"disk": {
"measurement": ["used_percent", "used", "total"],
"resources": ["/", "/data"],
"metrics_collection_interval": 60
},
"netstat": {
"measurement": ["tcp_established", "tcp_time_wait"],
"metrics_collection_interval": 60
}
}
}
}
EOF
# Start the agent with the configuration
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
-a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json
The agent pushes these metrics to the CWAgent namespace by default. You can then build alarms and dashboards on memory, disk, swap, and network connection states—all without writing custom collection scripts.
Building CloudWatch Dashboards
What Dashboards Provide
CloudWatch dashboards are customizable, real-time visual canvases that display metric graphs, alarm status widgets, and text annotations. They persist across sessions, can be shared with direct URLs, and are ideal for NOC screens, on-call dashboards, and post-incident review sessions. A single dashboard can aggregate metrics from multiple regions and AWS services.
Creating a Dashboard via AWS CLI
Dashboards are defined as JSON strings containing widget definitions. The following creates a dashboard with CPU, network, and alarm status widgets for a specific instance.
aws cloudwatch put-dashboard \
--dashboard-name "Production-EC2-Overview" \
--dashboard-body '{
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"metrics": [
[ "AWS/EC2", "CPUUtilization", { "InstanceId": "i-0abc1234def5678" } ],
[ ".", "NetworkIn", ".", "." ],
[ ".", "NetworkOut", ".", "." ]
],
"period": 300,
"stat": "Average",
"region": "us-east-1",
"title": "Instance CPU and Network",
"view": "timeSeries",
"stacked": false
}
},
{
"type": "alarm",
"x": 12,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"alarms": [
"arn:aws:cloudwatch:us-east-1:123456789012:alarm:HighCPUAlarm",
"arn:aws:cloudwatch:us-east-1:123456789012:alarm:AutoRecoverInstance"
],
"title": "Active Alarms"
}
},
{
"type": "text",
"x": 0,
"y": 6,
"width": 24,
"height": 2,
"properties": {
"markdown": "## Production Instance Health\n\nLast updated: {{datetime}} | Instance: i-0abc1234def5678 | Auto-recovery enabled"
}
}
]
}'
Dashboard Widget Types
CloudWatch supports several widget types, each serving a distinct purpose:
- metric – Line charts, stacked area charts, or single-value numbers. You can display up to 15 metrics in one widget.
- alarm – Shows the current state of selected alarms with color-coded status indicators (green for OK, red for ALARM, gray for insufficient data).
- text – Free-form Markdown content. Use this for documentation, links to runbooks, or live template variables like
{{region}}and{{datetime}}. - log – Queries and displays CloudWatch Logs Insights results directly on the dashboard.
- explorer – Interactive metric explorer widget for ad-hoc data exploration.
Programmatic Dashboard Generation with Python
For large-scale deployments, manually crafting JSON is error-prone. Use boto3 to generate dashboards dynamically based on your infrastructure inventory.
import boto3
cloudwatch = boto3.client('cloudwatch')
def build_dashboard_for_instances(instance_ids, dashboard_name):
widgets = []
y_position = 0
for idx, instance_id in enumerate(instance_ids):
# CPU widget for each instance
widgets.append({
"type": "metric",
"x": 0,
"y": y_position,
"width": 8,
"height": 4,
"properties": {
"metrics": [
["AWS/EC2", "CPUUtilization", {"InstanceId": instance_id}]
],
"period": 300,
"stat": "Average",
"region": "us-east-1",
"title": f"CPU - {instance_id}"
}
})
# Network widget for each instance
widgets.append({
"type": "metric",
"x": 8,
"y": y_position,
"width": 8,
"height": 4,
"properties": {
"metrics": [
["AWS/EC2", "NetworkIn", {"InstanceId": instance_id}],
[".", "NetworkOut", ".", "."]
],
"period": 300,
"stat": "Sum",
"region": "us-east-1",
"title": f"Network - {instance_id}"
}
})
# Alarm status widget
widgets.append({
"type": "alarm",
"x": 16,
"y": y_position,
"width": 8,
"height": 4,
"properties": {
"alarms": [
f"arn:aws:cloudwatch:us-east-1:123456789012:alarm:HighCPU-{instance_id}"
],
"title": f"Alarms - {instance_id}"
}
})
y_position += 4
dashboard_body = {"widgets": widgets}
cloudwatch.put_dashboard(
DashboardName=dashboard_name,
DashboardBody=str(dashboard_body).replace("'", '"') # Ensure valid JSON
)
print(f"Dashboard '{dashboard_name}' created with {len(instance_ids)} instance panels.")
# Example usage
instance_list = ['i-0abc1234def5678', 'i-1def2345abc6789', 'i-2ghi3456def7890']
build_dashboard_for_instances(instance_list, "Fleet-Health-Overview")
This approach scales to hundreds of instances. You can extend it to pull instance IDs from EC2 describe-instances, group them by Auto Scaling group tags, and create per-service dashboard slices automatically.
Using Metric Math on Dashboards
CloudWatch Metric Math lets you perform calculations on metrics and display the results as dashboard widgets. This is incredibly useful for computing aggregate views, ratios, and anomaly scores without publishing additional custom metrics.
{
"type": "metric",
"x": 0,
"y": 0,
"width": 12,
"height": 6,
"properties": {
"metrics": [
{
"label": "Total Network Traffic (GB)",
"expression": "(m1 + m2) / 1073741824",
"id": "e1",
"color": "#2ca02c"
},
{
"label": "Network In",
"id": "m1",
"metricStat": {
"metric": {
"namespace": "AWS/EC2",
"metricName": "NetworkIn",
"dimensions": { "InstanceId": "i-0abc1234def5678" }
},
"period": 300,
"stat": "Sum"
},
"visible": false
},
{
"label": "Network Out",
"id": "m2",
"metricStat": {
"metric": {
"namespace": "AWS/EC2",
"metricName": "NetworkOut",
"dimensions": { "InstanceId": "i-0abc1234def5678" }
},
"period": 300,
"stat": "Sum"
},
"visible": false
}
],
"view": "timeSeries",
"title": "Combined Network Traffic (GB)",
"region": "us-east-1"
}
}
The expression field uses a simple query language. Common functions include SUM(METRICS()) for aggregating across multiple instances, METRICS("AWS/EC2", "CPUUtilization") to dynamically pull all matching metrics, arithmetic operations, and statistical functions like STDDEV and ANOMALY_DETECTION_BAND.
Best Practices for EC2 Monitoring
1. Enable Detailed Monitoring Selectively
Detailed monitoring (1-minute intervals) costs approximately $0.015 per instance-hour on top of the instance cost. Enable it for production instances where rapid detection matters—especially those behind Auto Scaling groups that need responsive scaling. For development and staging environments, standard 5-minute monitoring is usually sufficient.
2. Use Composite Alarms to Reduce Noise
A single metric spike rarely indicates a real problem. Design composite alarms that require multiple correlated signals. For example, trigger only when CPU is sustained above 90% AND application health checks are failing, rather than alerting on CPU alone. This dramatically reduces false-positive pages at 3 AM.
3. Implement the CloudWatch Agent Everywhere
Default EC2 metrics miss memory, disk space, and swap utilization. Deploy the CloudWatch Agent via your AMI baking process or user-data scripts so every instance reports these critical system metrics from the moment it launches. Store the agent configuration in Parameter Store for centralized management.
4. Set Up Status Check Alarms with Recovery Actions
This is perhaps the highest-leverage single action you can take. A StatusCheckFailed_System alarm paired with the EC2 recover action automatically replaces failed hardware without human intervention. The instance retains its configuration, EBS volumes, and network identity. Test this in staging first to ensure your applications survive the brief restart.
5. Build Tiered Dashboards
Create at least three dashboard layers:
- Executive/Overview – High-level service health, aggregate metrics, and top-level alarm status. One screen tells the whole story.
- Service/Application – Per-service dashboards showing instance-level metrics, custom application metrics, and relevant alarm history for the teams that own each service.
- Deep-Dive/Troubleshooting – Granular per-instance views with network, disk, memory, and process-level metrics. Include CloudWatch Logs Insights queries for quick log correlation.
6. Use Metric Math for Derived Insights
Instead of publishing a separate "requests per second per CPU percent" custom metric, compute it on the dashboard with Metric Math. This keeps your metric publishing lean and your dashboards flexible. You can iterate on derived metrics without redeploying metric collection code.
7. Tag Metrics with Meaningful Dimensions
When publishing custom metrics, include dimensions beyond just InstanceId. Add Environment, Service, Version, or Tenant dimensions. This enables cross-cutting views—you can graph latency for all instances of a particular service version regardless of which instance they're running on.
8. Monitor CPUCreditBalance for Burstable Instances
If you run T-family instances, credit exhaustion silently throttles CPU to baseline performance, which can cause mysterious slowdowns. Set a CloudWatch alarm on CPUCreditBalance when it drops below a critical threshold (e.g., 10 credits) and treat it as a warning that you may need to switch to an unlimited credit mode or a different instance family.
9. Automate Dashboard Creation
Manually maintaining dashboards as instances come and go is unsustainable. Use the SDK examples shown earlier to regenerate dashboards during deployments, or adopt Infrastructure as Code tools like CloudFormation or Terraform to define dashboards alongside your instance definitions. A dashboard that reflects only last week's topology is worse than no dashboard at all.
10. Set Alarms on Anomaly Detection Models
CloudWatch can automatically learn a metric's expected range using statistical models. Enable anomaly detection on critical metrics, then create alarms that trigger when values fall outside the expected band. This catches subtle issues that fixed-threshold alarms miss—like a gradual memory leak that never spikes above 90% but steadily climbs over days.
Conclusion
Effective EC2 monitoring is not merely about collecting data—it's about converting raw metrics into actionable intelligence. CloudWatch provides the foundational tools: automatic host metrics at no extra cost, a flexible alarm system capable of triggering recoveries and scaling actions, and dashboards that transform time-series data into visual narratives. By layering on custom metrics via the CloudWatch Agent or SDK-based instrumentation, you gain full-stack visibility from the hypervisor to the application. The practices outlined here—tiered dashboards, composite alarms, automated recovery, anomaly detection, and infrastructure-as-code dashboard generation—form a mature monitoring posture that reduces mean time to detection, minimizes alert fatigue, and ultimately keeps your EC2 fleet healthy and performant. Start with the fundamentals, iterate on your alarms as you learn your workload patterns, and treat your dashboards as living artifacts that evolve alongside your architecture.