Configure AWS VPC Default Network ACLs

The aws:ec2/defaultNetworkAcl:DefaultNetworkAcl resource, part of the Pulumi AWS provider, adopts a VPC’s default network ACL into Pulumi management, allowing you to define ingress and egress rules declaratively. This guide focuses on three capabilities: adopting the default ACL with standard AWS rules, creating asymmetric traffic patterns, and blocking all traffic to enforce explicit ACL usage.

This resource adopts an existing default ACL rather than creating one. Every VPC has a default ACL that AWS creates automatically. When Pulumi first adopts the ACL, it immediately removes all existing rules, then creates only the rules you specify. The examples are intentionally small. Combine them with your own VPC and subnet configuration.

Adopt the default ACL with AWS default rules

When you create a VPC, AWS provisions a default network ACL with permissive rules that allow all traffic. Teams often adopt this ACL into management to detect drift when rules change outside of infrastructure code.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const mainvpc = new aws.ec2.Vpc("mainvpc", {cidrBlock: "10.1.0.0/16"});
const _default = new aws.ec2.DefaultNetworkAcl("default", {
    defaultNetworkAclId: mainvpc.defaultNetworkAclId,
    ingress: [{
        protocol: "-1",
        ruleNo: 100,
        action: "allow",
        cidrBlock: "0.0.0.0/0",
        fromPort: 0,
        toPort: 0,
    }],
    egress: [{
        protocol: "-1",
        ruleNo: 100,
        action: "allow",
        cidrBlock: "0.0.0.0/0",
        fromPort: 0,
        toPort: 0,
    }],
});
import pulumi
import pulumi_aws as aws

mainvpc = aws.ec2.Vpc("mainvpc", cidr_block="10.1.0.0/16")
default = aws.ec2.DefaultNetworkAcl("default",
    default_network_acl_id=mainvpc.default_network_acl_id,
    ingress=[{
        "protocol": "-1",
        "rule_no": 100,
        "action": "allow",
        "cidr_block": "0.0.0.0/0",
        "from_port": 0,
        "to_port": 0,
    }],
    egress=[{
        "protocol": "-1",
        "rule_no": 100,
        "action": "allow",
        "cidr_block": "0.0.0.0/0",
        "from_port": 0,
        "to_port": 0,
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		mainvpc, err := ec2.NewVpc(ctx, "mainvpc", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.1.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewDefaultNetworkAcl(ctx, "default", &ec2.DefaultNetworkAclArgs{
			DefaultNetworkAclId: mainvpc.DefaultNetworkAclId,
			Ingress: ec2.DefaultNetworkAclIngressArray{
				&ec2.DefaultNetworkAclIngressArgs{
					Protocol:  pulumi.String("-1"),
					RuleNo:    pulumi.Int(100),
					Action:    pulumi.String("allow"),
					CidrBlock: pulumi.String("0.0.0.0/0"),
					FromPort:  pulumi.Int(0),
					ToPort:    pulumi.Int(0),
				},
			},
			Egress: ec2.DefaultNetworkAclEgressArray{
				&ec2.DefaultNetworkAclEgressArgs{
					Protocol:  pulumi.String("-1"),
					RuleNo:    pulumi.Int(100),
					Action:    pulumi.String("allow"),
					CidrBlock: pulumi.String("0.0.0.0/0"),
					FromPort:  pulumi.Int(0),
					ToPort:    pulumi.Int(0),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var mainvpc = new Aws.Ec2.Vpc("mainvpc", new()
    {
        CidrBlock = "10.1.0.0/16",
    });

    var @default = new Aws.Ec2.DefaultNetworkAcl("default", new()
    {
        DefaultNetworkAclId = mainvpc.DefaultNetworkAclId,
        Ingress = new[]
        {
            new Aws.Ec2.Inputs.DefaultNetworkAclIngressArgs
            {
                Protocol = "-1",
                RuleNo = 100,
                Action = "allow",
                CidrBlock = "0.0.0.0/0",
                FromPort = 0,
                ToPort = 0,
            },
        },
        Egress = new[]
        {
            new Aws.Ec2.Inputs.DefaultNetworkAclEgressArgs
            {
                Protocol = "-1",
                RuleNo = 100,
                Action = "allow",
                CidrBlock = "0.0.0.0/0",
                FromPort = 0,
                ToPort = 0,
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.DefaultNetworkAcl;
import com.pulumi.aws.ec2.DefaultNetworkAclArgs;
import com.pulumi.aws.ec2.inputs.DefaultNetworkAclIngressArgs;
import com.pulumi.aws.ec2.inputs.DefaultNetworkAclEgressArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var mainvpc = new Vpc("mainvpc", VpcArgs.builder()
            .cidrBlock("10.1.0.0/16")
            .build());

        var default_ = new DefaultNetworkAcl("default", DefaultNetworkAclArgs.builder()
            .defaultNetworkAclId(mainvpc.defaultNetworkAclId())
            .ingress(DefaultNetworkAclIngressArgs.builder()
                .protocol("-1")
                .ruleNo(100)
                .action("allow")
                .cidrBlock("0.0.0.0/0")
                .fromPort(0)
                .toPort(0)
                .build())
            .egress(DefaultNetworkAclEgressArgs.builder()
                .protocol("-1")
                .ruleNo(100)
                .action("allow")
                .cidrBlock("0.0.0.0/0")
                .fromPort(0)
                .toPort(0)
                .build())
            .build());

    }
}
resources:
  mainvpc:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.1.0.0/16
  default:
    type: aws:ec2:DefaultNetworkAcl
    properties:
      defaultNetworkAclId: ${mainvpc.defaultNetworkAclId}
      ingress:
        - protocol: -1
          ruleNo: 100
          action: allow
          cidrBlock: 0.0.0.0/0
          fromPort: 0
          toPort: 0
      egress:
        - protocol: -1
          ruleNo: 100
          action: allow
          cidrBlock: 0.0.0.0/0
          fromPort: 0
          toPort: 0

The defaultNetworkAclId property references the VPC’s default ACL ID. The ingress and egress arrays define rules with protocol (-1 means all protocols), ruleNo for evaluation order, action (allow or deny), and CIDR blocks. This configuration preserves AWS default behavior while bringing the ACL under Pulumi management.

Block outbound traffic while allowing inbound

Some security architectures require asymmetric traffic rules, such as allowing inbound connections while preventing resources from initiating outbound requests.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const mainvpc = new aws.ec2.Vpc("mainvpc", {cidrBlock: "10.1.0.0/16"});
const _default = new aws.ec2.DefaultNetworkAcl("default", {
    defaultNetworkAclId: mainvpc.defaultNetworkAclId,
    ingress: [{
        protocol: "-1",
        ruleNo: 100,
        action: "allow",
        cidrBlock: mainvpcAwsDefaultVpc.cidrBlock,
        fromPort: 0,
        toPort: 0,
    }],
});
import pulumi
import pulumi_aws as aws

mainvpc = aws.ec2.Vpc("mainvpc", cidr_block="10.1.0.0/16")
default = aws.ec2.DefaultNetworkAcl("default",
    default_network_acl_id=mainvpc.default_network_acl_id,
    ingress=[{
        "protocol": "-1",
        "rule_no": 100,
        "action": "allow",
        "cidr_block": mainvpc_aws_default_vpc["cidrBlock"],
        "from_port": 0,
        "to_port": 0,
    }])
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		mainvpc, err := ec2.NewVpc(ctx, "mainvpc", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.1.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewDefaultNetworkAcl(ctx, "default", &ec2.DefaultNetworkAclArgs{
			DefaultNetworkAclId: mainvpc.DefaultNetworkAclId,
			Ingress: ec2.DefaultNetworkAclIngressArray{
				&ec2.DefaultNetworkAclIngressArgs{
					Protocol:  pulumi.String("-1"),
					RuleNo:    pulumi.Int(100),
					Action:    pulumi.String("allow"),
					CidrBlock: pulumi.Any(mainvpcAwsDefaultVpc.CidrBlock),
					FromPort:  pulumi.Int(0),
					ToPort:    pulumi.Int(0),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var mainvpc = new Aws.Ec2.Vpc("mainvpc", new()
    {
        CidrBlock = "10.1.0.0/16",
    });

    var @default = new Aws.Ec2.DefaultNetworkAcl("default", new()
    {
        DefaultNetworkAclId = mainvpc.DefaultNetworkAclId,
        Ingress = new[]
        {
            new Aws.Ec2.Inputs.DefaultNetworkAclIngressArgs
            {
                Protocol = "-1",
                RuleNo = 100,
                Action = "allow",
                CidrBlock = mainvpcAwsDefaultVpc.CidrBlock,
                FromPort = 0,
                ToPort = 0,
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.DefaultNetworkAcl;
import com.pulumi.aws.ec2.DefaultNetworkAclArgs;
import com.pulumi.aws.ec2.inputs.DefaultNetworkAclIngressArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var mainvpc = new Vpc("mainvpc", VpcArgs.builder()
            .cidrBlock("10.1.0.0/16")
            .build());

        var default_ = new DefaultNetworkAcl("default", DefaultNetworkAclArgs.builder()
            .defaultNetworkAclId(mainvpc.defaultNetworkAclId())
            .ingress(DefaultNetworkAclIngressArgs.builder()
                .protocol("-1")
                .ruleNo(100)
                .action("allow")
                .cidrBlock(mainvpcAwsDefaultVpc.cidrBlock())
                .fromPort(0)
                .toPort(0)
                .build())
            .build());

    }
}
resources:
  mainvpc:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.1.0.0/16
  default:
    type: aws:ec2:DefaultNetworkAcl
    properties:
      defaultNetworkAclId: ${mainvpc.defaultNetworkAclId}
      ingress:
        - protocol: -1
          ruleNo: 100
          action: allow
          cidrBlock: ${mainvpcAwsDefaultVpc.cidrBlock}
          fromPort: 0
          toPort: 0

Omitting the egress property creates an implicit deny-all for outbound traffic. The ingress rule allows traffic from within the VPC’s CIDR block. This creates a one-way traffic pattern where resources can receive connections but cannot initiate them.

Lock down the default ACL to force explicit associations

Teams that want to enforce explicit network ACL assignments can configure the default ACL to deny all traffic, ensuring subnets must be associated with purpose-built ACLs.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const mainvpc = new aws.ec2.Vpc("mainvpc", {cidrBlock: "10.1.0.0/16"});
const _default = new aws.ec2.DefaultNetworkAcl("default", {defaultNetworkAclId: mainvpc.defaultNetworkAclId});
import pulumi
import pulumi_aws as aws

mainvpc = aws.ec2.Vpc("mainvpc", cidr_block="10.1.0.0/16")
default = aws.ec2.DefaultNetworkAcl("default", default_network_acl_id=mainvpc.default_network_acl_id)
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		mainvpc, err := ec2.NewVpc(ctx, "mainvpc", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.1.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewDefaultNetworkAcl(ctx, "default", &ec2.DefaultNetworkAclArgs{
			DefaultNetworkAclId: mainvpc.DefaultNetworkAclId,
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var mainvpc = new Aws.Ec2.Vpc("mainvpc", new()
    {
        CidrBlock = "10.1.0.0/16",
    });

    var @default = new Aws.Ec2.DefaultNetworkAcl("default", new()
    {
        DefaultNetworkAclId = mainvpc.DefaultNetworkAclId,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.DefaultNetworkAcl;
import com.pulumi.aws.ec2.DefaultNetworkAclArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var mainvpc = new Vpc("mainvpc", VpcArgs.builder()
            .cidrBlock("10.1.0.0/16")
            .build());

        var default_ = new DefaultNetworkAcl("default", DefaultNetworkAclArgs.builder()
            .defaultNetworkAclId(mainvpc.defaultNetworkAclId())
            .build());

    }
}
resources:
  mainvpc:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.1.0.0/16
  default:
    type: aws:ec2:DefaultNetworkAcl
    properties:
      defaultNetworkAclId: ${mainvpc.defaultNetworkAclId}

Omitting both ingress and egress properties creates an implicit deny-all for all traffic. Any subnet associated with this default ACL will be completely isolated. This forces you to create custom NetworkAcl resources and explicitly associate subnets with them.

Beyond these examples

These snippets focus on specific default network ACL features: adopting the default ACL into management, ingress and egress rule configuration, and implicit deny-all through rule omission. They’re intentionally minimal rather than full network security configurations.

The examples assume pre-existing infrastructure such as a VPC with a default network ACL (created automatically by AWS). They focus on ACL rule configuration rather than provisioning the surrounding network infrastructure.

To keep things focused, common ACL patterns are omitted, including:

  • Explicit subnet associations (subnetIds)
  • Tags for organization and cost tracking
  • IPv6 CIDR blocks (ipv6CidrBlock)
  • ICMP type/code configuration for protocol-specific rules

These omissions are intentional: the goal is to illustrate how default ACL adoption and rule management work, not provide drop-in network security modules. See the Default Network ACL resource reference for all available configuration options.

Let's configure AWS VPC Default Network ACLs

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Resource Behavior & Lifecycle
What happens when I first adopt the default network ACL?
Pulumi immediately removes all existing rules in the ACL before creating the rules specified in your configuration. Ensure your configuration includes all necessary rules to avoid unintended traffic blocking.
Can I destroy the default network ACL?
No, the default network ACL cannot be deleted. Removing this resource from your configuration only removes it from Pulumi’s state management but leaves the ACL, its rules, and subnet associations intact. You can resume managing it via the AWS Console.
Rule Management
Why can't I use aws.ec2.NetworkAclRule with this resource?
This resource treats inline rules as absolute, meaning only the rules defined within it are created. It’s incompatible with separate aws.ec2.NetworkAclRule resources. Define all rules inline using the ingress and egress properties.
What happens if I add or change rules outside of Pulumi?
Any external additions or removals will result in diffs being shown, as this resource treats its inline rules as absolute. Only the rules defined in your configuration will be maintained.
Subnet Associations
Why do I see recurring plans with subnet associations?
Subnets cannot be removed from the default network ACL; they must be reassigned to another ACL or destroyed. Orphaned subnets (from deleted custom ACLs) are automatically adopted by the default ACL, causing recurring diffs. To avoid this, either reassign subnets, add them to subnetIds, or use lifecycle ignoreChanges for subnetIds.
Common Configurations
How do I deny all egress traffic while allowing ingress?
Omit the egress property entirely and define only ingress rules. This denies all outbound traffic while permitting the specified inbound traffic.
How do I lock down the VPC by denying all traffic?
Create the resource with only defaultNetworkAclId specified, omitting both ingress and egress properties. This denies all traffic to any subnet in the default network ACL.

Using a different cloud?

Explore networking guides for other cloud providers: