Skip to main content
emnode / learn
Compliance

Enable VPC flow logs in every VPC

Security Hub EC2.6 — without flow logs you have no network audit trail. Investigations and threat detection need them.

12 min·10 sections·AWS

Last reviewed

Remediates AWS Security Hub: EC2.6

VPC flow logs: the basics

What does a flow log actually capture?

VPC flow logs are AWS's network-layer audit trail. Once enabled on a VPC, every packet that crosses an Elastic Network Interface (ENI) inside it produces a log record containing the source and destination IP, source and destination ports, protocol number, action (ACCEPT or REJECT — i.e. whether the Security Group or NACL allowed it), byte and packet counts, and timestamps for the start and end of the flow. They are metadata-only — no payloads — but they're more than enough to reconstruct who talked to whom, when, and whether the firewall stopped them.

The catch is that flow logs are off by default. A new VPC produces zero log records until someone explicitly turns the feature on, and the AWS Console doesn't surface a warning when they're missing. Security Hub control EC2.6 ("VPC flow logging should be enabled in all VPCs") exists for exactly this reason: it sweeps every VPC in every region and fails any that don't have at least one active flow log resource attached.

Without flow logs you have no idea what's happening on your network. The first time you'll wish you had them is during an incident — when GuardDuty fires a finding and the investigation needs to know which other hosts the attacker touched. By then it's too late: AWS does not retroactively reconstruct flows you didn't ask it to capture.

In this lesson you'll learn what a VPC flow log captures, how to pick the right destination (CloudWatch Logs vs S3 vs Kinesis Firehose) for your use case and budget, how to scope the filter and aggregation interval to keep costs sane, and how to bulk-enable flow logs across an account so no VPC ever ships without an audit trail.

Fun fact

GuardDuty is reading flow logs whether you can see them or not

Amazon GuardDuty ingests VPC flow logs implicitly for any account that has GuardDuty enabled — even if you, the customer, haven't turned flow logs on. That's how it spots crypto-miner traffic and command-and-control beacons. The catch: GuardDuty keeps that data internal. When it fires a finding and you want to investigate which other hosts the suspect IP touched, you'll find there are no flow logs to query unless you'd already enabled them yourself. GuardDuty raises the alarm; flow logs are the forensics.

Enabling flow logs in action

Marco joined a fintech six weeks ago as their first dedicated cloud security engineer. The Security Hub dashboard is a sea of red — among other things, EC2.6 is failing on 23 of the company's 31 VPCs across four regions. No flow logs anywhere.

He starts by enumerating which VPCs are unprotected. He doesn't want to enable flow logs on the eight that already have them — that's just duplicated cost.

Then he picks a destination: S3 with Athena for the cheap long-term audit trail, plus a small CloudWatch Logs feed on the production VPC for real-time alerting via Logs Insights.

First, find every VPC in the region that doesn't already have a flow log. Cross-reference describe-vpcs against describe-flow-logs.

$ aws ec2 describe-vpcs --query 'Vpcs[].VpcId' --output text | tr '\t' '\n' | while read v; do n=$(aws ec2 describe-flow-logs --filter Name=resource-id,Values=$v --query 'length(FlowLogs)' --output text); [ "$n" = "0" ] && echo "$v MISSING" || echo "$v ok"; done
vpc-0a1b2c3d4e5f60001 MISSING
vpc-0a1b2c3d4e5f60002 ok
vpc-0a1b2c3d4e5f60003 MISSING
vpc-0a1b2c3d4e5f60004 MISSING
vpc-0a1b2c3d4e5f60005 ok
# Three of five VPCs in this region have no flow log at all.

Inventory of unprotected VPCs in the region.

Now enable a flow log on one of the missing VPCs, shipping to S3 with a 600-second aggregation interval and the ALL filter for full visibility.

$ aws ec2 create-flow-logs --resource-type VPC --resource-ids vpc-0a1b2c3d4e5f60001 --traffic-type ALL --log-destination-type s3 --log-destination arn:aws:s3:::acme-flow-logs-eu-west-1/AWSLogs/ --max-aggregation-interval 600
{
"ClientToken": "abc123XYZ==",
"FlowLogIds": [
"fl-0123456789abcdef0"
],
"Unsuccessful": []
}
# Flow log active within ~10 min. EC2.6 will pass on next Security Hub scan.

VPC flow log live, shipping to S3 for cheap retention and Athena queries.

Flow logs under the hooddeep dive

Flow logs are not packet captures — they're flow records, one per (5-tuple, action, aggregation window). The 5-tuple is (src IP, dst IP, src port, dst port, protocol). The aggregation window is configurable: 60 seconds (the default and the only option for ENI-level logs on older instance types) or 600 seconds. Doubling the window roughly halves the record count and therefore halves the storage and ingest cost, which matters once a busy VPC starts producing tens of millions of records per day.

You pick one of three destinations per flow log. CloudWatch Logs lets you query interactively with Logs Insights and triggers metric filters and alarms in seconds — but it bills at roughly $0.50 per GB ingested plus storage, which on a chatty VPC adds up fast. S3 is the cheap option, around $0.023/GB/month for storage with Athena charged per-query — perfect for the long-term audit trail and ad-hoc investigations. Kinesis Data Firehose is the real-time pipeline option: stream flow logs straight into a SIEM (Splunk, Datadog, Sumo Logic) for correlation with other security telemetry.

The traffic-type filter controls what gets captured. ALL is the default and the safest for forensics — every accepted and rejected flow. REJECT-only is the cheapest baseline and surfaces blocked traffic (useful for spotting attempted intrusions and misconfigured services that can't reach what they need). ACCEPT-only filters down to allowed traffic, which is what you want for egress analysis but blinds you to attacks that were blocked.

# Three destination patterns for the same VPC, picked by use case.

# 1. CloudWatch Logs for real-time alerting via Logs Insights.
aws ec2 create-flow-logs \
  --resource-type VPC --resource-ids vpc-0a1b2c3d4e5f60001 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/flow-logs \
  --deliver-logs-permission-arn arn:aws:iam::123456789012:role/flow-logs-role

# 2. S3 for cheap long-term storage + Athena queries.
aws ec2 create-flow-logs \
  --resource-type VPC --resource-ids vpc-0a1b2c3d4e5f60001 \
  --traffic-type ALL \
  --log-destination-type s3 \
  --log-destination arn:aws:s3:::acme-flow-logs-eu-west-1/AWSLogs/ \
  --max-aggregation-interval 600

# 3. Kinesis Firehose for SIEM streaming.
aws ec2 create-flow-logs \
  --resource-type VPC --resource-ids vpc-0a1b2c3d4e5f60001 \
  --traffic-type ALL \
  --log-destination-type kinesis-data-firehose \
  --log-destination arn:aws:firehose:eu-west-1:123456789012:deliverystream/flow-logs-to-siem

What is the impact of leaving flow logs disabled?

The first impact is forensic blindness. When an incident happens — a leaked credential, a misconfigured S3 bucket, a compromised instance — the investigation needs to know which IPs the affected resources talked to, when, and how much data moved. Without flow logs, that question has no answer. You can rotate credentials and rebuild instances, but you can't tell the board, a regulator, or a customer what was exfiltrated.

The second impact is regulatory. SOC 2 CC7.2, ISO 27001 A.12.4, PCI DSS Requirement 10, and HIPAA all expect network access to be logged and reviewable. An auditor asking for six months of network-flow evidence is not a question you want to answer with "we didn't have flow logs on." Security Hub's EC2.6 finding becomes audit evidence the moment they walk in.

The third impact is detection. While GuardDuty consumes flow logs implicitly for its own analysis, more sophisticated detection — egress anomaly detection (an instance suddenly talking to a country it never has before), lateral movement detection (an internal IP scanning the VPC), exfiltration detection (an instance pushing gigabytes outbound at 3am) — requires that you have your own copy of the data to query, alert on, and correlate with other signals.

The cost of enabling flow logs is small and predictable: S3 with a 600s aggregation interval and ALL traffic typically runs $50-$200 per month per busy VPC. The cost of needing them and not having them is a public breach disclosure, a six-figure incident-response engagement, and a regulator who is no longer interested in your explanation.

How do you enable flow logs everywhere?

Getting flow logs in place is a four-step loop. Done right, you do it once and never have a Security Hub EC2.6 finding again.

1. Inventory every VPC across every region

Run describe-vpcs in each enabled region and cross-reference describe-flow-logs to find the ones without coverage. Don't forget the default VPCs AWS creates in regions you've never touched — Security Hub will flag those too, and they're easy to miss because nobody's deployed anything into them. Either delete them or enable flow logs.

2. Pick a destination per environment, not per VPC

S3 with a single account-wide bucket (one prefix per VPC) is the cheap default for the long-term audit trail. Add CloudWatch Logs only on production VPCs where you want Logs Insights queries during an incident. Add Kinesis Firehose only if you have a SIEM that's actively correlating flow data — otherwise you'll pay for a pipe nobody reads.

3. Bulk-enable across the account in one script run

Don't fix EC2.6 one VPC at a time through the console — write the script once, run it across every region. The describe-vpcs / create-flow-logs loop with a check for existing flow logs is idempotent, so you can re-run it after any new VPC gets created and it'll only act on the gaps.

4. Prevent recurrence with AWS Config and SCPs

Enable the AWS Config managed rule vpc-flow-logs-enabled to alert within minutes whenever a new VPC ships without a flow log. For organisations using AWS Organizations, attach a Service Control Policy that denies ec2:CreateVpc unless it's accompanied by ec2:CreateFlowLogs — or simply auto-remediate via Config + Systems Manager Automation.

# Bulk-enable VPC flow logs to S3 across every region, skipping VPCs that already have one.
BUCKET_ARN="arn:aws:s3:::acme-flow-logs"

for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
  for vpc in $(aws ec2 describe-vpcs --region $region --query 'Vpcs[].VpcId' --output text); do
    existing=$(aws ec2 describe-flow-logs --region $region \
      --filter Name=resource-id,Values=$vpc \
      --query 'length(FlowLogs)' --output text)
    if [ "$existing" = "0" ]; then
      echo "[$region] enabling flow log on $vpc"
      aws ec2 create-flow-logs --region $region \
        --resource-type VPC --resource-ids $vpc \
        --traffic-type ALL \
        --log-destination-type s3 \
        --log-destination $BUCKET_ARN/$region/ \
        --max-aggregation-interval 600
    fi
  done
done

Quick quiz

Question 1 of 5

You need a cheap long-term audit trail for forensics and ad-hoc investigations across 30 VPCs. Which destination + filter combination fits best?

You've completed Enable VPC flow logs in every VPC. You now know what flow logs actually capture, how to pick a destination by use case and budget, how to bulk-enable across an account, and how to prevent gaps from recurring with AWS Config and SCPs. The next time Security Hub fires an EC2.6 finding — or worse, an incident lands and someone asks "who did that instance talk to?" — you'll have the four-step loop and the audit trail ready to run.

Back to the library