Skip to content
AWS aws networking 5 min read

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 at 169.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.

LevelCapturesWhen to use
VPCAll ENIs (Elastic Network Interfaces — the virtual network cards) in the whole VPCBroad auditing or compliance where you need everything
SubnetAll ENIs in one subnetInvestigating a tier (e.g. a private app subnet)
ENIOne network interfaceDebugging 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.

DestinationBest forQuery toolCost shape
CloudWatch LogsReal-time troubleshooting, alarms, dashboardsCloudWatch Logs InsightsIngestion + storage per GB
Amazon S3Cheap long-term archive, big analyticsAmazon 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

  1. Open the VPC console and select Your VPCs (or Subnets, or Network Interfaces depending on the level you want).
  2. Select the resource, e.g. the VPC vpc-0a1b2c3d.
  3. Choose the Flow logs tab, then Create flow log.
  4. Set Filter to Reject (recommended to start), Accept, or All.
  5. Set the Maximum aggregation interval to 10 minutes (cheaper) or 1 minute (more detail).
  6. Choose the Destination: Send to CloudWatch Logs or Send to an S3 bucket.
  7. For CloudWatch, pick the log group and an IAM role that allows publishing logs.
  8. Choose the log record format (use the AWS default unless you need custom fields).
  9. 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 ALL traffic at a 1-minute interval can generate millions of records and a surprisingly large CloudWatch ingestion bill (charged per GB ingested and stored). Start with REJECT only, 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 REJECT so 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.
Last updated June 15, 2026
Was this helpful?