Understanding VPC Monitoring
Monitoring your Virtual Private Cloud (VPC) is the practice of collecting, analyzing, and acting on operational data from your network infrastructure. In AWS, VPC monitoring encompasses network traffic analysis through VPC Flow Logs, performance metrics from NAT Gateways and VPN connections, subnet-level IP utilization, and custom metrics you define. This data flows into Amazon CloudWatch, where you can visualize trends, trigger automated responses, and build dashboards that give you a unified view of your network's health.
What VPC Monitoring Actually Is
At its core, VPC monitoring is about gaining visibility into three distinct layers of your network:
- Traffic observability – VPC Flow Logs capture metadata about every IP packet traversing your network interfaces, giving you source/destination IPs, ports, protocols, and accept/reject dispositions for each flow
- Resource performance – Managed services like NAT Gateways and VPN tunnels emit CloudWatch metrics for throughput, connection counts, and error rates
- Capacity planning – Subnet-level metrics track IP address exhaustion so you never run out of assignable addresses mid-deployment
These signals are ingested by CloudWatch as time-series data points, where you can query them, build alarms with threshold-based triggers, and assemble custom dashboards for real-time operational awareness.
Why VPC Monitoring Matters
Without monitoring, your VPC is a black box. You cannot answer fundamental questions like "Is my NAT Gateway throttling outbound traffic?" or "Is someone scanning my internal subnets?" The consequences of inadequate monitoring include:
- Silent failures – A VPN tunnel can drop without any visible impact until remote users report connectivity loss
- Cost overruns – An over-provisioned NAT Gateway can cost thousands of dollars monthly when a simple resize would suffice
- Security blind spots – Unusual traffic patterns, such as port scans or data exfiltration attempts, go undetected
- Resource exhaustion – Running out of IP addresses in a critical subnet can halt all new container or instance launches
- Compliance gaps – Regulatory frameworks often require proof of network-level auditing and anomaly detection
Effective monitoring transforms your VPC from opaque infrastructure into a transparent, predictable system where problems are caught before users notice them.
Key VPC Metrics You Should Track
AWS surfaces several categories of metrics natively. Understanding what each represents and when to alert on it is the foundation of a solid monitoring strategy.
VPC Flow Logs Metrics
Flow Logs themselves do not emit CloudWatch metrics directly—they publish log records to CloudWatch Logs, S3, or Kinesis Data Firehose. However, you can extract metrics from these logs using CloudWatch Logs Metric Filters. The most valuable derived metrics include:
- RejectedTraffic – Count of packets rejected by security groups or NACLs, signaling misconfigurations or intrusion attempts
- AcceptedTrafficByPort – Breakdown of accepted flows by destination port, useful for profiling legitimate traffic patterns
- CrossAZTraffic – Flows traversing Availability Zones, which incur inter-AZ data transfer costs
- ExternalIPAccess – Flows to/from public IP addresses outside your VPC, indicating internet-facing communication
Here is how you create a metric filter that counts rejected traffic and surfaces it as a CloudWatch metric:
aws logs put-metric-filter \
--log-group-name "vpc-flow-logs" \
--filter-name "RejectedTraffic" \
--filter-pattern '{ ($.action = "REJECT") }' \
--metric-transformations \
'[{
"metricName": "RejectedPacketCount",
"metricNamespace": "VPC/FlowLogs",
"metricValue": "1",
"unit": "Count",
"defaultValue": 0
}]' \
--region us-east-1
This command assumes your flow logs are in JSON format (the recommended configuration for metric extraction). The filter pattern uses CloudWatch Logs' JSON query syntax to match only records where the action field equals REJECT. Each matching log entry increments the metric by 1.
NAT Gateway Metrics
NAT Gateways emit the following native CloudWatch metrics under the AWS/NATGateway namespace every minute:
- BytesInFromSource – Bytes received from instances in your VPC
- BytesOutToDestination – Bytes sent to external destinations
- BytesOutToSource – Bytes returned to instances from external hosts
- BytesInFromDestination – Bytes received from external hosts
- ActiveConnectionCount – Concurrent active connections (max ~55,000 per NAT Gateway)
- PacketsInFromSource / PacketsOutToDestination – Packet-level throughput counters
- ConnectionAttemptCount – New connection attempts per minute
- ErrorPortAllocation – Increments when ports are exhausted (signals you need to scale)
- IdleTimeoutCount – Connections closed due to idle timeout (350 seconds default)
A critical alarm to configure is on ErrorPortAllocation, which indicates port starvation. Each NAT Gateway has roughly 55,000 ports available. When this metric is non-zero, connections are being refused:
aws cloudwatch put-metric-alarm \
--alarm-name "NATGateway-PortExhaustion" \
--alarm-description "Alert when NAT Gateway runs out of ports" \
--namespace "AWS/NATGateway" \
--metric-name "ErrorPortAllocation" \
--statistic "Sum" \
--period 300 \
--evaluation-periods 1 \
--threshold 1 \
--comparison-operator "GreaterThanOrEqualToThreshold" \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:ops-alerts" \
--dimensions "Name=NatGatewayId,Value=nat-0abcd1234efgh5678" \
--region us-east-1
VPN Connection Metrics
Site-to-Site VPN tunnels publish metrics under AWS/VPN namespace. These are essential for hybrid cloud architectures:
- TunnelState – Binary indicator (0 = down, 1 = up) for each tunnel endpoint
- TunnelDataIn / TunnelDataOut – Bytes received/sent per tunnel
- TunnelErrorCount – Errors on the tunnel during the reporting interval
The TunnelState metric is the most important—you want to know immediately when a VPN endpoint fails. Here is a composite alarm that triggers when either tunnel goes down:
aws cloudwatch put-metric-alarm \
--alarm-name "VPN-TunnelDown-Tunnel1" \
--namespace "AWS/VPN" \
--metric-name "TunnelState" \
--statistic "Minimum" \
--period 60 \
--evaluation-periods 3 \
--threshold 0 \
--comparison-operator "LessThanOrEqualToThreshold" \
--dimensions "Name=VpnId,Value=vpn-0abc1234" "Name=TunnelIpAddress,Value=203.0.113.25" \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:vpn-alerts" \
--region us-east-1
Repeat this for the second tunnel IP. A best practice is to configure these alarms with a composite alarm so you only page when both tunnels are down, avoiding noise from transient single-tunnel flaps.
Subnet IP Utilization Metrics
Introduced in 2022, these metrics appear under AWS/EC2 namespace with the SubnetId dimension:
- SubnetIPUsage – Number of IP addresses currently allocated in the subnet
- SubnetIPTotal – Total usable IP addresses (CIDR size minus 5 reserved by AWS)
These are crucial for container clusters (EKS, ECS) or auto-scaling groups where IP consumption grows rapidly. You can compute utilization percentage using CloudWatch Math expressions in dashboards or alarms:
{
"metrics": [
{
"id": "used",
"namespace": "AWS/EC2",
"metricName": "SubnetIPUsage",
"dimensions": { "SubnetId": "subnet-0abc1234" },
"stat": "Average",
"period": 300
},
{
"id": "total",
"namespace": "AWS/EC2",
"metricName": "SubnetIPTotal",
"dimensions": { "SubnetId": "subnet-0abc1234" },
"stat": "Average",
"period": 300
},
{
"id": "utilization",
"expression": "(used / total) * 100",
"label": "IP Utilization %",
"region": "us-east-1"
}
]
}
Setting Up Alarms That Actually Work
Alarms transform raw metrics into actionable notifications. The difference between a useful alarm and noise lies in thoughtful configuration of thresholds, evaluation periods, and composite logic.
Static Threshold Alarms
The simplest alarm type triggers when a metric crosses a fixed value. Use these for binary conditions (tunnel state, port exhaustion errors) or metrics with predictable ceilings (CPU on a fixed-size fleet). The NAT Gateway port exhaustion alarm shown earlier is a classic static threshold example.
Anomaly Detection Alarms
For metrics with variable baselines—like NAT Gateway throughput that fluctuates with user activity—anomaly detection learns the expected pattern and alerts on deviations. This reduces false positives from static thresholds on dynamic workloads:
aws cloudwatch put-metric-alarm \
--alarm-name "NATGateway-AnomalousThroughput" \
--namespace "AWS/NATGateway" \
--metric-name "BytesOutToDestination" \
--statistic "Sum" \
--period 300 \
--evaluation-periods 2 \
--threshold 2 \
--comparison-operator "GreaterThanUpperThreshold" \
--treat-missing-data "missing" \
--dimensions "Name=NatGatewayId,Value=nat-0abcd1234efgh5678" \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:ops-alerts" \
--region us-east-1
The GreaterThanUpperThreshold comparison operator triggers when the metric exceeds the upper band of the anomaly detection model. The alarm automatically adapts to daily and weekly patterns.
Metric Math Alarms
Sometimes you need to alert on a derived value—like the ratio of rejected to accepted traffic. Metric math alarms use expressions to compute new metrics from existing ones:
aws cloudwatch put-metric-alarm \
--alarm-name "HighRejectionRatio" \
--namespace "VPC/FlowLogs" \
--metric-name "RejectionRatio" \
--statistic "Average" \
--period 300 \
--evaluation-periods 3 \
--threshold 20 \
--comparison-operator "GreaterThanThreshold" \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:security-alerts" \
--metrics '[{
"Id": "rejected",
"MetricStat": {
"Metric": { "Namespace": "VPC/FlowLogs", "MetricName": "RejectedPacketCount" },
"Period": 300, "Stat": "Sum"
},
"ReturnData": false
},
{
"Id": "accepted",
"MetricStat": {
"Metric": { "Namespace": "VPC/FlowLogs", "MetricName": "AcceptedPacketCount" },
"Period": 300, "Stat": "Sum"
},
"ReturnData": false
},
{
"Id": "ratio",
"Expression": "(rejected / accepted) * 100",
"Label": "RejectionPercentage"
}]' \
--region us-east-1
This alarm calculates the percentage of rejected packets relative to accepted ones and fires when it exceeds 20%, potentially indicating a misconfigured security group or an active threat.
Composite Alarms
Composite alarms reduce alert fatigue by combining multiple alarm states. They only trigger when all underlying alarms are in alarm state. This is ideal for VPN tunnels where you want notification only when both tunnels fail:
aws cloudwatch put-composite-alarm \
--alarm-name "VPN-BothTunnelsDown" \
--alarm-description "Triggers only when both VPN tunnels are down" \
--alarm-rule '(ALARM("arn:aws:cloudwatch:us-east-1:123456789012:alarm:VPN-TunnelDown-Tunnel1")
AND ALARM("arn:aws:cloudwatch:us-east-1:123456789012:alarm:VPN-TunnelDown-Tunnel2"))' \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:critical-alerts" \
--region us-east-1
Building Effective Dashboards
CloudWatch Dashboards give you a unified, shareable view of your VPC's operational state. A well-designed dashboard tells a story: traffic flows top to bottom, from aggregate overviews to specific resource details.
Dashboard Architecture for VPC Monitoring
A production-grade VPC dashboard typically contains these sections:
- Network Overview Row – Total bytes in/out across all NAT Gateways, aggregate VPN throughput
- Traffic Analysis Row – Flow log derived metrics showing accept/reject ratios, top talkers by port
- Resource Health Row – Individual NAT Gateway connection counts, port errors, VPN tunnel states
- Capacity Planning Row – Subnet IP utilization gauges for each critical subnet
Creating a Dashboard Programmatically
You can create dashboards via the AWS CLI, which is ideal for infrastructure-as-code pipelines. Here is a complete dashboard definition that covers VPC monitoring:
aws cloudwatch put-dashboard \
--dashboard-name "VPC-Operations-Dashboard" \
--dashboard-body '{
"widgets": [
{
"type": "metric",
"x": 0, "y": 0, "width": 12, "height": 6,
"properties": {
"metrics": [
["AWS/NATGateway", "BytesOutToDestination", { "stat": "Sum", "period": 300 }],
["AWS/NATGateway", "BytesInFromDestination", { "stat": "Sum", "period": 300 }]
],
"view": "timeSeries",
"stacked": false,
"region": "us-east-1",
"title": "NAT Gateway Aggregate Throughput",
"yAxis": { "left": { "label": "Bytes/5min" } }
}
},
{
"type": "metric",
"x": 0, "y": 6, "width": 6, "height": 6,
"properties": {
"metrics": [
["VPC/FlowLogs", "RejectedPacketCount", { "stat": "Sum", "period": 300 }],
["VPC/FlowLogs", "AcceptedPacketCount", { "stat": "Sum", "period": 300 }]
],
"view": "timeSeries",
"stacked": true,
"region": "us-east-1",
"title": "Flow Log Accept/Reject Breakdown"
}
},
{
"type": "metric",
"x": 6, "y": 6, "width": 6, "height": 6,
"properties": {
"metrics": [
["AWS/VPN", "TunnelState", { "stat": "Minimum", "period": 60, "dimensions": { "VpnId": "vpn-0abc1234" } }]
],
"view": "timeSeries",
"region": "us-east-1",
"title": "VPN Tunnel State (1=Up, 0=Down)"
}
},
{
"type": "metric",
"x": 0, "y": 12, "width": 6, "height": 6,
"properties": {
"metrics": [
["AWS/NATGateway", "ActiveConnectionCount", { "stat": "Average", "period": 300 }],
["AWS/NATGateway", "ErrorPortAllocation", { "stat": "Sum", "period": 300 }]
],
"view": "timeSeries",
"region": "us-east-1",
"title": "NAT Gateway Connections & Errors"
}
},
{
"type": "metric",
"x": 6, "y": 12, "width": 6, "height": 6,
"properties": {
"metrics": [
{
"id": "used",
"namespace": "AWS/EC2",
"metricName": "SubnetIPUsage",
"dimensions": { "SubnetId": "subnet-0abc1234" },
"stat": "Average", "period": 300
},
{
"id": "total",
"namespace": "AWS/EC2",
"metricName": "SubnetIPTotal",
"dimensions": { "SubnetId": "subnet-0abc1234" },
"stat": "Average", "period": 300
},
{
"id": "utilization",
"expression": "(used / total) * 100",
"label": "IP Utilization %"
}
],
"view": "gauge",
"region": "us-east-1",
"title": "Subnet IP Utilization",
"yAxis": { "left": { "min": 0, "max": 100 } }
}
}
]
}' \
--region us-east-1
This dashboard JSON defines five widgets arranged in a grid. The x/y coordinates position each widget, and the width/height control their span. The gauge widget for subnet utilization is particularly effective for capacity planning—it shows at a glance how close you are to exhaustion.
Adding Flow Log Dashboards with CloudWatch Logs Insights
For deeper traffic analysis, you can combine dashboards with CloudWatch Logs Insights queries that run against your Flow Logs. While not a native dashboard widget, you can pin Logs Insights results to dashboards. Here is a query you would run and pin to analyze top destination ports:
fields dstPort, @timestamp, action
| filter action = 'ACCEPT'
| stats count(*) as flowCount by dstPort
| sort flowCount desc
| limit 10
This query aggregates accepted flows by destination port, revealing your most active services. Pinning this to a dashboard gives you continuous visibility into traffic composition without running ad-hoc queries.
Infrastructure as Code: CloudFormation Example
For production environments, you should define monitoring alongside your infrastructure. Here is a CloudFormation snippet that creates a NAT Gateway with its associated alarm in a single template:
Resources:
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt EIP.AllocationId
SubnetId: !Ref PublicSubnet1
NatGatewayPortAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${AWS::StackName}-NATGW-PortExhaustion"
Namespace: AWS/NATGateway
MetricName: ErrorPortAllocation
Statistic: Sum
Period: 300
EvaluationPeriods: 1
Threshold: 1
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: NatGatewayId
Value: !Ref NatGateway
AlarmActions:
- !Ref AlertingSNSTopic
VpnTunnelAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${AWS::StackName}-VPN-TunnelDown"
Namespace: AWS/VPN
MetricName: TunnelState
Statistic: Minimum
Period: 60
EvaluationPeriods: 3
Threshold: 0
ComparisonOperator: LessThanOrEqualToThreshold
Dimensions:
- Name: VpnId
Value: !Ref VpnConnection
- Name: TunnelIpAddress
Value: "203.0.113.25"
AlarmActions:
- !Ref AlertingSNSTopic
FlowLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/vpc/${AWS::StackName}-flow-logs"
RetentionInDays: 30
RejectedTrafficMetricFilter:
Type: AWS::Logs::MetricFilter
Properties:
LogGroupName: !Ref FlowLogGroup
FilterPattern: '{ ($.action = "REJECT") }'
MetricTransformations:
- MetricName: RejectedPacketCount
MetricNamespace: VPC/FlowLogs
MetricValue: "1"
DefaultValue: 0
This template ensures every deployment gets monitoring by default. The alarm references the NAT Gateway resource directly, so the dimensions are always correct. The metric filter on the flow log group surfaces rejection metrics automatically.
Best Practices for VPC Monitoring
After implementing the technical components, these operational practices ensure your monitoring stays effective over time:
1. Enable Flow Logs on Every VPC
Enable VPC Flow Logs at the VPC level to capture all traffic. Use JSON format with the full set of fields. Publish to CloudWatch Logs for metric extraction or to S3 for long-term retention and Athena-based analysis. The minimal additional cost (roughly $0.50 per TB processed) is negligible compared to the visibility gained.
2. Build Alarms Before You Need Them
Configure alarms during infrastructure provisioning, not after an outage. A NAT Gateway port exhaustion alarm should exist from day one—retroactively adding it means you will eventually experience the failure before the alarm exists.
3. Use Composite Alarms for High-Availability Resources
For redundant resources like VPN tunnels (two per connection) or NAT Gateways (one per AZ), use composite alarms to reduce noise. Alert only when redundancy is exhausted, not when a single component fails over.
4. Implement Anomaly Detection for Dynamic Workloads
Static thresholds work poorly for traffic patterns that vary by time of day or day of week. Anomaly detection models learn these patterns automatically. Enable it on throughput metrics for NAT Gateways and VPN connections in production environments with variable load.
5. Monitor Subnet IP Utilization Proactively
Set alarms at 70% and 85% utilization on every subnet that hosts dynamic workloads (containers, auto-scaling groups). IP exhaustion causes hard failures that are difficult to recover from without subnet resizing or re-architecting.
6. Centralize Dashboards Across Accounts
If you operate multiple AWS accounts, use CloudWatch cross-account dashboards or tools like Grafana with CloudWatch data sources to build a single pane of glass. VPC monitoring scattered across account-specific dashboards creates fragmentation.
7. Tag Metrics for Cost Attribution
Use CloudWatch metric tags or consistent namespace conventions to attribute monitoring costs to specific teams or environments. NAT Gateway metrics are free, but custom metrics and Logs Insights queries have associated costs.
8. Test Your Alarm Pipeline
Periodically trigger alarms intentionally (e.g., by temporarily modifying a security group to generate rejects) to validate that notifications reach the correct channels and that runbooks are current. An alarm that fires into an unmonitored Slack channel provides no value.
Conclusion
VPC monitoring is not optional in any serious AWS deployment. The combination of VPC Flow Logs for traffic observability, native CloudWatch metrics for resource performance, and subnet utilization tracking for capacity planning gives you complete visibility into your network infrastructure. By setting up thoughtful alarms—using static thresholds for binary conditions, anomaly detection for variable workloads, and composite alarms for redundant resources—you catch problems before they impact users. Well-structured dashboards turn this data into operational context that your entire team can act on. The code examples in this guide provide a starting point; the real value comes from embedding these patterns into your infrastructure-as-code templates so that every VPC you deploy is observable from the moment it is created.