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 FREEFrequently Asked Questions
Common Errors & Pitfalls
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
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.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
enableDynamicPortAllocation and enableEndpointIndependentMapping are mutually exclusive.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
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.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
name, endpointTypes, project, region, router, type, and initialNatIps.Using a different cloud?
Explore networking guides for other cloud providers: