VPC Flow Logs
When something in your VPC (Virtual Private Cloud, your own private network inside AWS) can’t connect, you usually have no idea whether the traffic was blocked, dropped, or never arrived. VPC Flow Logs solve this by recording metadata about the IP traffic going to and from your network interfaces. They tell you the source, destination, ports, protocol, and whether each flow was accepted or rejected. This is the single most useful tool for debugging “why can’t these two things talk to each other?” and for spotting suspicious traffic.
What Flow Logs actually capture
A Flow Log records a flow — a summary of network traffic for a given source/destination pair over a short time window (typically a 60-second or 600-second aggregation interval). Each record includes fields like the source and destination IP, source and destination port, protocol number, packet and byte counts, and an action of either ACCEPT or REJECT.
The most important thing to understand: Flow Logs capture metadata only, never the contents of the packets. You will see that 10.0.1.45 talked to 10.0.2.90 on port 443, but you will not see what was inside that HTTPS conversation. For packet contents you need a different tool (VPC Traffic Mirroring).
Gotcha: Flow Logs do not record traffic to AWS-reserved or instance metadata addresses. That means DNS to the Amazon DNS server (the VPC base + 2 address, e.g.
10.0.0.2), DHCP, the instance metadata service at169.254.169.254, and Windows license activation traffic are all invisible. Don’t go hunting for them — they will never appear.
Three levels: VPC, subnet, or ENI
You can attach a Flow Log at three scopes. The level you pick controls how much you capture and how much you pay.
| Level | Captures | When to use |
|---|---|---|
| VPC | All ENIs (Elastic Network Interfaces — the virtual network cards) in the whole VPC | Broad auditing or compliance where you need everything |
| Subnet | All ENIs in one subnet | Investigating a tier (e.g. a private app subnet) |
| ENI | One network interface | Debugging one specific instance or load balancer node |
When to use this (and when not to): Use VPC-level logs for compliance and security baselines where coverage matters more than cost. Use ENI-level logs when you are actively debugging one resource and want a small, cheap, focused log. Avoid logging everything at full ACCEPT+REJECT scale in a busy production VPC unless you genuinely need it — see the cost note below.
Choosing a destination: CloudWatch Logs vs S3
Flow Log records have to go somewhere. You pick one of two destinations.
| Destination | Best for | Query tool | Cost shape |
|---|---|---|---|
| CloudWatch Logs | Real-time troubleshooting, alarms, dashboards | CloudWatch Logs Insights | Ingestion + storage per GB |
| Amazon S3 | Cheap long-term archive, big analytics | Amazon Athena (SQL over files) | Storage per GB (much cheaper at scale) |
Use CloudWatch Logs when you want to query and alert on traffic within minutes. Use S3 when you are keeping months of logs for audit and will run occasional SQL queries with Athena.
Enabling a Flow Log
Console steps
- Open the VPC console and select Your VPCs (or Subnets, or Network Interfaces depending on the level you want).
- Select the resource, e.g. the VPC
vpc-0a1b2c3d. - Choose the Flow logs tab, then Create flow log.
- Set Filter to
Reject(recommended to start),Accept, orAll. - Set the Maximum aggregation interval to
10 minutes(cheaper) or1 minute(more detail). - Choose the Destination: Send to CloudWatch Logs or Send to an S3 bucket.
- For CloudWatch, pick the log group and an IAM role that allows publishing logs.
- Choose the log record format (use the AWS default unless you need custom fields).
- Click Create flow log.
AWS CLI
This creates a VPC-level Flow Log that records only rejected traffic into CloudWatch Logs (AWS CLI v2):
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids vpc-0a1b2c3d \
--traffic-type REJECT \
--max-aggregation-interval 600 \
--log-destination-type cloud-watch-logs \
--log-group-name /vpc/flow-logs \
--deliver-logs-permission-arn arn:aws:iam::111122223333:role/flow-logs-role
Output:
{
"ClientToken": "abc123dEfGhIjKlMnOpQrStUvWxYz=",
"FlowLogIds": [
"fl-0a1b2c3d4e5f6g7h"
],
"Unsuccessful": []
}
To send to S3 instead, swap the destination flags:
aws ec2 create-flow-logs \
--resource-type Subnet \
--resource-ids subnet-0a1b2c3d \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination arn:aws:s3:::my-flow-logs-bucket/vpc/
Terraform
resource "aws_flow_log" "vpc_reject" {
vpc_id = "vpc-0a1b2c3d"
traffic_type = "REJECT"
max_aggregation_interval = 600
log_destination_type = "cloud-watch-logs"
log_destination = aws_cloudwatch_log_group.flow.arn
iam_role_arn = aws_iam_role.flow_logs.arn
}
A sample query
If your logs go to CloudWatch, use Logs Insights. This query finds the top rejected destination ports — a great way to spot a misconfigured security group or a port scan.
fields @timestamp, srcAddr, dstAddr, dstPort, action
| filter action = "REJECT"
| stats count(*) as rejects by dstPort
| sort rejects desc
| limit 10
Output:
# dstPort rejects
# 22 1842
# 3389 910
# 443 207
Lots of rejects on port 22 (SSH) or 3389 (RDP) from outside usually means scanners probing your edge — expected noise on a public ENI, but worth confirming nothing is accepted.
Cost warning: On a busy VPC, logging
ALLtraffic at a 1-minute interval can generate millions of records and a surprisingly large CloudWatch ingestion bill (charged per GB ingested and stored). Start withREJECTonly, or scope to specific ENIs, and use a 10-minute aggregation interval. Send long-term archives to S3, which is far cheaper per GB than CloudWatch Logs.
Best Practices
- Start with
traffic-type REJECTso you see blocked traffic without paying to log every accepted packet. - Use a 10-minute aggregation interval unless you specifically need per-minute detail.
- Send high-volume, long-retention logs to S3 + Athena; reserve CloudWatch Logs for active debugging and alarms.
- Scope to a subnet or ENI when troubleshooting one resource instead of logging the whole VPC.
- Remember Flow Logs are metadata only — reach for VPC Traffic Mirroring if you need actual packet contents.
- Set a retention policy on your CloudWatch log group (or an S3 lifecycle rule) so old logs expire and stop costing you money.
- Don’t expect to see DNS-to-Amazon, DHCP, or metadata (
169.254.169.254) traffic — it is intentionally excluded.