Create AWS VPC NAT Gateways

The aws:ec2/natGateway:NatGateway resource, part of the Pulumi AWS provider, provisions NAT gateways that enable outbound internet access from private subnets or route traffic between private subnets. This guide focuses on four capabilities: public NAT for internet access, private NAT for subnet-to-subnet routing, secondary IPs for connection scaling, and regional deployment for multi-AZ availability.

NAT gateways require Elastic IPs (for public NAT), subnets, and an Internet Gateway. Route tables must be configured separately to direct traffic through the gateway. The examples are intentionally small. Combine them with your own VPC infrastructure and routing configuration.

Enable internet access for private subnets

Most VPCs route outbound traffic from private subnets to the internet while keeping instances unreachable from outside. A public NAT gateway provides this one-way connectivity.

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

const example = new aws.ec2.NatGateway("example", {
    allocationId: exampleAwsEip.id,
    subnetId: exampleAwsSubnet.id,
    tags: {
        Name: "gw NAT",
    },
}, {
    dependsOn: [exampleAwsInternetGateway],
});
import pulumi
import pulumi_aws as aws

example = aws.ec2.NatGateway("example",
    allocation_id=example_aws_eip["id"],
    subnet_id=example_aws_subnet["id"],
    tags={
        "Name": "gw NAT",
    },
    opts = pulumi.ResourceOptions(depends_on=[example_aws_internet_gateway]))
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 {
		_, err := ec2.NewNatGateway(ctx, "example", &ec2.NatGatewayArgs{
			AllocationId: pulumi.Any(exampleAwsEip.Id),
			SubnetId:     pulumi.Any(exampleAwsSubnet.Id),
			Tags: pulumi.StringMap{
				"Name": pulumi.String("gw NAT"),
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			exampleAwsInternetGateway,
		}))
		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 example = new Aws.Ec2.NatGateway("example", new()
    {
        AllocationId = exampleAwsEip.Id,
        SubnetId = exampleAwsSubnet.Id,
        Tags = 
        {
            { "Name", "gw NAT" },
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            exampleAwsInternetGateway,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.NatGateway;
import com.pulumi.aws.ec2.NatGatewayArgs;
import com.pulumi.resources.CustomResourceOptions;
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 example = new NatGateway("example", NatGatewayArgs.builder()
            .allocationId(exampleAwsEip.id())
            .subnetId(exampleAwsSubnet.id())
            .tags(Map.of("Name", "gw NAT"))
            .build(), CustomResourceOptions.builder()
                .dependsOn(exampleAwsInternetGateway)
                .build());

    }
}
resources:
  example:
    type: aws:ec2:NatGateway
    properties:
      allocationId: ${exampleAwsEip.id}
      subnetId: ${exampleAwsSubnet.id}
      tags:
        Name: gw NAT
    options:
      dependsOn:
        - ${exampleAwsInternetGateway}

The allocationId property associates an Elastic IP address with the NAT gateway, giving it a public IP for internet-bound traffic. The subnetId must reference a public subnet (one with a route to an Internet Gateway). The dependsOn ensures the Internet Gateway exists before creating the NAT gateway. After creation, update your private subnet route tables to send 0.0.0.0/0 traffic to this NAT gateway.

Route traffic between private subnets

Some architectures need to route traffic between private subnets without internet access, such as connecting application tiers or enabling cross-VPC communication.

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

const example = new aws.ec2.NatGateway("example", {
    connectivityType: "private",
    subnetId: exampleAwsSubnet.id,
});
import pulumi
import pulumi_aws as aws

example = aws.ec2.NatGateway("example",
    connectivity_type="private",
    subnet_id=example_aws_subnet["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 {
		_, err := ec2.NewNatGateway(ctx, "example", &ec2.NatGatewayArgs{
			ConnectivityType: pulumi.String("private"),
			SubnetId:         pulumi.Any(exampleAwsSubnet.Id),
		})
		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 example = new Aws.Ec2.NatGateway("example", new()
    {
        ConnectivityType = "private",
        SubnetId = exampleAwsSubnet.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.NatGateway;
import com.pulumi.aws.ec2.NatGatewayArgs;
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 example = new NatGateway("example", NatGatewayArgs.builder()
            .connectivityType("private")
            .subnetId(exampleAwsSubnet.id())
            .build());

    }
}
resources:
  example:
    type: aws:ec2:NatGateway
    properties:
      connectivityType: private
      subnetId: ${exampleAwsSubnet.id}

The connectivityType property set to “private” creates a NAT gateway without an Elastic IP. This configuration routes traffic between private subnets or to on-premises networks via Transit Gateway, but blocks internet access entirely. Private NAT gateways cost less than public ones since they don’t require EIP allocation.

Add secondary IPs for connection scaling

High-traffic NAT gateways can exhaust the port range of a single IP when handling many concurrent connections to the same destination.

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

const example = new aws.ec2.NatGateway("example", {
    allocationId: exampleAwsEip.id,
    subnetId: exampleAwsSubnet.id,
    secondaryAllocationIds: [secondary.id],
    secondaryPrivateIpAddresses: ["10.0.1.5"],
});
import pulumi
import pulumi_aws as aws

example = aws.ec2.NatGateway("example",
    allocation_id=example_aws_eip["id"],
    subnet_id=example_aws_subnet["id"],
    secondary_allocation_ids=[secondary["id"]],
    secondary_private_ip_addresses=["10.0.1.5"])
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 {
		_, err := ec2.NewNatGateway(ctx, "example", &ec2.NatGatewayArgs{
			AllocationId: pulumi.Any(exampleAwsEip.Id),
			SubnetId:     pulumi.Any(exampleAwsSubnet.Id),
			SecondaryAllocationIds: pulumi.StringArray{
				secondary.Id,
			},
			SecondaryPrivateIpAddresses: pulumi.StringArray{
				pulumi.String("10.0.1.5"),
			},
		})
		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 example = new Aws.Ec2.NatGateway("example", new()
    {
        AllocationId = exampleAwsEip.Id,
        SubnetId = exampleAwsSubnet.Id,
        SecondaryAllocationIds = new[]
        {
            secondary.Id,
        },
        SecondaryPrivateIpAddresses = new[]
        {
            "10.0.1.5",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.NatGateway;
import com.pulumi.aws.ec2.NatGatewayArgs;
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 example = new NatGateway("example", NatGatewayArgs.builder()
            .allocationId(exampleAwsEip.id())
            .subnetId(exampleAwsSubnet.id())
            .secondaryAllocationIds(secondary.id())
            .secondaryPrivateIpAddresses("10.0.1.5")
            .build());

    }
}
resources:
  example:
    type: aws:ec2:NatGateway
    properties:
      allocationId: ${exampleAwsEip.id}
      subnetId: ${exampleAwsSubnet.id}
      secondaryAllocationIds:
        - ${secondary.id}
      secondaryPrivateIpAddresses:
        - 10.0.1.5

The secondaryAllocationIds property adds additional Elastic IPs to the NAT gateway. The secondaryPrivateIpAddresses property assigns specific private IPs from your subnet range. Each additional IP provides 64,512 ports, expanding capacity for concurrent connections. This is useful when many instances connect to the same external endpoint simultaneously.

Deploy multi-AZ NAT with automatic expansion

Regional NAT gateways distribute traffic across multiple availability zones for higher availability. Auto mode automatically expands to new AZs.

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

const available = aws.getAvailabilityZones({});
const example = new aws.ec2.Vpc("example", {cidrBlock: "10.0.0.0/16"});
const exampleInternetGateway = new aws.ec2.InternetGateway("example", {vpcId: example.id});
const exampleNatGateway = new aws.ec2.NatGateway("example", {
    vpcId: example.id,
    availabilityMode: "regional",
});
import pulumi
import pulumi_aws as aws

available = aws.get_availability_zones()
example = aws.ec2.Vpc("example", cidr_block="10.0.0.0/16")
example_internet_gateway = aws.ec2.InternetGateway("example", vpc_id=example.id)
example_nat_gateway = aws.ec2.NatGateway("example",
    vpc_id=example.id,
    availability_mode="regional")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
	"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 {
		_, err := aws.GetAvailabilityZones(ctx, &aws.GetAvailabilityZonesArgs{}, nil)
		if err != nil {
			return err
		}
		example, err := ec2.NewVpc(ctx, "example", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.0.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewInternetGateway(ctx, "example", &ec2.InternetGatewayArgs{
			VpcId: example.ID(),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewNatGateway(ctx, "example", &ec2.NatGatewayArgs{
			VpcId:            example.ID(),
			AvailabilityMode: pulumi.String("regional"),
		})
		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 available = Aws.GetAvailabilityZones.Invoke();

    var example = new Aws.Ec2.Vpc("example", new()
    {
        CidrBlock = "10.0.0.0/16",
    });

    var exampleInternetGateway = new Aws.Ec2.InternetGateway("example", new()
    {
        VpcId = example.Id,
    });

    var exampleNatGateway = new Aws.Ec2.NatGateway("example", new()
    {
        VpcId = example.Id,
        AvailabilityMode = "regional",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetAvailabilityZonesArgs;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.InternetGateway;
import com.pulumi.aws.ec2.InternetGatewayArgs;
import com.pulumi.aws.ec2.NatGateway;
import com.pulumi.aws.ec2.NatGatewayArgs;
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) {
        final var available = AwsFunctions.getAvailabilityZones(GetAvailabilityZonesArgs.builder()
            .build());

        var example = new Vpc("example", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build());

        var exampleInternetGateway = new InternetGateway("exampleInternetGateway", InternetGatewayArgs.builder()
            .vpcId(example.id())
            .build());

        var exampleNatGateway = new NatGateway("exampleNatGateway", NatGatewayArgs.builder()
            .vpcId(example.id())
            .availabilityMode("regional")
            .build());

    }
}
resources:
  example:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.0.0.0/16
  exampleInternetGateway:
    type: aws:ec2:InternetGateway
    name: example
    properties:
      vpcId: ${example.id}
  exampleNatGateway:
    type: aws:ec2:NatGateway
    name: example
    properties:
      vpcId: ${example.id}
      availabilityMode: regional
variables:
  available:
    fn::invoke:
      function: aws:getAvailabilityZones
      arguments: {}

The availabilityMode property set to “regional” creates a multi-AZ NAT gateway. The vpcId property specifies where to deploy. Without availabilityZoneAddresses specified, the gateway operates in auto mode: AWS automatically expands to new AZs and allocates EIPs when it detects network interfaces. Regional NAT gateways must use connectivityType “public”.

Control EIP allocation per availability zone

When you need explicit control over which EIPs are used in each AZ, manual mode lets you specify the configuration.

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

const available = aws.getAvailabilityZones({});
const example = new aws.ec2.Vpc("example", {cidrBlock: "10.0.0.0/16"});
const exampleInternetGateway = new aws.ec2.InternetGateway("example", {vpcId: example.id});
const exampleEip: aws.ec2.Eip[] = [];
for (const range = {value: 0}; range.value < 3; range.value++) {
    exampleEip.push(new aws.ec2.Eip(`example-${range.value}`, {domain: "vpc"}));
}
const exampleNatGateway = new aws.ec2.NatGateway("example", {
    vpcId: example.id,
    availabilityMode: "regional",
    availabilityZoneAddresses: [
        {
            allocationIds: [exampleEip[0].id],
            availabilityZone: available.then(available => available.names?.[0]),
        },
        {
            allocationIds: [
                exampleEip[1].id,
                exampleEip[2].id,
            ],
            availabilityZone: available.then(available => available.names?.[1]),
        },
    ],
});
import pulumi
import pulumi_aws as aws

available = aws.get_availability_zones()
example = aws.ec2.Vpc("example", cidr_block="10.0.0.0/16")
example_internet_gateway = aws.ec2.InternetGateway("example", vpc_id=example.id)
example_eip = []
for range in [{"value": i} for i in range(0, 3)]:
    example_eip.append(aws.ec2.Eip(f"example-{range['value']}", domain="vpc"))
example_nat_gateway = aws.ec2.NatGateway("example",
    vpc_id=example.id,
    availability_mode="regional",
    availability_zone_addresses=[
        {
            "allocation_ids": [example_eip[0].id],
            "availability_zone": available.names[0],
        },
        {
            "allocation_ids": [
                example_eip[1].id,
                example_eip[2].id,
            ],
            "availability_zone": available.names[1],
        },
    ])
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
	"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 {
		available, err := aws.GetAvailabilityZones(ctx, &aws.GetAvailabilityZonesArgs{}, nil)
		if err != nil {
			return err
		}
		example, err := ec2.NewVpc(ctx, "example", &ec2.VpcArgs{
			CidrBlock: pulumi.String("10.0.0.0/16"),
		})
		if err != nil {
			return err
		}
		_, err = ec2.NewInternetGateway(ctx, "example", &ec2.InternetGatewayArgs{
			VpcId: example.ID(),
		})
		if err != nil {
			return err
		}
		var exampleEip []*ec2.Eip
		for index := 0; index < 3; index++ {
			key0 := index
			_ := index
			__res, err := ec2.NewEip(ctx, fmt.Sprintf("example-%v", key0), &ec2.EipArgs{
				Domain: pulumi.String("vpc"),
			})
			if err != nil {
				return err
			}
			exampleEip = append(exampleEip, __res)
		}
		_, err = ec2.NewNatGateway(ctx, "example", &ec2.NatGatewayArgs{
			VpcId:            example.ID(),
			AvailabilityMode: pulumi.String("regional"),
			AvailabilityZoneAddresses: ec2.NatGatewayAvailabilityZoneAddressArray{
				&ec2.NatGatewayAvailabilityZoneAddressArgs{
					AllocationIds: pulumi.StringArray{
						exampleEip[0].ID(),
					},
					AvailabilityZone: pulumi.String(available.Names[0]),
				},
				&ec2.NatGatewayAvailabilityZoneAddressArgs{
					AllocationIds: pulumi.StringArray{
						exampleEip[1].ID(),
						exampleEip[2].ID(),
					},
					AvailabilityZone: pulumi.String(available.Names[1]),
				},
			},
		})
		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 available = Aws.GetAvailabilityZones.Invoke();

    var example = new Aws.Ec2.Vpc("example", new()
    {
        CidrBlock = "10.0.0.0/16",
    });

    var exampleInternetGateway = new Aws.Ec2.InternetGateway("example", new()
    {
        VpcId = example.Id,
    });

    var exampleEip = new List<Aws.Ec2.Eip>();
    for (var rangeIndex = 0; rangeIndex < 3; rangeIndex++)
    {
        var range = new { Value = rangeIndex };
        exampleEip.Add(new Aws.Ec2.Eip($"example-{range.Value}", new()
        {
            Domain = "vpc",
        }));
    }
    var exampleNatGateway = new Aws.Ec2.NatGateway("example", new()
    {
        VpcId = example.Id,
        AvailabilityMode = "regional",
        AvailabilityZoneAddresses = new[]
        {
            new Aws.Ec2.Inputs.NatGatewayAvailabilityZoneAddressArgs
            {
                AllocationIds = new[]
                {
                    exampleEip[0].Id,
                },
                AvailabilityZone = available.Apply(getAvailabilityZonesResult => getAvailabilityZonesResult.Names[0]),
            },
            new Aws.Ec2.Inputs.NatGatewayAvailabilityZoneAddressArgs
            {
                AllocationIds = new[]
                {
                    exampleEip[1].Id,
                    exampleEip[2].Id,
                },
                AvailabilityZone = available.Apply(getAvailabilityZonesResult => getAvailabilityZonesResult.Names[1]),
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetAvailabilityZonesArgs;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.ec2.InternetGateway;
import com.pulumi.aws.ec2.InternetGatewayArgs;
import com.pulumi.aws.ec2.Eip;
import com.pulumi.aws.ec2.EipArgs;
import com.pulumi.aws.ec2.NatGateway;
import com.pulumi.aws.ec2.NatGatewayArgs;
import com.pulumi.aws.ec2.inputs.NatGatewayAvailabilityZoneAddressArgs;
import com.pulumi.codegen.internal.KeyedValue;
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) {
        final var available = AwsFunctions.getAvailabilityZones(GetAvailabilityZonesArgs.builder()
            .build());

        var example = new Vpc("example", VpcArgs.builder()
            .cidrBlock("10.0.0.0/16")
            .build());

        var exampleInternetGateway = new InternetGateway("exampleInternetGateway", InternetGatewayArgs.builder()
            .vpcId(example.id())
            .build());

        for (var i = 0; i < 3; i++) {
            new Eip("exampleEip-" + i, EipArgs.builder()
                .domain("vpc")
                .build());

        
}
        var exampleNatGateway = new NatGateway("exampleNatGateway", NatGatewayArgs.builder()
            .vpcId(example.id())
            .availabilityMode("regional")
            .availabilityZoneAddresses(            
                NatGatewayAvailabilityZoneAddressArgs.builder()
                    .allocationIds(exampleEip[0].id())
                    .availabilityZone(available.names()[0])
                    .build(),
                NatGatewayAvailabilityZoneAddressArgs.builder()
                    .allocationIds(                    
                        exampleEip[1].id(),
                        exampleEip[2].id())
                    .availabilityZone(available.names()[1])
                    .build())
            .build());

    }
}
resources:
  example:
    type: aws:ec2:Vpc
    properties:
      cidrBlock: 10.0.0.0/16
  exampleInternetGateway:
    type: aws:ec2:InternetGateway
    name: example
    properties:
      vpcId: ${example.id}
  exampleEip:
    type: aws:ec2:Eip
    name: example
    properties:
      domain: vpc
    options: {}
  exampleNatGateway:
    type: aws:ec2:NatGateway
    name: example
    properties:
      vpcId: ${example.id}
      availabilityMode: regional
      availabilityZoneAddresses:
        - allocationIds:
            - ${exampleEip[0].id}
          availabilityZone: ${available.names[0]}
        - allocationIds:
            - ${exampleEip[1].id}
            - ${exampleEip[2].id}
          availabilityZone: ${available.names[1]}
variables:
  available:
    fn::invoke:
      function: aws:getAvailabilityZones
      arguments: {}

The availabilityZoneAddresses property disables automatic expansion and gives you explicit control. Each block specifies an availability zone and the EIP allocation IDs to use there. You can assign multiple EIPs per AZ for additional capacity. This example creates three EIPs and distributes them across two AZs: one EIP in the first AZ, two in the second.

Beyond these examples

These snippets focus on specific NAT gateway features: public and private connectivity types, secondary IP addresses for connection scaling, and regional deployment with auto and manual modes. They’re intentionally minimal rather than full VPC configurations.

The examples reference pre-existing infrastructure such as VPC with public and private subnets, Elastic IP addresses, and Internet Gateway (for public NAT). They focus on configuring the NAT gateway rather than provisioning the surrounding network.

To keep things focused, common NAT gateway patterns are omitted, including:

  • Route table configuration to direct traffic through NAT
  • Private IP address assignment (privateIp)
  • Secondary private IP count (secondaryPrivateIpAddressCount)
  • Tags and resource naming

These omissions are intentional: the goal is to illustrate how each NAT gateway feature is wired, not provide drop-in networking modules. See the NAT Gateway resource reference for all available configuration options.

Let's create AWS VPC NAT Gateways

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration Errors & Conflicts
Why am I seeing perpetual differences when using secondary IP addresses?
Don’t use secondaryAllocationIds on the NAT Gateway resource together with separate aws.ec2.NatGatewayEipAssociation resources. This combination causes perpetual differences and overwrites associations. Choose one approach: manage secondary allocations directly via secondaryAllocationIds, or use separate association resources.
Zonal vs Regional NAT Gateways
What are the configuration differences between zonal and regional NAT gateways?
Zonal NAT gateways (default) require subnetId and allocationId (for public connectivity). Regional NAT gateways require vpcId instead, and must use connectivityType: public. For regional gateways, use availabilityZoneAddresses to specify EIPs per AZ rather than a single allocationId.
What's the difference between auto and manual mode for regional NAT gateways?

Regional NAT gateways support two modes:

  1. Auto mode (default): Omit availabilityZoneAddresses. AWS automatically expands to new AZs and allocates EIPs when it detects elastic network interfaces.
  2. Manual mode: Specify availabilityZoneAddresses with explicit EIPs for each AZ. This disables auto-expansion.
Can I create a private regional NAT gateway?
No. When availabilityMode is set to regional, connectivityType must be public.
Public vs Private NAT Gateways
How do I create a public NAT gateway?
For a zonal public NAT gateway, provide allocationId (Elastic IP) and subnetId. The example shows using dependsOn to ensure the Internet Gateway exists first.
How do I create a private NAT gateway?
Set connectivityType to private and provide subnetId. Private NAT gateways don’t require an Elastic IP allocation.
Secondary IP Addresses
How do I configure secondary IP addresses for a NAT gateway?

You have three options:

  1. Secondary EIPs: Use secondaryAllocationIds with a list of EIP allocation IDs
  2. Specific private IPs: Use secondaryPrivateIpAddresses with explicit IP addresses
  3. Auto-assigned private IPs: Use secondaryPrivateIpAddressCount to specify how many

To remove all secondary addresses, pass an empty list to the respective property.

Immutability & Dependencies
What properties can't be changed after creating a NAT gateway?
These properties are immutable: availabilityMode, connectivityType, allocationId, subnetId, vpcId, and privateIp. Changing any of these requires replacing the NAT gateway.
Why should I use dependsOn with an Internet Gateway?
The public NAT gateway example shows dependsOn for the Internet Gateway. This ensures the IGW exists before creating the NAT gateway, preventing dependency errors.

Using a different cloud?

Explore networking guides for other cloud providers: