When traffic is mysteriously dropped or an instance is talking to somewhere it should not, you need to see the traffic. VPC Flow Logs record metadata for every IP flow in your VPC - source, destination, ports, bytes, and whether it was accepted or rejected.
This page enables flow logs from the SDK to both CloudWatch Logs and S3, explains the IAM and format choices, and shows how to query the results to answer real questions.
The common setup is flow logs on a whole VPC delivered to a CloudWatch Logs group. This needs an IAM role that grants the flow-logs service permission to write - passed as DeliverLogsPermissionArn.
ResourceType can be VPC, Subnet, or NetworkInterface, scoping the logs from broad to narrow. TrafficType of ALL captures both accepted and rejected flows; the IAM role must trust vpc-flow-logs.amazonaws.com and allow logs:CreateLogStream and logs:PutLogEvents.
Delivering to S3 instead of CloudWatch Logs is cheaper for high volume and needs no IAM role - just a bucket whose policy allows the log-delivery service to write. This variant also narrows to REJECT traffic, the fastest way to hunt blocked connections.
Note LogDestination (an S3 ARN) replaces LogGroupName + DeliverLogsPermissionArn. Always check the Unsuccessful array in the response - CreateFlowLogs can partially fail per resource and still return 200, so an empty Unsuccessful is your confirmation.
Each record is a line of space-separated fields. The default format captures the 5-tuple and outcome: version, account id, interface id, source and destination address, source and destination port, protocol, packets, bytes, start and end time, action (ACCEPT / REJECT), and log status. The action field is what tells you a security group or NACL dropped the flow.
Crucially, flow logs are metadata, not packet capture. They record that 10.0.1.5 sent 4 KB to 52.10.0.5:443 and it was accepted - never the payload. For packet contents you need Traffic Mirroring, a separate feature.
You can capture extra fields (like pkt-srcaddr, pkt-dstaddr for the real source behind NAT, tcp-flags, or flow-direction) with a custom format string. Pass it as LogFormat with ${field} tokens.
Logs in CloudWatch are queryable with Logs Insights via StartQuery / GetQueryResults on the logs client. This finds the top REJECTed destinations - a quick triage for blocked traffic.
// --- TypeScript (AWS SDK v3) ---import { CloudWatchLogsClient, StartQueryCommand, GetQueryResultsCommand } from "@aws-sdk/client-cloudwatch-logs";const logs = new CloudWatchLogsClient({ region: "us-east-1" });const now = Math.floor(Date.now() / 1000);const q = await logs.send(new StartQueryCommand({ logGroupName: "/vpc/flowlogs", startTime: now - 3600, endTime: now, queryString: "filter action='REJECT' | stats count(*) as hits by dstAddr, dstPort " + "| sort hits desc | limit 20",}));await new Promise((r) => setTimeout(r, 3000));const res = await logs.send(new GetQueryResultsCommand({ queryId: q.queryId! }));console.log(res.results);
Insights queries are asynchronous: StartQuery returns a queryId, and you poll GetQueryResults until status is Complete. For S3-delivered logs you query with Athena instead.
Missing or misconfigured IAM role. CloudWatch delivery needs a role trusting vpc-flow-logs.amazonaws.com. Without it, CreateFlowLogs reports the resource in Unsuccessful.
Ignoring the Unsuccessful array. The call can partially fail and still return successfully; check that array to confirm each resource actually got a flow log.
Expecting packet contents. Flow logs are metadata only. Use Traffic Mirroring if you need payloads.
No traffic in the logs yet. Delivery is not instant - CloudWatch can lag several minutes, S3 up to ten. An empty log group right after creation is normal.
REJECT does not always mean a firewall. Some rejects are normal scanning noise from the internet; correlate with your own source IPs before alarming.
Forgetting log costs. High-volume ALL logging to CloudWatch can be expensive; use S3 for bulk retention and REJECT-only or subnet scope to trim volume.
Traffic Mirroring copies actual packets to a monitoring appliance when metadata is not enough - deep inspection at higher cost.
CloudWatch Logs Insights vs Athena - query CloudWatch-delivered logs with Insights; query S3-delivered logs with Athena over the raw files.
AWS Network Firewall / GuardDuty analyze traffic for threats automatically, layering detection on top of raw flow visibility.
Reachability Analyzer answers "can A reach B and why not" statically from configuration, complementing the observed traffic flow logs show.
Enable REJECT flow logs on sensitive subnets for cheap security visibility, add ALL to S3 for forensics and retention, and reach for Traffic Mirroring only when you truly need packet contents.
Metadata for each IP flow - source and destination address and port, protocol, packet and byte counts, time window, and whether the flow was accepted or rejected. Not the packet contents.
Can flow logs show packet payloads?
No. They are metadata only. For actual packet contents use Traffic Mirroring, a separate feature.
What can I attach flow logs to?
A whole VPC, a single subnet, or an individual network interface - set by ResourceType as VPC, Subnet, or NetworkInterface.
Do I need an IAM role for flow logs?
For CloudWatch Logs delivery yes - a role passed as DeliverLogsPermissionArn that trusts the flow-logs service. For S3 delivery you need only a bucket policy, no role.
What is the difference between ALL, ACCEPT, and REJECT?
TrafficType filters which flows are logged: everything, only accepted, or only rejected. REJECT is the quickest way to find traffic a security group or NACL blocked.
Why is my log group empty right after creating the flow log?
Delivery lags - a few minutes for CloudWatch, up to ten for S3. Also check the Unsuccessful array in the create response in case the resource failed to attach.
How do I query flow logs?
For CloudWatch-delivered logs use Logs Insights via StartQuery/GetQueryResults. For S3-delivered logs use Athena over the stored files.
Can I add custom fields to the log?
Yes. Pass a LogFormat string with ${field} tokens to capture extras like pkt-srcaddr, tcp-flags, or flow-direction beyond the default fields.
How do I reduce flow-log costs?
Deliver bulk logs to S3 rather than CloudWatch, log only REJECT where you just need blocked-traffic visibility, and scope to a subnet or ENI instead of the whole VPC.