VPC Flow Logs Not Showing? Enable, View and Read Them: Record Format, REJECT Records, CloudWatch Logs Insights, Athena, Cost, GCP and Azure

Logeshwaran
—

VPC Flow Logs record the IP traffic going to and from the network interfaces in your VPC: who talked to whom, on which port, how much, and whether security groups and network ACLs accepted or rejected it. You turn them on for a whole VPC, a subnet or one network interface, and send the records to CloudWatch Logs (easy to search), Amazon S3 (cheaper for volume, queried with Athena) or Data Firehose. They are billed per GB of log data delivered, at a lower rate to S3 than to CloudWatch Logs, plus storage. If your flow log shows Active but nothing appears, the usual reasons are: the first records can take 10 minutes or more, and the CloudWatch log group is only created once traffic is recorded; the IAM role cannot publish to CloudWatch Logs (status Access error); the S3 bucket policy or a KMS key blocks delivery; or the traffic you are looking for is one of the types flow logs never record, such as Amazon DNS, instance metadata and DHCP. Below: setup with the console, CLI, Terraform and CloudFormation, how to view and read the records field by field, REJECT records, CloudWatch Logs Insights and Athena queries, costs, Google Cloud and Azure equivalents, and every reason flow logs do not show.

The shop's ticket database on RDS stopped accepting connections from a new Lambda function one Tuesday, with nothing more helpful than a timeout. Ethan suspected a security group but could not see which one. He turned on VPC Flow Logs for the subnet, waited twelve long minutes in which nothing appeared, and then saw the answer in one line: traffic from the function's address to port 5432, action REJECT. Flow logs did not fix the problem; they showed exactly where it was. This page is how to set them up, read them, and get past the twelve minutes of nothing.

⚡ Quick Answer

• Enable: VPC console → your VPC → Flow logs tab → Create flow log. CLI and Terraform.

• Nothing showing? Wait 10+ minutes, generate traffic, and check the Status column. Every cause.

• Read a record: source, destination, ports, protocol, bytes, ACCEPT or REJECT. Field by field.

• Cost: per GB delivered; S3 is cheaper than CloudWatch Logs. Cost.

What they capture · View them · REJECT records · Logs Insights · Athena · GCP and Azure.

New to VPCs and networking on AWS? The cards below cover S3, Athena, CloudWatch and the routing problems flow logs help you diagnose.

🧭 NEW HERE? READ THESE FIRST

New to AWS networking or logging? These five pages give you the background this one builds on:

📌 Bookmark this; the field table and the "not showing" checklist are the parts you will come back for.

What VPC Flow Logs capture, and what they do not

A flow log is a running summary of network conversations. For each "flow" (a source and destination address and port, a protocol, on one network interface, during a short interval), it records how many packets and bytes passed and whether they were allowed. It does not record the content of the traffic: no URLs, no file contents, no database queries. Think of it as a phone bill that lists calls, not a recording of them.

You can create a flow log at three levels:

  • A VPC: every network interface in it, including ones created later.
  • A subnet: every interface in that subnet.
  • A network interface (ENI): just one instance, Lambda function, load balancer or endpoint.

Transit Gateways have their own flow logs too, and AWS services that place interfaces in your VPC (RDS, Lambda, load balancers, NAT gateways, VPC endpoints) are covered automatically when you log their VPC or subnet.

What people use them for. Four jobs cover most of it: finding out why a connection fails (the REJECT line), seeing what talks to what before you tighten a security group, investigating something suspicious after the fact, and meeting audit requirements that say network traffic must be logged. A fifth, less obvious one: finding what drives a large NAT gateway or data transfer bill.

Traffic flow logs never record. This list explains many "my flow log is missing traffic" reports:

  • Traffic to the Amazon DNS server (if you run your own DNS server, that traffic is logged).
  • Instance metadata at 169.254.169.254 and the Amazon Time Sync Service at 169.254.169.123.
  • DHCP and ARP traffic.
  • Windows license activation traffic to Amazon.
  • Traffic to the reserved IP address of the default VPC router.
  • Traffic between an endpoint network interface and a Network Load Balancer network interface.
  • The source side of Traffic Mirroring (only mirrored target traffic appears).

If you need packet contents, Traffic Mirroring copies real packets to an analysis tool; flow logs are for the who-talked-to-whom view.

Jake: "So it's like the shop's door log, not a camera?"

Ethan: "Exactly. It says who came in, when, and whether the door let them in. Not what they said once inside."

Enable VPC Flow Logs: console, CLI, Terraform, CloudFormation

In the console:

  1. Open the VPC console, choose Your VPCs (or Subnets, or the EC2 console's Network Interfaces), and select the resource.
  2. Open the Flow logs tab and choose Create flow log.
  3. Choose the filter: All, Accept or Reject. "All" is the usual choice; "Reject" alone is a cheap way to watch only blocked traffic.
  4. Choose the maximum aggregation interval: 1 minute (more detail, more records) or 10 minutes. On Nitro-based instances, which is most current instance types, the interval is always 1 minute or less whatever you pick.
  5. Choose the destination (CloudWatch Logs, S3 or Firehose) and its settings, described below.
  6. Keep the default record format or choose custom fields, then create.

With the CLI, to S3 (no IAM role needed; the bucket policy is updated for you):

aws ec2 create-flow-logs \
  --resource-type VPC --resource-ids vpc-0abc1234def567890 \
  --traffic-type ALL \
  --log-destination-type s3 \
  --log-destination arn:aws:s3:::shop-flow-logs/vpc-main/ \
  --max-aggregation-interval 60 \
  --destination-options FileFormat=parquet,HiveCompatiblePartitions=true,PerHourPartition=true

To CloudWatch Logs, you also pass a log group name and an IAM role the flow logs service can use:

aws ec2 create-flow-logs \
  --resource-type Subnet --resource-ids subnet-0abc1234def567890 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /vpc/flow-logs/db-subnet \
  --deliver-logs-permission-arn arn:aws:iam::111122223333:role/VPCFlowLogsToCloudWatch

With a custom format, add --log-format and list the fields you want, each wrapped in ${...}. Put the string in single quotes in Bash so the shell leaves it alone:

--log-format '${version} ${vpc-id} ${subnet-id} ${instance-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${tcp-flags} ${packets} ${bytes} ${start} ${end} ${action} ${flow-direction} ${traffic-path} ${pkt-srcaddr} ${pkt-dstaddr} ${log-status}'

Terraform:

resource "aws_flow_log" "main" {
  vpc_id                   = aws_vpc.main.id
  traffic_type             = "ALL"
  log_destination_type     = "s3"
  log_destination          = "${aws_s3_bucket.flow_logs.arn}/vpc-main/"
  max_aggregation_interval = 60

  destination_options {
    file_format                = "parquet"
    hive_compatible_partitions = true
    per_hour_partition         = true
  }
}

For CloudWatch Logs in Terraform, set log_destination_type = "cloud-watch-logs", point log_destination at an aws_cloudwatch_log_group ARN, and add iam_role_arn. Creating the log group yourself in Terraform also lets you set its retention, which the flow logs service will not do for you.

CloudFormation uses AWS::EC2::FlowLog with ResourceId, ResourceType: VPC, TrafficType: ALL, LogDestinationType: s3 and LogDestination set to the bucket ARN:

VpcFlowLog:
  Type: AWS::EC2::FlowLog
  Properties:
    ResourceId: !Ref MainVpc
    ResourceType: VPC
    TrafficType: ALL
    LogDestinationType: s3
    LogDestination: !Sub "arn:aws:s3:::${FlowLogBucket}/vpc-main/"
    MaxAggregationInterval: 60

Settings cannot be changed after creation: to change the format, role, filter or destination, delete the flow log and create a new one. With Terraform or CloudFormation, that happens for you when you change one of those properties, and you lose nothing except a few minutes of records.

The IAM role for CloudWatch Logs, and the S3 bucket policy

For CloudWatch Logs, the flow logs service publishes using a role you provide. Its trust policy must name the flow logs service:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "vpc-flow-logs.amazonaws.com"},
    "Action": "sts:AssumeRole",
    "Condition": {"StringEquals": {"aws:SourceAccount": "111122223333"}}
  }]
}

And its permissions policy must allow writing logs:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents",
      "logs:DescribeLogGroups", "logs:DescribeLogStreams"
    ],
    "Resource": "*"
  }]
}

A wrong principal (for example ec2.amazonaws.com), a missing trust relationship, or missing log permissions all show up the same way: the flow log's status becomes Access error. The person creating the flow log also needs iam:PassRole on that role, or the create call itself fails.

For S3, no role is involved. The log delivery service writes to the bucket, and when you create a flow log in the same account, AWS adds the needed statement (for the delivery.logs.amazonaws.com service) to the bucket policy for you. For a bucket in another account, you add that statement yourself, with conditions limiting it to your source accounts. If the bucket uses SSE-KMS default encryption, the key policy must also let the delivery service use the key.

VPC flow logs not showing: every cause

First, how long is normal. After each aggregation interval closes, delivery takes extra time: typically about 5 minutes to CloudWatch Logs and about 10 minutes to S3, on a best-effort basis. Add the interval itself and the first records after creating a flow log can take 10 to 20 minutes. If you are past that, the table below covers the rest.

SymptomCauseFix
Status Active, no log group or files yetFirst records take 10 minutes or more; the log group appears only when traffic is recordedWait, and generate traffic (connect to something from an instance)
Status Access errorIAM role lacks log permissions, or its trust policy does not name vpc-flow-logs.amazonaws.comFix the role; the flow log keeps the same role, so update the role itself
Status Rate limitedToo many records for one interface, or the account's log group quota reachedUse S3 for high-volume interfaces; raise the CloudWatch Logs quota
LogDestinationNotFoundException or Access Denied for LogDestination when creatingBucket does not exist, ARN wrong, or bucket policy blocks deliveryCheck the bucket ARN; add the delivery statement to the bucket policy
LogDestinationPermissionIssueExceptionBucket policy hit its 20 KB size limit from many flow log entriesReplace per-flow-log entries with one statement for arn:aws:s3:::bucket/*
"LogDestination ... is undeliverable"Bucket default encryption names a KMS key ID, not a key ARNSet the bucket's default encryption to the KMS key ARN
Records exist, but not for the traffic you expectThe traffic is a type flow logs never capture (DNS, metadata, DHCP...)Check the "not captured" list above
Only some interfaces appearFlow log on a subnet or ENI, not the VPC; or the interfaces had no trafficCreate the flow log at the level you need
Looking in the wrong placeDifferent Region, different log group name or S3 prefixCheck the destination in the flow log's details
Traffic to a secondary IP shows the wrong addressdstaddr shows the interface's primary private IPAdd pkt-dstaddr to a custom format to see the original address
Cannot enable flow logs for a peered VPCYou can only create flow logs for a peered VPC that is in your own accountAsk the other account's owner to create one on their side
Records with NODATA or SKIPDATANo traffic in that interval, or records skipped by an internal limitNODATA is normal; occasional SKIPDATA is expected

To see the status and any error message from the command line: aws ec2 describe-flow-logs --query "FlowLogs[].[FlowLogId,FlowLogStatus,DeliverLogsStatus,DeliverLogsErrorMessage]" --output table. A delivery status of FAILED with an error message tells you which row of the table you are in.

For a quick check after creating a flow log, start a small, deliberate flow: from an instance in the VPC, try to reach something on a port you know is blocked, then something you know is allowed. Within one aggregation interval plus a few minutes of delivery, both should appear, one as REJECT and one as ACCEPT.

Jake: "Twelve minutes of nothing, and then the answer?"

Ethan: "Twelve minutes of the service setting itself up. After that, new records show up every few minutes."

How to view and check VPC flow logs

Where you look depends on the destination you chose. The flow log's own details (VPC console, your VPC, Flow logs tab) show the destination, so start there if you are not sure.

In CloudWatch Logs:

  1. Open the CloudWatch console and choose Log groups.
  2. Open the flow log's log group, for example /vpc/flow-logs/db-subnet.
  3. You will see one log stream per network interface, named after the interface ID (such as eni-0a1b2c3d4e5f67890-all). Open the stream for the interface you care about.
  4. Use the filter box to narrow down, for example by typing REJECT or an IP address.

To find which interface belongs to which resource, the EC2 console's Network interfaces page shows each interface's description (such as "AWS Lambda VPC ENI" or "ELB app/shop-alb") and private IP.

In S3:

  1. Open the bucket and the prefix you set, then AWSLogs/, your account ID, vpcflowlogs/, the Region, and the year, month and day.
  2. Each file holds a few minutes of records for one flow log. Text files are gzip-compressed; Parquet files need a query tool.
  3. Download a .log.gz file and open it with any tool that reads gzip to see the raw lines, or skip this and query with Athena (below).

From the command line, the AWS CLI can follow a CloudWatch Logs flow log live, much like tail -f:

# Follow new records as they arrive
aws logs tail /vpc/flow-logs/db-subnet --follow

# Only REJECT records from the last hour
aws logs tail /vpc/flow-logs/db-subnet --since 1h --filter-pattern REJECT

# Everything to or from one address
aws logs tail /vpc/flow-logs/db-subnet --since 30m --filter-pattern '"10.0.2.40"'

For S3, aws s3 ls s3://shop-flow-logs/vpc-main/AWSLogs/ --recursive lists the files, and Athena is the practical way to read more than a handful.

The flow log record format, field by field

The default format (version 2) has 14 fields separated by spaces:

2 111122223333 eni-0a1b2c3d4e5f67890 10.0.1.25 10.0.2.40 49152 5432 6 5 300 1760872000 1760872060 REJECT OK
FieldIn the exampleMeaning
version2Record format version
account-id111122223333Account that owns the source interface
interface-ideni-0a1b...The network interface the traffic passed through
srcaddr, dstaddr10.0.1.25 → 10.0.2.40Source and destination IP addresses
srcport, dstport49152 → 5432Ports; the high random one is usually the client
protocol6IANA number: 6 TCP, 17 UDP, 1 ICMP
packets, bytes5, 300Volume during the interval
start, endUnix timestampsThe capture window, in seconds since 1970 (UTC)
actionREJECTACCEPT or REJECT by security groups and network ACLs
log-statusOKOK, NODATA (no traffic), or SKIPDATA (records skipped)

Read the example aloud and it tells a story: something at 10.0.1.25 tried to reach PostgreSQL (port 5432) at 10.0.2.40 and was rejected. That was Ethan's Lambda function, blocked by the database's security group. A field that does not apply to a record shows a dash (-), which is why NODATA lines are mostly dashes.

Flow log versions and custom fields. The default format cannot be changed, but a custom format can include any of the newer fields, in any order. The version number in each record is the highest version among the fields you picked:

VersionFields it addedWhat you would use them for
2The 14 default fieldsThe basics: who, where, how much, allowed or not
3vpc-id, subnet-id, instance-id, tcp-flags, type, pkt-srcaddr, pkt-dstaddrGrouping by VPC or instance; seeing real addresses behind NAT gateways and EKS pods
4region, az-id, sublocation-type, sublocation-idCross-AZ traffic (and its data transfer cost), Local Zones and Outposts
5pkt-src-aws-service, pkt-dst-aws-service, flow-direction, traffic-pathIs this traffic to S3 or DynamoDB? Ingress or egress? Did it leave through an internet gateway, endpoint or peering?
7ECS cluster, service, task and container fieldsTracing traffic back to an ECS task or service
8reject-reasonBPA when VPC Block Public Access rejected it, EC for encryption controls
9 to 11resource-id, encryption-status, instance, interface and Auto Scaling group tags, interface-type, next-hop fieldsRegional NAT gateways, encryption-in-transit checks, and labeling records with your own tags

tcp-flags is the most useful of the extra fields for troubleshooting. It is a bitmask: FIN is 1, SYN is 2, RST is 4 and SYN-ACK is 18, and flags seen during one interval are added together (19 means SYN-ACK plus FIN). A flow from a client with only flag 2 and no matching 18 coming back means the client kept knocking and nobody answered: a timeout in the making, even when the action says ACCEPT, which points at the server, a route or the other side's network rather than your security group.

traffic-path (for egress traffic) tells you which way traffic left: 1 another resource in the same VPC, 2 an internet gateway or gateway VPC endpoint, 3 a virtual private gateway, 4 intra-Region peering, 5 inter-Region peering, 6 a Local Zone or Wavelength Zone, 7 a gateway VPC endpoint, 8 an internet gateway. That one number answers "is my S3 traffic using the endpoint?" without guessing.

Reading REJECT records: security group or network ACL?

A REJECT means a security group or network ACL blocked the traffic (or packets arrived after the connection had already closed), but the record does not say which. Two clues narrow it down:

  • Security groups are stateful. If a request is accepted, the reply is allowed automatically. So a REJECT on the incoming request (a client's high port to your service's port) points at the security group or the network ACL's inbound rules.
  • Network ACLs are stateless. Replies must be allowed separately, usually to the ephemeral ports 1024 to 65535. If you see the request ACCEPTed but the reply REJECTed (traffic from your service's port back to the client's high port), a network ACL's outbound or the client side's inbound rule is the likely culprit.

For Ethan's database, the request from the Lambda function's address to port 5432 was rejected on arrival: the database's security group allowed the old app server's security group, but not the new function's. Adding an inbound rule for the function's security group fixed it in a minute. Our RDS connection timeout guide walks through that exact case.

Not every REJECT is a problem: internet-facing addresses receive constant scans, and rejected scans are your security groups doing their job. A few stray REJECTs after a connection closes are also normal. What deserves attention is a REJECT for traffic you expected to work, or a sudden jump in REJECTs from inside your own network.

Querying flow logs in CloudWatch Logs Insights

When flow logs go to CloudWatch Logs in the default format, Logs Insights recognizes the fields automatically (as srcAddr, dstAddr, dstPort, action, bytes and so on). Open CloudWatch, choose Logs Insights, pick your flow log group, set the time range, and try these:

# Top rejected sources and ports
filter action = "REJECT"
| stats count(*) as rejects by srcAddr, dstAddr, dstPort
| sort rejects desc
| limit 20

# Who is talking to the database?
filter dstPort = 5432
| stats sum(bytes) as bytes, count(*) as flows by srcAddr, action

# Biggest talkers by bytes
stats sum(bytes) as total by srcAddr, dstAddr
| sort total desc
| limit 10

# Rejects over time, in 5-minute buckets
filter action = "REJECT"
| stats count(*) as rejects by bin(5m)

The last query, shown as a line chart, makes a sudden change obvious: a deployment that broke a security group rule shows up as a cliff at the minute it happened. With a custom format, Insights does not know your field order, so you split the message yourself with the parse command, naming each field in the order you chose.

Logs Insights is billed per GB of log data scanned by each query, so narrow the time range before running a query over weeks of busy flow logs.

Turning REJECTs into an alarm. A metric filter counts matching records and creates a CloudWatch metric you can alarm on. This pattern counts rejected SSH attempts in the default format:

aws logs put-metric-filter \
  --log-group-name /vpc/flow-logs/db-subnet \
  --filter-name RejectedSSH \
  --filter-pattern '[version, account, eni, source, destination, srcport, destport="22", protocol="6", packets, bytes, windowstart, windowend, action="REJECT", flowlogstatus]' \
  --metric-transformations metricName=RejectedSSH,metricNamespace=FlowLogs,metricValue=1

Then create an alarm on FlowLogs/RejectedSSH with a threshold that suits you. Set the alarm to treat missing data as not breaching; a quiet network produces no data points, and our guide to alarms stuck in INSUFFICIENT_DATA explains why that matters.

Querying flow logs in S3 with Athena

For flow logs in S3, the quickest start is the VPC console's Generate Athena integration option on the flow log, which creates a CloudFormation template with a partitioned table and sample queries. If you are new to Athena itself, our guide to querying S3 with Athena covers the setup. To understand what the integration builds, here is a minimal table for the default text format over one day's folder:

CREATE EXTERNAL TABLE vpc_flow_logs (
  `version` int, `account_id` string, `interface_id` string,
  `srcaddr` string, `dstaddr` string, `srcport` int, `dstport` int,
  `protocol` bigint, `packets` bigint, `bytes` bigint,
  `start` bigint, `end` bigint, `action` string, `log_status` string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ' '
LOCATION 's3://shop-flow-logs/vpc-main/AWSLogs/111122223333/vpcflowlogs/us-east-1/2026/10/19/'
TBLPROPERTIES ("skip.header.line.count" = "1");

SELECT srcaddr, dstport, count(*) AS rejects
FROM vpc_flow_logs
WHERE action = 'REJECT'
GROUP BY srcaddr, dstport
ORDER BY rejects DESC
LIMIT 20;

"no viable alternative" when creating the table. This parse error usually comes from two things in flow log field names: hyphens (account-id is not a valid column name, so use account_id) and reserved words such as end, which need backticks as in the example. Each text file also starts with a header line naming the fields, which the skip.header.line.count property skips.

For anything beyond a day, use partitions (the generated integration does this) so each query reads only the dates it needs: Athena bills by data scanned. Delivering flow logs in Parquet with Hive-compatible, per-hour partitions, as in the CLI example above, makes Athena queries both faster and much cheaper than gzipped text, because Athena reads only the columns your query uses. If a query fails on one damaged file, our Athena HIVE_BAD_DATA guide shows how to find and skip it.

A useful daily query once partitions are set up: total bytes per source to destinations outside your VPC range, which shows you who is sending the most data to the internet.

SELECT srcaddr, sum(bytes) / 1e9 AS gb_out
FROM vpc_flow_logs
WHERE action = 'ACCEPT'
  AND srcaddr LIKE '10.0.%'
  AND dstaddr NOT LIKE '10.%'
GROUP BY srcaddr
ORDER BY gb_out DESC
LIMIT 20;

Five questions flow logs answer

1. Why does my connection time out? Filter by the destination address and port. A REJECT points at a security group or network ACL. An ACCEPT with only SYN flags (tcp-flags 2) and nothing coming back points at the server not listening, a missing route, or a firewall on the far side. No record at all means the traffic never reached that interface: check routing first, including Transit Gateway routes, which our Transit Gateway propagation guide covers.

2. What is driving my NAT gateway bill? NAT gateways charge per GB processed. Log the NAT gateway's subnet with pkt-srcaddr and pkt-dstaddr in a custom format, then sum bytes by pkt-srcaddr: that shows which private instances send the data, rather than the NAT gateway's own address. Often the answer is S3 traffic that could use a free gateway endpoint instead. Our guide to Lambda in a VPC and NAT gateways covers the endpoint route.

3. Is my S3 traffic using the VPC endpoint? Add pkt-dst-aws-service and traffic-path. Records with S3 as the destination service and traffic-path 7 went through the gateway endpoint; traffic-path 8 means they went out through the internet gateway, so the endpoint's route table association is probably missing.

4. Which pod sent this? In Amazon EKS, pods can use addresses different from their node's interface. pkt-srcaddr shows the pod's address while srcaddr shows the node's, so you can trace traffic to a pod rather than a whole node.

5. What talks to this server before I lock it down? Before tightening a security group, run a flow log for a week and list every source and port that reached the server with ACCEPT. That list becomes your new rule set, and you avoid finding out about a forgotten nightly job by breaking it.

Jake: "So the flow log is less of an alarm and more of a diary?"

Ethan: "A diary you can search. The alarm is the metric filter you add on top."

What VPC Flow Logs cost

Flow logs themselves have no fixed fee, so they are not free, but you pay only for the log data they produce. They are billed as vended logs, at per-GB rates that depend on the destination:

  • To CloudWatch Logs: a per-GB ingestion charge (tiered, cheaper at higher volumes), plus CloudWatch Logs storage per GB-month and the cost of Logs Insights queries.
  • To S3: a lower per-GB delivery charge than CloudWatch Logs, plus ordinary S3 storage, plus Athena scanning when you query.
  • To Data Firehose: delivery charges plus Firehose processing, for streaming into other tools.

The volume depends on traffic, not on the number of instances: one busy NAT gateway or load balancer can produce more log data than a hundred quiet servers. A 1-minute aggregation interval and a long custom format both add volume. Four ways to keep the bill sensible:

  • Send high volumes to S3, ideally in Parquet, and keep CloudWatch Logs for small, frequently searched flow logs.
  • Log only REJECT where you only need to see blocked traffic.
  • Scope the flow log to the subnets or interfaces you care about instead of the whole VPC.
  • Set retention: a CloudWatch Logs retention period or an S3 lifecycle rule, so last year's flows do not pile up.

To estimate before committing, run a flow log to S3 for a day or two and check how many gigabytes it produced; multiply by your Region's per-GB rate. Tag the flow log, and its delivery cost can be followed in Cost Explorer like any other tagged resource. If some records were skipped (SKIPDATA), Cost Explorer can show slightly more flow log volume than you can find in your destination; that small gap is expected.

Centralized flow logs for many accounts

Organizations with many AWS accounts usually send every account's flow logs to one S3 bucket in a dedicated logging account:

  1. Create the bucket in the log archive account, with versioning, encryption and a lifecycle rule.
  2. Add a bucket policy statement allowing the log delivery service to write, with conditions on aws:SourceAccount (or your organization ID) so only your accounts can deliver. Keep it to one statement covering the whole bucket, so the policy never hits its 20 KB size limit.
  3. In each account, create flow logs with the central bucket's ARN as the destination; each account's logs land under its own AWSLogs/ACCOUNT-ID/ prefix.
  4. Query everything from one place with Athena, partitioned by account, Region and date.

AWS Organizations, AWS Control Tower and tools such as Terraform modules can create the flow logs in every account automatically, so new accounts are covered from day one.

Making sure every VPC has flow logs

It is easy to create a new VPC and forget its flow log. Two built-in checks catch that:

  • AWS Config has a managed rule, vpc-flow-logs-enabled, that marks any VPC without a flow log as noncompliant. You can set it to require a specific traffic type, such as REJECT or ALL.
  • AWS Security Hub includes control EC2.6, "VPC flow logging should be enabled in all VPCs", in the AWS Foundational Security Best Practices and CIS AWS Foundations standards. It recommends logging at least rejected traffic.

Both tell you about the gap; neither closes it on its own. To close it, pair the Config rule with an automatic remediation (a Systems Manager Automation document or a small Lambda function that creates the flow log), or create flow logs in the same Terraform or CloudFormation code that creates each VPC, so a VPC never exists without one.

VPC Flow Logs in Google Cloud and Azure

If you work across clouds, the idea carries over, but the details differ.

Google Cloud VPC Flow Logs are enabled per subnet (and, in newer setups, through configurations at the network or organization level). Two settings differ from AWS: an aggregation interval that can be as short as 5 seconds, and a sampling rate between 0 and 1, because Google Cloud records a sample of flows rather than every one. Records go to Cloud Logging, from where you can send them to BigQuery for analysis. Enabling them on a subnet with gcloud looks like this:

gcloud compute networks subnets update shop-subnet \
  --region=us-central1 \
  --enable-flow-logs \
  --logging-aggregation-interval=interval-5-sec \
  --logging-flow-sampling=0.5 \
  --logging-metadata=include-all

A sampling rate of 0.5 records about half of the flows, which keeps cost down; set 1 when you need everything. To see traffic that Google Cloud firewall rules blocked, you turn on Firewall Rules Logging for those rules, a separate feature from flow logs.

Azure has two kinds of flow logs in Network Watcher, and one is on its way out. NSG flow logs (network security group flow logs) can no longer be created since June 30, 2025, and are retired on September 30, 2027. Their replacement is virtual network flow logs, enabled per virtual network, subnet or network interface, stored in an Azure Storage account, and analyzed with Traffic Analytics in a Log Analytics workspace. If you still have NSG flow logs, plan the move before the retirement date.

AWSGoogle CloudAzure
Enabled onVPC, subnet, interface, Transit GatewaySubnet (plus network/org configs)Virtual network, subnet, interface
Every flow or a sample?Every flow (rare SKIPDATA)Sampled, rate you chooseEvery flow
Shows blocked trafficYes, action REJECTVia Firewall Rules LoggingYes, flow state denied
Where records goCloudWatch Logs, S3, FirehoseCloud Logging, then BigQuery or storageStorage account, Traffic Analytics
Query toolLogs Insights, AthenaLogs Explorer, BigQueryLog Analytics (KQL)

Flow logs vs GuardDuty and CloudTrail

  • GuardDuty analyzes VPC flow log data from its own independent feed for threat detection. You do not need to enable flow logs for GuardDuty to work, and enabling them does not change GuardDuty's bill. Your own flow logs are for your own investigation and retention: GuardDuty tells you something looks wrong, and your flow logs let you look at the surrounding traffic yourself.
  • CloudTrail records API calls (who created a security group, who changed a route), not network traffic. When a flow log shows traffic suddenly being rejected, CloudTrail tells you who changed the rule and when. The two work best together.
  • Traffic Mirroring copies actual packets for deep inspection. It is the right tool when you need to see contents, and far heavier than flow logs, so it is usually turned on for a specific investigation rather than left running.

Frequently asked questions

What are VPC Flow Logs?

A feature that records summaries of IP traffic to and from network interfaces in a VPC: addresses, ports, protocol, bytes, and whether the traffic was accepted or rejected.

What are VPC flow logs used for?

Troubleshooting connections that fail, seeing what talks to what before tightening security groups, security investigations, audit requirements and finding what drives NAT gateway and data transfer costs.

How do I enable VPC Flow Logs?

In the VPC console, select the VPC, subnet or interface, open the Flow logs tab and choose Create flow log, or run aws ec2 create-flow-logs.

Why are my VPC flow logs not showing?

The first records can take 10 minutes or more, the log group appears only once there is traffic, or the status shows an access error from the IAM role or bucket policy.

How long until VPC flow logs appear?

Delivery typically takes about 5 minutes to CloudWatch Logs and about 10 minutes to S3 after each aggregation interval, on a best-effort basis.

How do I check or view VPC flow logs?

In CloudWatch Logs, open the flow log's log group and the stream for the network interface; in S3, browse the AWSLogs prefix or query with Athena.

Are VPC Flow Logs free?

No. There is no fixed fee, but you pay per GB of log data delivered at vended log rates, lower for S3 than CloudWatch Logs, plus storage and query costs.

What does REJECT mean in VPC flow logs?

A security group or network ACL blocked the traffic. The record does not say which; request versus reply direction helps tell them apart.

What is the VPC flow log format?

By default, version 2 with 14 space-separated fields: version, account-id, interface-id, srcaddr, dstaddr, srcport, dstport, protocol, packets, bytes, start, end, action and log-status.

What traffic is not captured by VPC flow logs?

Amazon DNS, instance metadata, Amazon Time Sync, DHCP, ARP, Windows license activation, traffic to the default VPC router's reserved IP, and some endpoint-to-NLB traffic.

What IAM role do VPC flow logs need for CloudWatch Logs?

A role trusted by vpc-flow-logs.amazonaws.com with logs:CreateLogGroup, CreateLogStream, PutLogEvents, DescribeLogGroups and DescribeLogStreams.

Should I send flow logs to CloudWatch Logs or S3?

CloudWatch Logs for small volumes you search often; S3 for large volumes, long retention and Athena queries, at lower cost.

How do I query VPC flow logs with Athena?

Use the console's Generate Athena integration option, or create an external table over the S3 location, then query with SQL.

Can I change a flow log after creating it?

No. Delete it and create a new one with the destination, role or fields you need.

What do NODATA and SKIPDATA mean?

NODATA means no traffic in that interval. SKIPDATA means some records were skipped during the interval, usually due to an internal capacity limit.

Does GuardDuty need VPC flow logs enabled?

No. GuardDuty reads flow log data from its own independent feed.

How do I enable VPC flow logs in Terraform?

Use the aws_flow_log resource with vpc_id, traffic_type, log_destination_type and log_destination, plus destination_options for Parquet.

What is the LogDestinationPermissionIssueException error?

The S3 bucket policy reached its 20 KB size limit from many flow log entries. Replace them with one statement covering the whole bucket.

How do I check that every VPC has flow logs?

Use the AWS Config managed rule vpc-flow-logs-enabled or Security Hub control EC2.6, which flag any VPC without a flow log.

What are VPC flow logs in GCP?

Google Cloud's equivalent, enabled per subnet, with a sampling rate and aggregation interval you choose, sent to Cloud Logging.

For readers preparing for an AWS certification

We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.

Exams that test this topic: CloudOps Engineer – Associate (SOA-C03) • Security – Specialty (SCS-C03) • Solutions Architect – Associate (SAA-C03).

Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:

1. An application cannot reach a database in the same VPC. Which feature shows whether security groups or network ACLs rejected the traffic?

VPC Flow Logs: records with action REJECT for the source, destination and port.

2. A flow log publishing to CloudWatch Logs shows the status "Access error". What is the likely cause?

The IAM role is missing log permissions, or its trust policy does not allow vpc-flow-logs.amazonaws.com.

3. A security team needs years of flow logs at the lowest cost, queried with SQL. Which destination and tool fit?

Amazon S3 (ideally Parquet with partitions) queried with Amazon Athena.

4. A request is ACCEPTed in the flow log but the reply to the client's ephemeral port is REJECTed. What is the likely cause?

A stateless network ACL rule that does not allow the return traffic; security groups are stateful.

5. Which traffic never appears in VPC flow logs?

Traffic to the Amazon DNS server, instance metadata (169.254.169.254), Amazon Time Sync, DHCP and ARP, among others.

6. How do you change the record format of an existing flow log?

You cannot; delete it and create a new flow log with a custom format.

7. Which managed checks flag VPCs without flow logs?

The AWS Config rule vpc-flow-logs-enabled and Security Hub control EC2.6.

Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.

πŸ“š ALSO READ

Where to go next, from load balancers and subnets to the problems flow logs help you find:

📌 Bookmark this; the field table and the "not showing" checklist are the parts you will come back for.

The flow log stayed on after the database fix, sending to S3 in Parquet at a cost of a few cents a month for the shop's quiet network. Two weeks later it answered another question in one Athena query: a misconfigured backup job had been trying to reach an old server's address every five minutes since the summer. Jake now asks "what does the flow log say?" before anyone touches a security group. Ethan considers that the best outcome of the whole exercise.

📌 If you keep one line from this page

Create the flow log on the VPC, subnet or interface, send it to S3 in Parquet for volume or CloudWatch Logs for quick searches, wait 10+ minutes with real traffic before worrying, and read the Status column: Access error means the role or bucket policy, not the network.

14 default fields · REJECT = security group or NACL · DNS, metadata and DHCP never logged · settings fixed after creation.

Revision note. Written October 19, 2026. A network that fails silently is hard to fix; a flow log turns the silence into one line that says REJECT.

Related