Configure GCP Cloud Router NAT

The gcp:compute/routerNat:RouterNat resource, part of the Pulumi GCP provider, configures Cloud NAT on a Cloud Router to translate private IP addresses for outbound connectivity. This guide focuses on three capabilities: automatic IP allocation for internet access, manual IP allocation with traffic-based rules, and private NAT for inter-VPC communication.

Cloud NAT configurations attach to Cloud Routers and reference VPC networks and subnets. Private NAT additionally requires Network Connectivity Center hubs. The examples are intentionally small. Combine them with your own network topology and routing requirements.

Enable outbound internet access with automatic IP allocation

Most deployments allow private VMs to reach the internet without public IPs, letting Google Cloud manage external addresses automatically.

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

const net = new gcp.compute.Network("net", {name: "my-network"});
const subnet = new gcp.compute.Subnetwork("subnet", {
    name: "my-subnetwork",
    network: net.id,
    ipCidrRange: "10.0.0.0/16",
    region: "us-central1",
});
const router = new gcp.compute.Router("router", {
    name: "my-router",
    region: subnet.region,
    network: net.id,
    bgp: {
        asn: 64514,
    },
});
const nat = new gcp.compute.RouterNat("nat", {
    name: "my-router-nat",
    router: router.name,
    region: router.region,
    natIpAllocateOption: "AUTO_ONLY",
    sourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_IP_RANGES",
    logConfig: {
        enable: true,
        filter: "ERRORS_ONLY",
    },
});
import pulumi
import pulumi_gcp as gcp

net = gcp.compute.Network("net", name="my-network")
subnet = gcp.compute.Subnetwork("subnet",
    name="my-subnetwork",
    network=net.id,
    ip_cidr_range="10.0.0.0/16",
    region="us-central1")
router = gcp.compute.Router("router",
    name="my-router",
    region=subnet.region,
    network=net.id,
    bgp={
        "asn": 64514,
    })
nat = gcp.compute.RouterNat("nat",
    name="my-router-nat",
    router=router.name,
    region=router.region,
    nat_ip_allocate_option="AUTO_ONLY",
    source_subnetwork_ip_ranges_to_nat="ALL_SUBNETWORKS_ALL_IP_RANGES",
    log_config={
        "enable": True,
        "filter": "ERRORS_ONLY",
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		net, err := compute.NewNetwork(ctx, "net", &compute.NetworkArgs{
			Name: pulumi.String("my-network"),
		})
		if err != nil {
			return err
		}
		subnet, err := compute.NewSubnetwork(ctx, "subnet", &compute.SubnetworkArgs{
			Name:        pulumi.String("my-subnetwork"),
			Network:     net.ID(),
			IpCidrRange: pulumi.String("10.0.0.0/16"),
			Region:      pulumi.String("us-central1"),
		})
		if err != nil {
			return err
		}
		router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
			Name:    pulumi.String("my-router"),
			Region:  subnet.Region,
			Network: net.ID(),
			Bgp: &compute.RouterBgpArgs{
				Asn: pulumi.Int(64514),
			},
		})
		if err != nil {
			return err
		}
		_, err = compute.NewRouterNat(ctx, "nat", &compute.RouterNatArgs{
			Name:                          pulumi.String("my-router-nat"),
			Router:                        router.Name,
			Region:                        router.Region,
			NatIpAllocateOption:           pulumi.String("AUTO_ONLY"),
			SourceSubnetworkIpRangesToNat: pulumi.String("ALL_SUBNETWORKS_ALL_IP_RANGES"),
			LogConfig: &compute.RouterNatLogConfigArgs{
				Enable: pulumi.Bool(true),
				Filter: pulumi.String("ERRORS_ONLY"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var net = new Gcp.Compute.Network("net", new()
    {
        Name = "my-network",
    });

    var subnet = new Gcp.Compute.Subnetwork("subnet", new()
    {
        Name = "my-subnetwork",
        Network = net.Id,
        IpCidrRange = "10.0.0.0/16",
        Region = "us-central1",
    });

    var router = new Gcp.Compute.Router("router", new()
    {
        Name = "my-router",
        Region = subnet.Region,
        Network = net.Id,
        Bgp = new Gcp.Compute.Inputs.RouterBgpArgs
        {
            Asn = 64514,
        },
    });

    var nat = new Gcp.Compute.RouterNat("nat", new()
    {
        Name = "my-router-nat",
        Router = router.Name,
        Region = router.Region,
        NatIpAllocateOption = "AUTO_ONLY",
        SourceSubnetworkIpRangesToNat = "ALL_SUBNETWORKS_ALL_IP_RANGES",
        LogConfig = new Gcp.Compute.Inputs.RouterNatLogConfigArgs
        {
            Enable = true,
            Filter = "ERRORS_ONLY",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.compute.inputs.RouterBgpArgs;
import com.pulumi.gcp.compute.RouterNat;
import com.pulumi.gcp.compute.RouterNatArgs;
import com.pulumi.gcp.compute.inputs.RouterNatLogConfigArgs;
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 net = new Network("net", NetworkArgs.builder()
            .name("my-network")
            .build());

        var subnet = new Subnetwork("subnet", SubnetworkArgs.builder()
            .name("my-subnetwork")
            .network(net.id())
            .ipCidrRange("10.0.0.0/16")
            .region("us-central1")
            .build());

        var router = new Router("router", RouterArgs.builder()
            .name("my-router")
            .region(subnet.region())
            .network(net.id())
            .bgp(RouterBgpArgs.builder()
                .asn(64514)
                .build())
            .build());

        var nat = new RouterNat("nat", RouterNatArgs.builder()
            .name("my-router-nat")
            .router(router.name())
            .region(router.region())
            .natIpAllocateOption("AUTO_ONLY")
            .sourceSubnetworkIpRangesToNat("ALL_SUBNETWORKS_ALL_IP_RANGES")
            .logConfig(RouterNatLogConfigArgs.builder()
                .enable(true)
                .filter("ERRORS_ONLY")
                .build())
            .build());

    }
}
resources:
  net:
    type: gcp:compute:Network
    properties:
      name: my-network
  subnet:
    type: gcp:compute:Subnetwork
    properties:
      name: my-subnetwork
      network: ${net.id}
      ipCidrRange: 10.0.0.0/16
      region: us-central1
  router:
    type: gcp:compute:Router
    properties:
      name: my-router
      region: ${subnet.region}
      network: ${net.id}
      bgp:
        asn: 64514
  nat:
    type: gcp:compute:RouterNat
    properties:
      name: my-router-nat
      router: ${router.name}
      region: ${router.region}
      natIpAllocateOption: AUTO_ONLY
      sourceSubnetworkIpRangesToNat: ALL_SUBNETWORKS_ALL_IP_RANGES
      logConfig:
        enable: true
        filter: ERRORS_ONLY

When natIpAllocateOption is set to AUTO_ONLY, Google Cloud allocates and manages external IPs. The sourceSubnetworkIpRangesToNat property controls which subnets can use NAT; ALL_SUBNETWORKS_ALL_IP_RANGES allows all subnets in the network. The logConfig block enables Cloud Logging for troubleshooting connection issues.

Route traffic through specific IPs with NAT rules

Applications needing predictable source IPs for allowlisting can use manual allocation with rules that match destination patterns.

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

const net = new gcp.compute.Network("net", {
    name: "my-network",
    autoCreateSubnetworks: false,
});
const subnet = new gcp.compute.Subnetwork("subnet", {
    name: "my-subnetwork",
    network: net.id,
    ipCidrRange: "10.0.0.0/16",
    region: "us-central1",
});
const router = new gcp.compute.Router("router", {
    name: "my-router",
    region: subnet.region,
    network: net.id,
});
const addr1 = new gcp.compute.Address("addr1", {
    name: "nat-address1",
    region: subnet.region,
});
const addr2 = new gcp.compute.Address("addr2", {
    name: "nat-address2",
    region: subnet.region,
});
const addr3 = new gcp.compute.Address("addr3", {
    name: "nat-address3",
    region: subnet.region,
});
const natRules = new gcp.compute.RouterNat("nat_rules", {
    name: "my-router-nat",
    router: router.name,
    region: router.region,
    natIpAllocateOption: "MANUAL_ONLY",
    natIps: [addr1.selfLink],
    sourceSubnetworkIpRangesToNat: "LIST_OF_SUBNETWORKS",
    subnetworks: [{
        name: subnet.id,
        sourceIpRangesToNats: ["ALL_IP_RANGES"],
    }],
    rules: [{
        ruleNumber: 100,
        description: "nat rules example",
        match: "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')",
        action: {
            sourceNatActiveIps: [
                addr2.selfLink,
                addr3.selfLink,
            ],
        },
    }],
    enableEndpointIndependentMapping: false,
});
import pulumi
import pulumi_gcp as gcp

net = gcp.compute.Network("net",
    name="my-network",
    auto_create_subnetworks=False)
subnet = gcp.compute.Subnetwork("subnet",
    name="my-subnetwork",
    network=net.id,
    ip_cidr_range="10.0.0.0/16",
    region="us-central1")
router = gcp.compute.Router("router",
    name="my-router",
    region=subnet.region,
    network=net.id)
addr1 = gcp.compute.Address("addr1",
    name="nat-address1",
    region=subnet.region)
addr2 = gcp.compute.Address("addr2",
    name="nat-address2",
    region=subnet.region)
addr3 = gcp.compute.Address("addr3",
    name="nat-address3",
    region=subnet.region)
nat_rules = gcp.compute.RouterNat("nat_rules",
    name="my-router-nat",
    router=router.name,
    region=router.region,
    nat_ip_allocate_option="MANUAL_ONLY",
    nat_ips=[addr1.self_link],
    source_subnetwork_ip_ranges_to_nat="LIST_OF_SUBNETWORKS",
    subnetworks=[{
        "name": subnet.id,
        "source_ip_ranges_to_nats": ["ALL_IP_RANGES"],
    }],
    rules=[{
        "rule_number": 100,
        "description": "nat rules example",
        "match": "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')",
        "action": {
            "source_nat_active_ips": [
                addr2.self_link,
                addr3.self_link,
            ],
        },
    }],
    enable_endpoint_independent_mapping=False)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		net, err := compute.NewNetwork(ctx, "net", &compute.NetworkArgs{
			Name:                  pulumi.String("my-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		subnet, err := compute.NewSubnetwork(ctx, "subnet", &compute.SubnetworkArgs{
			Name:        pulumi.String("my-subnetwork"),
			Network:     net.ID(),
			IpCidrRange: pulumi.String("10.0.0.0/16"),
			Region:      pulumi.String("us-central1"),
		})
		if err != nil {
			return err
		}
		router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
			Name:    pulumi.String("my-router"),
			Region:  subnet.Region,
			Network: net.ID(),
		})
		if err != nil {
			return err
		}
		addr1, err := compute.NewAddress(ctx, "addr1", &compute.AddressArgs{
			Name:   pulumi.String("nat-address1"),
			Region: subnet.Region,
		})
		if err != nil {
			return err
		}
		addr2, err := compute.NewAddress(ctx, "addr2", &compute.AddressArgs{
			Name:   pulumi.String("nat-address2"),
			Region: subnet.Region,
		})
		if err != nil {
			return err
		}
		addr3, err := compute.NewAddress(ctx, "addr3", &compute.AddressArgs{
			Name:   pulumi.String("nat-address3"),
			Region: subnet.Region,
		})
		if err != nil {
			return err
		}
		_, err = compute.NewRouterNat(ctx, "nat_rules", &compute.RouterNatArgs{
			Name:                pulumi.String("my-router-nat"),
			Router:              router.Name,
			Region:              router.Region,
			NatIpAllocateOption: pulumi.String("MANUAL_ONLY"),
			NatIps: pulumi.StringArray{
				addr1.SelfLink,
			},
			SourceSubnetworkIpRangesToNat: pulumi.String("LIST_OF_SUBNETWORKS"),
			Subnetworks: compute.RouterNatSubnetworkArray{
				&compute.RouterNatSubnetworkArgs{
					Name: subnet.ID(),
					SourceIpRangesToNats: pulumi.StringArray{
						pulumi.String("ALL_IP_RANGES"),
					},
				},
			},
			Rules: compute.RouterNatRuleArray{
				&compute.RouterNatRuleArgs{
					RuleNumber:  pulumi.Int(100),
					Description: pulumi.String("nat rules example"),
					Match:       pulumi.String("inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')"),
					Action: &compute.RouterNatRuleActionArgs{
						SourceNatActiveIps: pulumi.StringArray{
							addr2.SelfLink,
							addr3.SelfLink,
						},
					},
				},
			},
			EnableEndpointIndependentMapping: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var net = new Gcp.Compute.Network("net", new()
    {
        Name = "my-network",
        AutoCreateSubnetworks = false,
    });

    var subnet = new Gcp.Compute.Subnetwork("subnet", new()
    {
        Name = "my-subnetwork",
        Network = net.Id,
        IpCidrRange = "10.0.0.0/16",
        Region = "us-central1",
    });

    var router = new Gcp.Compute.Router("router", new()
    {
        Name = "my-router",
        Region = subnet.Region,
        Network = net.Id,
    });

    var addr1 = new Gcp.Compute.Address("addr1", new()
    {
        Name = "nat-address1",
        Region = subnet.Region,
    });

    var addr2 = new Gcp.Compute.Address("addr2", new()
    {
        Name = "nat-address2",
        Region = subnet.Region,
    });

    var addr3 = new Gcp.Compute.Address("addr3", new()
    {
        Name = "nat-address3",
        Region = subnet.Region,
    });

    var natRules = new Gcp.Compute.RouterNat("nat_rules", new()
    {
        Name = "my-router-nat",
        Router = router.Name,
        Region = router.Region,
        NatIpAllocateOption = "MANUAL_ONLY",
        NatIps = new[]
        {
            addr1.SelfLink,
        },
        SourceSubnetworkIpRangesToNat = "LIST_OF_SUBNETWORKS",
        Subnetworks = new[]
        {
            new Gcp.Compute.Inputs.RouterNatSubnetworkArgs
            {
                Name = subnet.Id,
                SourceIpRangesToNats = new[]
                {
                    "ALL_IP_RANGES",
                },
            },
        },
        Rules = new[]
        {
            new Gcp.Compute.Inputs.RouterNatRuleArgs
            {
                RuleNumber = 100,
                Description = "nat rules example",
                Match = "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')",
                Action = new Gcp.Compute.Inputs.RouterNatRuleActionArgs
                {
                    SourceNatActiveIps = new[]
                    {
                        addr2.SelfLink,
                        addr3.SelfLink,
                    },
                },
            },
        },
        EnableEndpointIndependentMapping = false,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.compute.Address;
import com.pulumi.gcp.compute.AddressArgs;
import com.pulumi.gcp.compute.RouterNat;
import com.pulumi.gcp.compute.RouterNatArgs;
import com.pulumi.gcp.compute.inputs.RouterNatSubnetworkArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleActionArgs;
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 net = new Network("net", NetworkArgs.builder()
            .name("my-network")
            .autoCreateSubnetworks(false)
            .build());

        var subnet = new Subnetwork("subnet", SubnetworkArgs.builder()
            .name("my-subnetwork")
            .network(net.id())
            .ipCidrRange("10.0.0.0/16")
            .region("us-central1")
            .build());

        var router = new Router("router", RouterArgs.builder()
            .name("my-router")
            .region(subnet.region())
            .network(net.id())
            .build());

        var addr1 = new Address("addr1", AddressArgs.builder()
            .name("nat-address1")
            .region(subnet.region())
            .build());

        var addr2 = new Address("addr2", AddressArgs.builder()
            .name("nat-address2")
            .region(subnet.region())
            .build());

        var addr3 = new Address("addr3", AddressArgs.builder()
            .name("nat-address3")
            .region(subnet.region())
            .build());

        var natRules = new RouterNat("natRules", RouterNatArgs.builder()
            .name("my-router-nat")
            .router(router.name())
            .region(router.region())
            .natIpAllocateOption("MANUAL_ONLY")
            .natIps(addr1.selfLink())
            .sourceSubnetworkIpRangesToNat("LIST_OF_SUBNETWORKS")
            .subnetworks(RouterNatSubnetworkArgs.builder()
                .name(subnet.id())
                .sourceIpRangesToNats("ALL_IP_RANGES")
                .build())
            .rules(RouterNatRuleArgs.builder()
                .ruleNumber(100)
                .description("nat rules example")
                .match("inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')")
                .action(RouterNatRuleActionArgs.builder()
                    .sourceNatActiveIps(                    
                        addr2.selfLink(),
                        addr3.selfLink())
                    .build())
                .build())
            .enableEndpointIndependentMapping(false)
            .build());

    }
}
resources:
  net:
    type: gcp:compute:Network
    properties:
      name: my-network
      autoCreateSubnetworks: false
  subnet:
    type: gcp:compute:Subnetwork
    properties:
      name: my-subnetwork
      network: ${net.id}
      ipCidrRange: 10.0.0.0/16
      region: us-central1
  router:
    type: gcp:compute:Router
    properties:
      name: my-router
      region: ${subnet.region}
      network: ${net.id}
  addr1:
    type: gcp:compute:Address
    properties:
      name: nat-address1
      region: ${subnet.region}
  addr2:
    type: gcp:compute:Address
    properties:
      name: nat-address2
      region: ${subnet.region}
  addr3:
    type: gcp:compute:Address
    properties:
      name: nat-address3
      region: ${subnet.region}
  natRules:
    type: gcp:compute:RouterNat
    name: nat_rules
    properties:
      name: my-router-nat
      router: ${router.name}
      region: ${router.region}
      natIpAllocateOption: MANUAL_ONLY
      natIps:
        - ${addr1.selfLink}
      sourceSubnetworkIpRangesToNat: LIST_OF_SUBNETWORKS
      subnetworks:
        - name: ${subnet.id}
          sourceIpRangesToNats:
            - ALL_IP_RANGES
      rules:
        - ruleNumber: 100
          description: nat rules example
          match: inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')
          action:
            sourceNatActiveIps:
              - ${addr2.selfLink}
              - ${addr3.selfLink}
      enableEndpointIndependentMapping: false

With MANUAL_ONLY allocation, you provide specific external IPs via the natIps property. The rules array defines traffic matching: the match expression uses destination IP ranges, and the action specifies which IPs to use for that traffic. The subnetworks property limits NAT to specific subnets when sourceSubnetworkIpRangesToNat is LIST_OF_SUBNETWORKS.

Translate private IPs for inter-VPC communication

Private NAT enables communication between VPCs with overlapping IP ranges by translating addresses at network boundaries.

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

const net = new gcp.compute.Network("net", {name: "my-network"});
const subnet = new gcp.compute.Subnetwork("subnet", {
    name: "my-subnetwork",
    network: net.id,
    ipCidrRange: "10.0.0.0/16",
    region: "us-central1",
    purpose: "PRIVATE_NAT",
});
const router = new gcp.compute.Router("router", {
    name: "my-router",
    region: subnet.region,
    network: net.id,
});
const hub = new gcp.networkconnectivity.Hub("hub", {
    name: "my-hub",
    description: "vpc hub for inter vpc nat",
});
const spoke = new gcp.networkconnectivity.Spoke("spoke", {
    name: "my-spoke",
    location: "global",
    description: "vpc spoke for inter vpc nat",
    hub: hub.id,
    linkedVpcNetwork: {
        excludeExportRanges: [
            "198.51.100.0/24",
            "10.10.0.0/16",
        ],
        uri: net.selfLink,
    },
});
const natType = new gcp.compute.RouterNat("nat_type", {
    name: "my-router-nat",
    router: router.name,
    region: router.region,
    sourceSubnetworkIpRangesToNat: "LIST_OF_SUBNETWORKS",
    enableDynamicPortAllocation: false,
    enableEndpointIndependentMapping: false,
    minPortsPerVm: 32,
    type: "PRIVATE",
    subnetworks: [{
        name: subnet.id,
        sourceIpRangesToNats: ["ALL_IP_RANGES"],
    }],
    rules: [{
        ruleNumber: 100,
        description: "rule for private nat",
        match: "nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"",
        action: {
            sourceNatActiveRanges: [subnet.selfLink],
        },
    }],
});
import pulumi
import pulumi_gcp as gcp

net = gcp.compute.Network("net", name="my-network")
subnet = gcp.compute.Subnetwork("subnet",
    name="my-subnetwork",
    network=net.id,
    ip_cidr_range="10.0.0.0/16",
    region="us-central1",
    purpose="PRIVATE_NAT")
router = gcp.compute.Router("router",
    name="my-router",
    region=subnet.region,
    network=net.id)
hub = gcp.networkconnectivity.Hub("hub",
    name="my-hub",
    description="vpc hub for inter vpc nat")
spoke = gcp.networkconnectivity.Spoke("spoke",
    name="my-spoke",
    location="global",
    description="vpc spoke for inter vpc nat",
    hub=hub.id,
    linked_vpc_network={
        "exclude_export_ranges": [
            "198.51.100.0/24",
            "10.10.0.0/16",
        ],
        "uri": net.self_link,
    })
nat_type = gcp.compute.RouterNat("nat_type",
    name="my-router-nat",
    router=router.name,
    region=router.region,
    source_subnetwork_ip_ranges_to_nat="LIST_OF_SUBNETWORKS",
    enable_dynamic_port_allocation=False,
    enable_endpoint_independent_mapping=False,
    min_ports_per_vm=32,
    type="PRIVATE",
    subnetworks=[{
        "name": subnet.id,
        "source_ip_ranges_to_nats": ["ALL_IP_RANGES"],
    }],
    rules=[{
        "rule_number": 100,
        "description": "rule for private nat",
        "match": "nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"",
        "action": {
            "source_nat_active_ranges": [subnet.self_link],
        },
    }])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		net, err := compute.NewNetwork(ctx, "net", &compute.NetworkArgs{
			Name: pulumi.String("my-network"),
		})
		if err != nil {
			return err
		}
		subnet, err := compute.NewSubnetwork(ctx, "subnet", &compute.SubnetworkArgs{
			Name:        pulumi.String("my-subnetwork"),
			Network:     net.ID(),
			IpCidrRange: pulumi.String("10.0.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Purpose:     pulumi.String("PRIVATE_NAT"),
		})
		if err != nil {
			return err
		}
		router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
			Name:    pulumi.String("my-router"),
			Region:  subnet.Region,
			Network: net.ID(),
		})
		if err != nil {
			return err
		}
		hub, err := networkconnectivity.NewHub(ctx, "hub", &networkconnectivity.HubArgs{
			Name:        pulumi.String("my-hub"),
			Description: pulumi.String("vpc hub for inter vpc nat"),
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "spoke", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("my-spoke"),
			Location:    pulumi.String("global"),
			Description: pulumi.String("vpc spoke for inter vpc nat"),
			Hub:         hub.ID(),
			LinkedVpcNetwork: &networkconnectivity.SpokeLinkedVpcNetworkArgs{
				ExcludeExportRanges: pulumi.StringArray{
					pulumi.String("198.51.100.0/24"),
					pulumi.String("10.10.0.0/16"),
				},
				Uri: net.SelfLink,
			},
		})
		if err != nil {
			return err
		}
		_, err = compute.NewRouterNat(ctx, "nat_type", &compute.RouterNatArgs{
			Name:                             pulumi.String("my-router-nat"),
			Router:                           router.Name,
			Region:                           router.Region,
			SourceSubnetworkIpRangesToNat:    pulumi.String("LIST_OF_SUBNETWORKS"),
			EnableDynamicPortAllocation:      pulumi.Bool(false),
			EnableEndpointIndependentMapping: pulumi.Bool(false),
			MinPortsPerVm:                    pulumi.Int(32),
			Type:                             pulumi.String("PRIVATE"),
			Subnetworks: compute.RouterNatSubnetworkArray{
				&compute.RouterNatSubnetworkArgs{
					Name: subnet.ID(),
					SourceIpRangesToNats: pulumi.StringArray{
						pulumi.String("ALL_IP_RANGES"),
					},
				},
			},
			Rules: compute.RouterNatRuleArray{
				&compute.RouterNatRuleArgs{
					RuleNumber:  pulumi.Int(100),
					Description: pulumi.String("rule for private nat"),
					Match:       pulumi.String("nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\""),
					Action: &compute.RouterNatRuleActionArgs{
						SourceNatActiveRanges: pulumi.StringArray{
							subnet.SelfLink,
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var net = new Gcp.Compute.Network("net", new()
    {
        Name = "my-network",
    });

    var subnet = new Gcp.Compute.Subnetwork("subnet", new()
    {
        Name = "my-subnetwork",
        Network = net.Id,
        IpCidrRange = "10.0.0.0/16",
        Region = "us-central1",
        Purpose = "PRIVATE_NAT",
    });

    var router = new Gcp.Compute.Router("router", new()
    {
        Name = "my-router",
        Region = subnet.Region,
        Network = net.Id,
    });

    var hub = new Gcp.NetworkConnectivity.Hub("hub", new()
    {
        Name = "my-hub",
        Description = "vpc hub for inter vpc nat",
    });

    var spoke = new Gcp.NetworkConnectivity.Spoke("spoke", new()
    {
        Name = "my-spoke",
        Location = "global",
        Description = "vpc spoke for inter vpc nat",
        Hub = hub.Id,
        LinkedVpcNetwork = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedVpcNetworkArgs
        {
            ExcludeExportRanges = new[]
            {
                "198.51.100.0/24",
                "10.10.0.0/16",
            },
            Uri = net.SelfLink,
        },
    });

    var natType = new Gcp.Compute.RouterNat("nat_type", new()
    {
        Name = "my-router-nat",
        Router = router.Name,
        Region = router.Region,
        SourceSubnetworkIpRangesToNat = "LIST_OF_SUBNETWORKS",
        EnableDynamicPortAllocation = false,
        EnableEndpointIndependentMapping = false,
        MinPortsPerVm = 32,
        Type = "PRIVATE",
        Subnetworks = new[]
        {
            new Gcp.Compute.Inputs.RouterNatSubnetworkArgs
            {
                Name = subnet.Id,
                SourceIpRangesToNats = new[]
                {
                    "ALL_IP_RANGES",
                },
            },
        },
        Rules = new[]
        {
            new Gcp.Compute.Inputs.RouterNatRuleArgs
            {
                RuleNumber = 100,
                Description = "rule for private nat",
                Match = "nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"",
                Action = new Gcp.Compute.Inputs.RouterNatRuleActionArgs
                {
                    SourceNatActiveRanges = new[]
                    {
                        subnet.SelfLink,
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedVpcNetworkArgs;
import com.pulumi.gcp.compute.RouterNat;
import com.pulumi.gcp.compute.RouterNatArgs;
import com.pulumi.gcp.compute.inputs.RouterNatSubnetworkArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleActionArgs;
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 net = new Network("net", NetworkArgs.builder()
            .name("my-network")
            .build());

        var subnet = new Subnetwork("subnet", SubnetworkArgs.builder()
            .name("my-subnetwork")
            .network(net.id())
            .ipCidrRange("10.0.0.0/16")
            .region("us-central1")
            .purpose("PRIVATE_NAT")
            .build());

        var router = new Router("router", RouterArgs.builder()
            .name("my-router")
            .region(subnet.region())
            .network(net.id())
            .build());

        var hub = new Hub("hub", HubArgs.builder()
            .name("my-hub")
            .description("vpc hub for inter vpc nat")
            .build());

        var spoke = new Spoke("spoke", SpokeArgs.builder()
            .name("my-spoke")
            .location("global")
            .description("vpc spoke for inter vpc nat")
            .hub(hub.id())
            .linkedVpcNetwork(SpokeLinkedVpcNetworkArgs.builder()
                .excludeExportRanges(                
                    "198.51.100.0/24",
                    "10.10.0.0/16")
                .uri(net.selfLink())
                .build())
            .build());

        var natType = new RouterNat("natType", RouterNatArgs.builder()
            .name("my-router-nat")
            .router(router.name())
            .region(router.region())
            .sourceSubnetworkIpRangesToNat("LIST_OF_SUBNETWORKS")
            .enableDynamicPortAllocation(false)
            .enableEndpointIndependentMapping(false)
            .minPortsPerVm(32)
            .type("PRIVATE")
            .subnetworks(RouterNatSubnetworkArgs.builder()
                .name(subnet.id())
                .sourceIpRangesToNats("ALL_IP_RANGES")
                .build())
            .rules(RouterNatRuleArgs.builder()
                .ruleNumber(100)
                .description("rule for private nat")
                .match("nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"")
                .action(RouterNatRuleActionArgs.builder()
                    .sourceNatActiveRanges(subnet.selfLink())
                    .build())
                .build())
            .build());

    }
}
resources:
  net:
    type: gcp:compute:Network
    properties:
      name: my-network
  subnet:
    type: gcp:compute:Subnetwork
    properties:
      name: my-subnetwork
      network: ${net.id}
      ipCidrRange: 10.0.0.0/16
      region: us-central1
      purpose: PRIVATE_NAT
  router:
    type: gcp:compute:Router
    properties:
      name: my-router
      region: ${subnet.region}
      network: ${net.id}
  hub:
    type: gcp:networkconnectivity:Hub
    properties:
      name: my-hub
      description: vpc hub for inter vpc nat
  spoke:
    type: gcp:networkconnectivity:Spoke
    properties:
      name: my-spoke
      location: global
      description: vpc spoke for inter vpc nat
      hub: ${hub.id}
      linkedVpcNetwork:
        excludeExportRanges:
          - 198.51.100.0/24
          - 10.10.0.0/16
        uri: ${net.selfLink}
  natType:
    type: gcp:compute:RouterNat
    name: nat_type
    properties:
      name: my-router-nat
      router: ${router.name}
      region: ${router.region}
      sourceSubnetworkIpRangesToNat: LIST_OF_SUBNETWORKS
      enableDynamicPortAllocation: false
      enableEndpointIndependentMapping: false
      minPortsPerVm: 32
      type: PRIVATE
      subnetworks:
        - name: ${subnet.id}
          sourceIpRangesToNats:
            - ALL_IP_RANGES
      rules:
        - ruleNumber: 100
          description: rule for private nat
          match: nexthop.hub == "//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub"
          action:
            sourceNatActiveRanges:
              - ${subnet.selfLink}

Setting type to PRIVATE changes NAT behavior from internet-bound to inter-VPC translation. The rules match traffic based on Network Connectivity Center hub paths, and sourceNatActiveRanges specifies which subnet ranges to use for translation. This configuration requires a hub and spoke topology to route traffic between VPCs.

Beyond these examples

These snippets focus on specific Cloud NAT features: automatic and manual IP allocation, NAT rules for traffic-based routing, and private NAT for inter-VPC translation. They’re intentionally minimal rather than full network architectures.

The examples may reference pre-existing infrastructure such as Cloud Router (created inline in examples), VPC networks and subnets, and Network Connectivity Center hubs for private NAT. They focus on configuring the NAT rather than provisioning complete network topologies.

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

  • Port allocation tuning (minPortsPerVm, maxPortsPerVm, enableDynamicPortAllocation)
  • Connection timeout configuration (tcpEstablishedIdleTimeoutSec, udpIdleTimeoutSec)
  • Endpoint type restrictions (endpointTypes)
  • NAT64 for IPv6 translation (nat64Subnetworks, sourceSubnetworkIpRangesToNat64)

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

Let's configure GCP Cloud Router NAT

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Common Errors & Pitfalls
Why am I getting a resourceInUseByAnotherResource error when recreating addresses?
Recreating a gcp.compute.Address that’s being used by gcp.compute.RouterNat triggers this error. To avoid it, add lifecycle.create_before_destroy = true to your address resource configuration.
IP Allocation & Addressing
What's the difference between AUTO_ONLY and MANUAL_ONLY IP allocation?
AUTO_ONLY lets Google Cloud Platform automatically allocate NAT IPs, while MANUAL_ONLY requires you to specify user-allocated NAT IP addresses via the natIps property.
What's the difference between initialNatIps and natIps?
initialNatIps is used as an initial value when creating the NAT alongside a RouterNatAddress resource and is immutable. natIps is the standard property for specifying NAT IPs. They conflict with each other and with drainNatIps. Both require natIpAllocateOption set to MANUAL_ONLY.
Port Allocation
Can I use dynamic port allocation with endpoint independent mapping?
No, enableDynamicPortAllocation and enableEndpointIndependentMapping are mutually exclusive.
What are the port allocation requirements for dynamic port allocation?
When enableDynamicPortAllocation is enabled: minPortsPerVm must be a power of two >= 32 (defaults to 32 if not set), and maxPortsPerVm must be a power of two greater than minPortsPerVm (defaults to 65536 if not set). For static allocation, minPortsPerVm defaults to 64.
Subnetwork & NAT Configuration
Can I have multiple NAT gateways in the same region with ALL_SUBNETWORKS configuration?
No, if sourceSubnetworkIpRangesToNat is set to ALL_SUBNETWORKS_ALL_IP_RANGES or ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES, there should not be any other RouterNat section in any Router for this network in this region.
What's the difference between PUBLIC and PRIVATE NAT?
PUBLIC NAT (the default) is used for public IP translation, while PRIVATE NAT is used for private IP translation. Private NAT rules use sourceNatActiveRanges instead of sourceNatActiveIps.
Timeouts & Immutability
What are the default timeout values for NAT connections?
Default timeouts are: ICMP (30s), TCP established (1200s), TCP TIME_WAIT (120s), TCP transitory (30s), and UDP (30s).
What properties can't be changed after creation?
The following properties are immutable: name, endpointTypes, project, region, router, type, and initialNatIps.

Using a different cloud?

Explore networking guides for other cloud providers: