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 default ACLs with standard AWS rules, creating one-way traffic patterns, and enforcing explicit ACL assignment.

This resource adopts an existing default ACL rather than creating one. Every VPC has a default ACL that Pulumi takes over when you define this resource. When adoption occurs, Pulumi immediately removes all existing rules before applying your configuration. The examples are intentionally small. Combine them with your own VPC and subnet infrastructure.

Adopt the default ACL with AWS default rules

When you create a VPC, AWS automatically creates a default network ACL that allows all traffic. Teams often adopt this ACL 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. The ingress and egress arrays define rules with protocol (-1 means all protocols), ruleNo (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 subnets that can receive traffic but cannot initiate outbound connections, useful for isolated data processing zones.

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 one-way pattern prevents subnets from initiating external connections while still accepting inbound requests.

Lock down the default ACL completely

To enforce explicit network ACL assignment, configure the default ACL to deny all traffic. This forces every subnet to use a custom ACL.

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}

With no ingress or egress rules defined, the default ACL denies all traffic. Subnets that aren’t explicitly assigned to another ACL will be blocked. This pattern prevents accidental exposure from subnets that fall back to the default.

Beyond these examples

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

The examples assume pre-existing infrastructure such as a VPC with 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:

  • Subnet association management (subnetIds)
  • Tags and resource metadata
  • Port-specific rules (examples use protocol -1 for all traffic)
  • IPv6 CIDR blocks (ipv6CidrBlock)

These omissions are intentional: the goal is to illustrate how default ACL adoption and rules are wired, 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 & Adoption
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 network disruption.
What's the difference between this resource and aws.ec2.NetworkAcl?
This resource adopts and manages the VPC’s existing default network ACL, which cannot be destroyed. The aws.ec2.NetworkAcl resource creates a new, custom network ACL that can be deleted.
How do I get the defaultNetworkAclId for my VPC?
Reference the defaultNetworkAclId output from your aws.ec2.Vpc resource, or find it manually via the AWS Console.
Rule Management
Can I use aws.ec2.NetworkAclRule with this resource?
No, this resource is incompatible with aws.ec2.NetworkAclRule. It treats inline rules as absolute, so all rules must be defined within the ingress and egress blocks.
Why do external rule changes show up as drift?
This resource treats inline rules as absolute. Any rules added or removed outside of Pulumi will be detected as drift and corrected on the next update.
How do I deny all traffic in the default network ACL?
Create the resource with only defaultNetworkAclId specified, omitting both ingress and egress blocks entirely.
How do I deny all egress traffic while allowing ingress?
Omit the egress block entirely and define only the ingress rules you need.
Subnet Management
Why do I see perpetual diffs with subnet_ids?
Subnets cannot be removed from the default ACL; they must be reassigned to another ACL or destroyed. Removing subnet_ids from your configuration or deleting a custom ACL (which orphans its subnets back to the default) causes recurring diffs. To resolve, either reassign/destroy the subnets or use lifecycle ignore_changes for subnet_ids.
How do I prevent Pulumi from managing subnet associations?
Use lifecycle ignore_changes for the subnet_ids property if you don’t want to explicitly manage which subnets are associated with the default ACL.
Lifecycle & Deletion
Can I delete the default network ACL?
No, every VPC has a default network ACL that cannot be destroyed. This resource only manages it; you cannot delete it.
What happens if I remove this resource from my configuration?
The resource is removed from your state and Pulumi management, but the network ACL itself remains in AWS with all its current rules and subnet associations intact. You can resume managing it via the AWS Console.

Using a different cloud?

Explore networking guides for other cloud providers: