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 FREEFrequently Asked Questions
Resource Behavior & Adoption
aws.ec2.NetworkAcl resource creates a new, custom network ACL that can be deleted.defaultNetworkAclId output from your aws.ec2.Vpc resource, or find it manually via the AWS Console.Rule Management
aws.ec2.NetworkAclRule. It treats inline rules as absolute, so all rules must be defined within the ingress and egress blocks.defaultNetworkAclId specified, omitting both ingress and egress blocks entirely.egress block entirely and define only the ingress rules you need.Subnet Management
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.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
Using a different cloud?
Explore networking guides for other cloud providers: