← Back to blog
10 min readApril 28, 2026

AWS Network Firewall: centralized egress inspection and IPS for AWS environments

Mohamed Aït El KamelBy Mohamed Aït El Kamel, Founder & AWS Solutions Architect
AWS Network Firewall: centralized egress inspection and IPS for AWS environments

What is AWS Network Firewall?

AWS Network Firewall is a managed stateful network firewall and intrusion prevention service (IPS) that you deploy inside a VPC. It provides deep packet inspection, domain-based filtering, and Suricata-compatible rule groups at the network layer — capabilities that security groups and NACLs simply don't have.

Security groups are stateful and resource-attached. NACLs are stateless and subnet-attached. Both are free and appropriate for basic traffic controls. Network Firewall sits above both in the security stack: it gives you full application-layer visibility, the ability to block traffic based on domain names (not just IPs), and managed threat intelligence rule sets. You pay for it accordingly.

The typical deployment pattern is a centralized inspection VPC that all other VPCs route outbound traffic through. Rather than deploying a firewall in every VPC, you funnel egress through one fleet, inspect it, and let it exit to the internet. This is the architecture worth understanding before touching the service.

How Network Firewall works

When you create a Network Firewall, AWS provisions firewall endpoints — essentially managed ENIs — in subnets you designate in your VPC. One endpoint per Availability Zone. Traffic doesn't automatically pass through these endpoints; you have to route it there explicitly using VPC route tables.

This is the part that trips people up. The firewall endpoint is just an ENI with a special service endpoint ID (like a Gateway Load Balancer endpoint). You configure your route tables so that traffic that should be inspected has a next-hop pointing to the firewall endpoint in the appropriate AZ. Get this wrong and traffic bypasses the firewall entirely — silently.

Component hierarchy:

  • Firewall — the resource attached to a VPC. References a firewall policy.
  • Firewall policy — an ordered collection of rule groups. Stateless rules are evaluated first; traffic that doesn't match a stateless rule passes to the stateful engine.
  • Rule groups — stateless (5-tuple matching, fast path) or stateful (deep inspection using Suricata rules). Rule groups have a capacity unit that limits how many rules they can contain.
  • Firewall endpoints — one ENI per AZ in the firewall subnets you designate.

Rule types

Stateless rules

5-tuple matching only: source IP, destination IP, source port, destination port, protocol. Three actions: pass (allow, skip stateful engine), drop, or forward_to_sfe (send to stateful engine for further inspection).

Stateless rules are evaluated in priority order. A rule that matches forwards or drops immediately. Unmatched traffic falls through to the default action (typically forward_to_sfe).

Stateful rules

Three formats for stateful rules:

Domain list rules — the simplest format. Define a list of domains and whether to allow or deny them. AWS handles the matching against TLS SNI for HTTPS traffic and the HTTP Host header for plain HTTP.

Allow list: *.amazonaws.com, *.s3.amazonaws.com
Deny list: *.malware-c2.com, *.pastebin.com

Standard stateful rules — 5-tuple with state tracking, similar to a traditional stateful firewall.

Suricata-compatible IPS rules — full Suricata rule syntax. This is the most powerful option: pattern matching on packet payloads, TLS metadata, HTTP headers, DNS queries. AWS evaluates these rules using the same Suricata engine.

Example Suricata rule to block a specific domain over TLS using SNI inspection:

drop tls any any -> any 443 (tls.sni; content:"twitter.com"; endswith; nocase; msg:"Block Twitter TLS"; flow:to_server; sid:1000001; rev:1;)

Example rule to detect DNS queries to known bad domains:

alert dns any any -> any 53 (dns.query; content:"badactor.net"; nocase; msg:"DNS query to known bad domain"; sid:1000002; rev:1;)

The alert action logs the match without dropping it. Use drop to block. AWS managed rule groups use this same Suricata format.

Centralized egress architecture

Deploying Network Firewall in every spoke VPC is expensive and operationally painful. The recommended pattern is a dedicated inspection VPC connected to a Transit Gateway:

Spoke VPC A ─┐
Spoke VPC B ─┤──→ TGW ──→ Inspection VPC ──→ NAT Gateway ──→ Internet
Spoke VPC C ─┘                  │
                        (Network Firewall endpoints
                          in firewall subnets)

Traffic flow for outbound internet access from Spoke VPC A:

  1. Instance in Spoke VPC A sends traffic to 0.0.0.0/0
  2. Spoke VPC route table: default route → TGW attachment
  3. TGW route table: default route → Inspection VPC attachment
  4. Inspection VPC firewall subnet route table: traffic → Network Firewall endpoint
  5. Network Firewall inspects and (if allowed) forwards to the public subnet
  6. Public subnet route table: traffic → NAT Gateway → Internet Gateway → internet

The return path is the reverse. Every hop requires a correct route table entry. Missing or incorrect routes either break traffic or create bypass paths.

The AZ symmetry requirement is critical. A firewall endpoint in us-east-1a can only receive traffic from the same AZ. TGW appliance mode must be enabled on the Inspection VPC attachment — otherwise TGW may cross-AZ route traffic to a firewall endpoint in a different AZ than the source, breaking flows.

Setting up Network Firewall with the AWS CLI

# Step 1: Create a stateful rule group with a domain allowlist
aws network-firewall create-rule-group \
  --rule-group-name allow-aws-domains \
  --type STATEFUL \
  --capacity 100 \
  --rule-group '{
    "RulesSource": {
      "RulesSourceList": {
        "Targets": ["amazonaws.com", ".aws.amazon.com", "s3.amazonaws.com"],
        "TargetTypes": ["TLS_SNI", "HTTP_HOST"],
        "GeneratedRulesType": "ALLOWLIST"
      }
    }
  }' \
  --region us-east-1

# Step 2: Create a firewall policy referencing the rule group
aws network-firewall create-firewall-policy \
  --firewall-policy-name prod-egress-policy \
  --firewall-policy '{
    "StatelessDefaultActions": ["aws:forward_to_sfe"],
    "StatelessFragmentDefaultActions": ["aws:forward_to_sfe"],
    "StatefulDefaultActions": ["aws:drop_established"],
    "StatefulRuleGroupReferences": [
      {
        "ResourceArn": "arn:aws:network-firewall:us-east-1:123456789012:stateful-rulegroup/allow-aws-domains"
      }
    ]
  }' \
  --region us-east-1

# Step 3: Create the firewall in the inspection VPC
# One subnet mapping per AZ (your dedicated firewall subnets)
aws network-firewall create-firewall \
  --firewall-name prod-egress-firewall \
  --firewall-policy-arn arn:aws:network-firewall:us-east-1:123456789012:firewall-policy/prod-egress-policy \
  --vpc-id vpc-0abc1234def567890 \
  --subnet-mappings \
    SubnetId=subnet-firewall-az1a \
    SubnetId=subnet-firewall-az1b \
  --region us-east-1

# Step 4: Get the firewall endpoint IDs (needed for route table entries)
aws network-firewall describe-firewall \
  --firewall-name prod-egress-firewall \
  --region us-east-1 \
  --query 'FirewallStatus.SyncStates'

The SyncStates output gives you the AttachmentState and — once READY — the endpoint ID for each subnet. Use these endpoint IDs as the next-hop targets in your VPC route tables.

Terraform example

resource "aws_networkfirewall_rule_group" "allow_aws" {
  name     = "allow-aws-domains"
  type     = "STATEFUL"
  capacity = 100

  rule_group {
    rules_source {
      rules_source_list {
        generated_rules_type = "ALLOWLIST"
        target_types         = ["TLS_SNI", "HTTP_HOST"]
        targets              = ["amazonaws.com", ".amazonaws.com"]
      }
    }
  }
}

resource "aws_networkfirewall_firewall_policy" "egress" {
  name = "prod-egress-policy"

  firewall_policy {
    stateless_default_actions          = ["aws:forward_to_sfe"]
    stateless_fragment_default_actions = ["aws:forward_to_sfe"]

    stateful_engine_options {
      rule_order = "STRICT_ORDER"
    }

    stateful_default_actions = ["aws:drop_established"]

    stateful_rule_group_reference {
      resource_arn = aws_networkfirewall_rule_group.allow_aws.arn
      priority     = 1
    }
  }
}

resource "aws_networkfirewall_firewall" "egress" {
  name                = "prod-egress-firewall"
  firewall_policy_arn = aws_networkfirewall_firewall_policy.egress.arn
  vpc_id              = aws_vpc.inspection.id

  dynamic "subnet_mapping" {
    for_each = aws_subnet.firewall
    content {
      subnet_id = subnet_mapping.value.id
    }
  }

  tags = {
    Environment = "prod"
  }
}

# Extract firewall endpoint IDs for route table configuration
locals {
  firewall_endpoints = {
    for sync_state in tolist(aws_networkfirewall_firewall.egress.firewall_status[0].sync_states) :
    sync_state.availability_zone => sync_state.attachment[0].endpoint_id
  }
}

# Route table entry in firewall subnet: send return traffic to NAT GW
# Route table entry in public subnet: send inspected traffic to IGW
# (Add aws_route resources referencing local.firewall_endpoints)

Logging configuration

Network Firewall supports three log types, each configurable to S3, CloudWatch Logs, or Kinesis Firehose independently:

  • Alert logs — records for traffic matching rules with the alert action. Your Suricata IDS matches land here.
  • Flow logs — connection-level records for all traffic that passes through the firewall. Similar to VPC Flow Logs but richer: includes the rule action that was applied.
  • TLS logs — metadata from TLS-inspected connections: SNI, certificate subject, issuer, validity. Only relevant if you enable TLS inspection (requires a certificate manager configuration).
aws network-firewall update-logging-configuration \
  --firewall-name prod-egress-firewall \
  --logging-configuration '{
    "LogDestinationConfigs": [
      {
        "LogType": "ALERT",
        "LogDestinationType": "S3",
        "LogDestination": {
          "bucketName": "my-firewall-logs",
          "prefix": "alerts/"
        }
      },
      {
        "LogType": "FLOW",
        "LogDestinationType": "CloudWatchLogs",
        "LogDestination": {
          "logGroup": "/aws/network-firewall/flow"
        }
      }
    ]
  }' \
  --region us-east-1

Flow logs at scale generate a lot of data. Route alert logs to CloudWatch for real-time alarming, and flow logs to S3 with Athena for ad-hoc querying.

Security groups vs NACLs vs Network Firewall

ControlSecurity GroupsNACLsNetwork Firewall
Attachment levelResource (ENI)SubnetVPC / centralized inspection
StatefulnessStatefulStatelessBoth (stateless + stateful engines)
Protocol supportTCP/UDP/ICMPAll IP protocolsAll IP protocols
Domain/FQDN filteringNoNoYes
IPS/IDS capabilityNoNoYes (Suricata)
Managed threat intel rulesNoNoYes
Deep packet inspectionNoNoYes
CostFreeFree$0.395/hr/AZ + $0.065/GB

These controls are complementary, not alternatives. Security groups should still enforce principle of least privilege at the resource level. NACLs can provide a backstop at the subnet level. Network Firewall adds egress inspection and threat detection that neither of the others can do.

AWS managed rule groups

AWS provides managed Suricata-compatible rule groups you can subscribe to. These are updated by AWS Threat Intelligence and don't require you to maintain individual rules:

  • AWSManagedRulesThreatIntelIPList — known malicious IPs. Free tier available.
  • AWSManagedRulesAbusedLegitMalwareDomainsRules — domains associated with malware distribution.
  • AWSManagedRulesBotControlRuleSet — bot and scanner signatures.
  • AWSManagedRulesSuricataRuleSet — AWS's general-purpose Suricata ruleset.

Subscribe via the console or reference them in your firewall policy. Managed rules consume capacity from your rule group capacity limits, so plan accordingly.

Pricing

Network Firewall pricing has two components:

  • Firewall endpoint: $0.395/hour per AZ — $288/month per AZ
  • Data processed: $0.065/GB

For a 2-AZ production deployment processing 5 TB/month:

  • Endpoints: 2 × $288 = $576/month
  • Data: 5,000 GB × $0.065 = $325/month
  • Total: ~$901/month

For regulated workloads (PCI-DSS, HIPAA), environments with strict egress controls, or companies replacing a self-managed NGFW (Palo Alto, Fortinet), this cost is well justified. For small environments with minimal compliance requirements, it may not be.

One cost optimization: centralized inspection architecture means you pay for one firewall fleet across all spoke VPCs rather than one per VPC. At scale this is significantly cheaper than distributed deployment.

Verifying your routing topology

The most dangerous misconfiguration in a Network Firewall deployment is a routing gap — a subnet whose default route bypasses the firewall subnet entirely and goes directly to the NAT Gateway or Internet Gateway. Traffic flows, nothing breaks obviously, but the firewall never sees it.

With a centralized inspection VPC, you have route tables in: spoke VPCs, TGW route tables, firewall subnets in the inspection VPC, and public subnets in the inspection VPC. Getting all of them right simultaneously, and keeping them correct as the environment changes, requires being able to see the full routing topology.

VizCon visualizes your inspection VPC topology including firewall endpoint positions, subnet-level route tables, and TGW attachment configurations alongside all your spoke VPCs. This makes it straightforward to audit whether every spoke VPC's default route actually flows through the firewall before reaching the internet — and to catch the routing gaps that a security group audit will never surface.

See how VizCon works in 10 minutes

Book a personalized demo and discover how VizCon visualizes your live AWS infrastructure.

Book a demo