Configure GCP Networking Forwarding Rules

The gcp:compute/forwardingRule:ForwardingRule resource, part of the Pulumi GCP provider, defines forwarding rules that route traffic to backend services, target pools, or Private Service Connect endpoints based on IP address, protocol, and port configuration. This guide focuses on four capabilities: external and internal load balancing, Private Service Connect endpoints, protocol-based routing (including L3_DEFAULT), and source IP-based traffic steering.

Forwarding rules reference backend services, target pools, or service attachments, and typically require VPC networks, subnetworks, and static IP addresses. The examples are intentionally small. Combine them with your own backend infrastructure and network configuration.

Route external traffic to a backend service

Most external load balancing deployments create a forwarding rule that directs incoming traffic from the internet to a regional backend service, providing a stable IP address while distributing load across instances.

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

const hc = new gcp.compute.RegionHealthCheck("hc", {
    name: "check-website-backend",
    checkIntervalSec: 1,
    timeoutSec: 1,
    region: "us-central1",
    tcpHealthCheck: {
        port: 80,
    },
});
const backend = new gcp.compute.RegionBackendService("backend", {
    name: "website-backend",
    region: "us-central1",
    loadBalancingScheme: "EXTERNAL",
    healthChecks: hc.id,
});
// Forwarding rule for External Network Load Balancing using Backend Services
const _default = new gcp.compute.ForwardingRule("default", {
    name: "website-forwarding-rule",
    region: "us-central1",
    portRange: "80",
    backendService: backend.id,
});
import pulumi
import pulumi_gcp as gcp

hc = gcp.compute.RegionHealthCheck("hc",
    name="check-website-backend",
    check_interval_sec=1,
    timeout_sec=1,
    region="us-central1",
    tcp_health_check={
        "port": 80,
    })
backend = gcp.compute.RegionBackendService("backend",
    name="website-backend",
    region="us-central1",
    load_balancing_scheme="EXTERNAL",
    health_checks=hc.id)
# Forwarding rule for External Network Load Balancing using Backend Services
default = gcp.compute.ForwardingRule("default",
    name="website-forwarding-rule",
    region="us-central1",
    port_range="80",
    backend_service=backend.id)
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 {
		hc, err := compute.NewRegionHealthCheck(ctx, "hc", &compute.RegionHealthCheckArgs{
			Name:             pulumi.String("check-website-backend"),
			CheckIntervalSec: pulumi.Int(1),
			TimeoutSec:       pulumi.Int(1),
			Region:           pulumi.String("us-central1"),
			TcpHealthCheck: &compute.RegionHealthCheckTcpHealthCheckArgs{
				Port: pulumi.Int(80),
			},
		})
		if err != nil {
			return err
		}
		backend, err := compute.NewRegionBackendService(ctx, "backend", &compute.RegionBackendServiceArgs{
			Name:                pulumi.String("website-backend"),
			Region:              pulumi.String("us-central1"),
			LoadBalancingScheme: pulumi.String("EXTERNAL"),
			HealthChecks:        hc.ID(),
		})
		if err != nil {
			return err
		}
		// Forwarding rule for External Network Load Balancing using Backend Services
		_, err = compute.NewForwardingRule(ctx, "default", &compute.ForwardingRuleArgs{
			Name:           pulumi.String("website-forwarding-rule"),
			Region:         pulumi.String("us-central1"),
			PortRange:      pulumi.String("80"),
			BackendService: backend.ID(),
		})
		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 hc = new Gcp.Compute.RegionHealthCheck("hc", new()
    {
        Name = "check-website-backend",
        CheckIntervalSec = 1,
        TimeoutSec = 1,
        Region = "us-central1",
        TcpHealthCheck = new Gcp.Compute.Inputs.RegionHealthCheckTcpHealthCheckArgs
        {
            Port = 80,
        },
    });

    var backend = new Gcp.Compute.RegionBackendService("backend", new()
    {
        Name = "website-backend",
        Region = "us-central1",
        LoadBalancingScheme = "EXTERNAL",
        HealthChecks = hc.Id,
    });

    // Forwarding rule for External Network Load Balancing using Backend Services
    var @default = new Gcp.Compute.ForwardingRule("default", new()
    {
        Name = "website-forwarding-rule",
        Region = "us-central1",
        PortRange = "80",
        BackendService = backend.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.RegionHealthCheck;
import com.pulumi.gcp.compute.RegionHealthCheckArgs;
import com.pulumi.gcp.compute.inputs.RegionHealthCheckTcpHealthCheckArgs;
import com.pulumi.gcp.compute.RegionBackendService;
import com.pulumi.gcp.compute.RegionBackendServiceArgs;
import com.pulumi.gcp.compute.ForwardingRule;
import com.pulumi.gcp.compute.ForwardingRuleArgs;
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 hc = new RegionHealthCheck("hc", RegionHealthCheckArgs.builder()
            .name("check-website-backend")
            .checkIntervalSec(1)
            .timeoutSec(1)
            .region("us-central1")
            .tcpHealthCheck(RegionHealthCheckTcpHealthCheckArgs.builder()
                .port(80)
                .build())
            .build());

        var backend = new RegionBackendService("backend", RegionBackendServiceArgs.builder()
            .name("website-backend")
            .region("us-central1")
            .loadBalancingScheme("EXTERNAL")
            .healthChecks(hc.id())
            .build());

        // Forwarding rule for External Network Load Balancing using Backend Services
        var default_ = new ForwardingRule("default", ForwardingRuleArgs.builder()
            .name("website-forwarding-rule")
            .region("us-central1")
            .portRange("80")
            .backendService(backend.id())
            .build());

    }
}
resources:
  # Forwarding rule for External Network Load Balancing using Backend Services
  default:
    type: gcp:compute:ForwardingRule
    properties:
      name: website-forwarding-rule
      region: us-central1
      portRange: 80
      backendService: ${backend.id}
  backend:
    type: gcp:compute:RegionBackendService
    properties:
      name: website-backend
      region: us-central1
      loadBalancingScheme: EXTERNAL
      healthChecks: ${hc.id}
  hc:
    type: gcp:compute:RegionHealthCheck
    properties:
      name: check-website-backend
      checkIntervalSec: 1
      timeoutSec: 1
      region: us-central1
      tcpHealthCheck:
        port: '80'

The backendService property points to your backend service, which must already exist with health checks configured. The portRange specifies which port receives traffic. When loadBalancingScheme is EXTERNAL (the default), the rule accepts traffic from the internet and routes it to your backend.

Route internal traffic within a VPC

Applications that load balance traffic between services within a VPC use internal forwarding rules, which provide private IP addresses accessible only from within the specified network.

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

const hc = new gcp.compute.HealthCheck("hc", {
    name: "check-website-backend",
    checkIntervalSec: 1,
    timeoutSec: 1,
    tcpHealthCheck: {
        port: 80,
    },
});
const backend = new gcp.compute.RegionBackendService("backend", {
    name: "website-backend",
    region: "us-central1",
    healthChecks: hc.id,
});
const defaultNetwork = new gcp.compute.Network("default", {
    name: "website-net",
    autoCreateSubnetworks: false,
});
const defaultSubnetwork = new gcp.compute.Subnetwork("default", {
    name: "website-net",
    ipCidrRange: "10.0.0.0/16",
    region: "us-central1",
    network: defaultNetwork.id,
});
// Forwarding rule for Internal Load Balancing
const _default = new gcp.compute.ForwardingRule("default", {
    name: "website-forwarding-rule",
    region: "us-central1",
    loadBalancingScheme: "INTERNAL",
    backendService: backend.id,
    allPorts: true,
    network: defaultNetwork.name,
    subnetwork: defaultSubnetwork.name,
    ipVersion: "IPV4",
});
import pulumi
import pulumi_gcp as gcp

hc = gcp.compute.HealthCheck("hc",
    name="check-website-backend",
    check_interval_sec=1,
    timeout_sec=1,
    tcp_health_check={
        "port": 80,
    })
backend = gcp.compute.RegionBackendService("backend",
    name="website-backend",
    region="us-central1",
    health_checks=hc.id)
default_network = gcp.compute.Network("default",
    name="website-net",
    auto_create_subnetworks=False)
default_subnetwork = gcp.compute.Subnetwork("default",
    name="website-net",
    ip_cidr_range="10.0.0.0/16",
    region="us-central1",
    network=default_network.id)
# Forwarding rule for Internal Load Balancing
default = gcp.compute.ForwardingRule("default",
    name="website-forwarding-rule",
    region="us-central1",
    load_balancing_scheme="INTERNAL",
    backend_service=backend.id,
    all_ports=True,
    network=default_network.name,
    subnetwork=default_subnetwork.name,
    ip_version="IPV4")
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 {
		hc, err := compute.NewHealthCheck(ctx, "hc", &compute.HealthCheckArgs{
			Name:             pulumi.String("check-website-backend"),
			CheckIntervalSec: pulumi.Int(1),
			TimeoutSec:       pulumi.Int(1),
			TcpHealthCheck: &compute.HealthCheckTcpHealthCheckArgs{
				Port: pulumi.Int(80),
			},
		})
		if err != nil {
			return err
		}
		backend, err := compute.NewRegionBackendService(ctx, "backend", &compute.RegionBackendServiceArgs{
			Name:         pulumi.String("website-backend"),
			Region:       pulumi.String("us-central1"),
			HealthChecks: hc.ID(),
		})
		if err != nil {
			return err
		}
		defaultNetwork, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
			Name:                  pulumi.String("website-net"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		defaultSubnetwork, err := compute.NewSubnetwork(ctx, "default", &compute.SubnetworkArgs{
			Name:        pulumi.String("website-net"),
			IpCidrRange: pulumi.String("10.0.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Network:     defaultNetwork.ID(),
		})
		if err != nil {
			return err
		}
		// Forwarding rule for Internal Load Balancing
		_, err = compute.NewForwardingRule(ctx, "default", &compute.ForwardingRuleArgs{
			Name:                pulumi.String("website-forwarding-rule"),
			Region:              pulumi.String("us-central1"),
			LoadBalancingScheme: pulumi.String("INTERNAL"),
			BackendService:      backend.ID(),
			AllPorts:            pulumi.Bool(true),
			Network:             defaultNetwork.Name,
			Subnetwork:          defaultSubnetwork.Name,
			IpVersion:           pulumi.String("IPV4"),
		})
		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 hc = new Gcp.Compute.HealthCheck("hc", new()
    {
        Name = "check-website-backend",
        CheckIntervalSec = 1,
        TimeoutSec = 1,
        TcpHealthCheck = new Gcp.Compute.Inputs.HealthCheckTcpHealthCheckArgs
        {
            Port = 80,
        },
    });

    var backend = new Gcp.Compute.RegionBackendService("backend", new()
    {
        Name = "website-backend",
        Region = "us-central1",
        HealthChecks = hc.Id,
    });

    var defaultNetwork = new Gcp.Compute.Network("default", new()
    {
        Name = "website-net",
        AutoCreateSubnetworks = false,
    });

    var defaultSubnetwork = new Gcp.Compute.Subnetwork("default", new()
    {
        Name = "website-net",
        IpCidrRange = "10.0.0.0/16",
        Region = "us-central1",
        Network = defaultNetwork.Id,
    });

    // Forwarding rule for Internal Load Balancing
    var @default = new Gcp.Compute.ForwardingRule("default", new()
    {
        Name = "website-forwarding-rule",
        Region = "us-central1",
        LoadBalancingScheme = "INTERNAL",
        BackendService = backend.Id,
        AllPorts = true,
        Network = defaultNetwork.Name,
        Subnetwork = defaultSubnetwork.Name,
        IpVersion = "IPV4",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.HealthCheck;
import com.pulumi.gcp.compute.HealthCheckArgs;
import com.pulumi.gcp.compute.inputs.HealthCheckTcpHealthCheckArgs;
import com.pulumi.gcp.compute.RegionBackendService;
import com.pulumi.gcp.compute.RegionBackendServiceArgs;
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.ForwardingRule;
import com.pulumi.gcp.compute.ForwardingRuleArgs;
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 hc = new HealthCheck("hc", HealthCheckArgs.builder()
            .name("check-website-backend")
            .checkIntervalSec(1)
            .timeoutSec(1)
            .tcpHealthCheck(HealthCheckTcpHealthCheckArgs.builder()
                .port(80)
                .build())
            .build());

        var backend = new RegionBackendService("backend", RegionBackendServiceArgs.builder()
            .name("website-backend")
            .region("us-central1")
            .healthChecks(hc.id())
            .build());

        var defaultNetwork = new Network("defaultNetwork", NetworkArgs.builder()
            .name("website-net")
            .autoCreateSubnetworks(false)
            .build());

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

        // Forwarding rule for Internal Load Balancing
        var default_ = new ForwardingRule("default", ForwardingRuleArgs.builder()
            .name("website-forwarding-rule")
            .region("us-central1")
            .loadBalancingScheme("INTERNAL")
            .backendService(backend.id())
            .allPorts(true)
            .network(defaultNetwork.name())
            .subnetwork(defaultSubnetwork.name())
            .ipVersion("IPV4")
            .build());

    }
}
resources:
  # Forwarding rule for Internal Load Balancing
  default:
    type: gcp:compute:ForwardingRule
    properties:
      name: website-forwarding-rule
      region: us-central1
      loadBalancingScheme: INTERNAL
      backendService: ${backend.id}
      allPorts: true
      network: ${defaultNetwork.name}
      subnetwork: ${defaultSubnetwork.name}
      ipVersion: IPV4
  backend:
    type: gcp:compute:RegionBackendService
    properties:
      name: website-backend
      region: us-central1
      healthChecks: ${hc.id}
  hc:
    type: gcp:compute:HealthCheck
    properties:
      name: check-website-backend
      checkIntervalSec: 1
      timeoutSec: 1
      tcpHealthCheck:
        port: '80'
  defaultNetwork:
    type: gcp:compute:Network
    name: default
    properties:
      name: website-net
      autoCreateSubnetworks: false
  defaultSubnetwork:
    type: gcp:compute:Subnetwork
    name: default
    properties:
      name: website-net
      ipCidrRange: 10.0.0.0/16
      region: us-central1
      network: ${defaultNetwork.id}

Setting loadBalancingScheme to INTERNAL restricts access to the specified network and subnetwork. The allPorts property accepts traffic on any port, simplifying configuration when backends listen on multiple ports. The ipVersion property controls whether the rule uses IPv4 or IPv6 addressing.

Forward traffic to a target pool

Legacy load balancing configurations use target pools to group instances, with forwarding rules directing traffic based on IP protocol and port range.

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

const defaultTargetPool = new gcp.compute.TargetPool("default", {name: "website-target-pool"});
const _default = new gcp.compute.ForwardingRule("default", {
    name: "website-forwarding-rule",
    target: defaultTargetPool.id,
    portRange: "80",
});
import pulumi
import pulumi_gcp as gcp

default_target_pool = gcp.compute.TargetPool("default", name="website-target-pool")
default = gcp.compute.ForwardingRule("default",
    name="website-forwarding-rule",
    target=default_target_pool.id,
    port_range="80")
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 {
		defaultTargetPool, err := compute.NewTargetPool(ctx, "default", &compute.TargetPoolArgs{
			Name: pulumi.String("website-target-pool"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewForwardingRule(ctx, "default", &compute.ForwardingRuleArgs{
			Name:      pulumi.String("website-forwarding-rule"),
			Target:    defaultTargetPool.ID(),
			PortRange: pulumi.String("80"),
		})
		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 defaultTargetPool = new Gcp.Compute.TargetPool("default", new()
    {
        Name = "website-target-pool",
    });

    var @default = new Gcp.Compute.ForwardingRule("default", new()
    {
        Name = "website-forwarding-rule",
        Target = defaultTargetPool.Id,
        PortRange = "80",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.TargetPool;
import com.pulumi.gcp.compute.TargetPoolArgs;
import com.pulumi.gcp.compute.ForwardingRule;
import com.pulumi.gcp.compute.ForwardingRuleArgs;
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 defaultTargetPool = new TargetPool("defaultTargetPool", TargetPoolArgs.builder()
            .name("website-target-pool")
            .build());

        var default_ = new ForwardingRule("default", ForwardingRuleArgs.builder()
            .name("website-forwarding-rule")
            .target(defaultTargetPool.id())
            .portRange("80")
            .build());

    }
}
resources:
  default:
    type: gcp:compute:ForwardingRule
    properties:
      name: website-forwarding-rule
      target: ${defaultTargetPool.id}
      portRange: '80'
  defaultTargetPool:
    type: gcp:compute:TargetPool
    name: default
    properties:
      name: website-target-pool

The target property references a TargetPool instead of a backend service. The portRange limits which port receives forwarded traffic. This configuration uses the default network when network and subnetwork are omitted.

Connect to services via Private Service Connect

Private Service Connect enables private connectivity to services across VPC networks without exposing traffic to the internet. Consumer forwarding rules establish endpoints that connect to producer service attachments.

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

// Consumer service endpoint
const consumerNet = new gcp.compute.Network("consumer_net", {
    name: "consumer-net",
    autoCreateSubnetworks: false,
});
const consumerSubnet = new gcp.compute.Subnetwork("consumer_subnet", {
    name: "consumer-net",
    ipCidrRange: "10.0.0.0/16",
    region: "us-central1",
    network: consumerNet.id,
});
const consumerAddress = new gcp.compute.Address("consumer_address", {
    name: "website-ip-1",
    region: "us-central1",
    subnetwork: consumerSubnet.id,
    addressType: "INTERNAL",
});
// Producer service attachment
const producerNet = new gcp.compute.Network("producer_net", {
    name: "producer-net",
    autoCreateSubnetworks: false,
});
const pscProducerSubnet = new gcp.compute.Subnetwork("psc_producer_subnet", {
    name: "producer-psc-net",
    ipCidrRange: "10.1.0.0/16",
    region: "us-central1",
    purpose: "PRIVATE_SERVICE_CONNECT",
    network: producerNet.id,
});
const producerSubnet = new gcp.compute.Subnetwork("producer_subnet", {
    name: "producer-net",
    ipCidrRange: "10.0.0.0/16",
    region: "us-central1",
    network: producerNet.id,
});
const producerServiceHealthCheck = new gcp.compute.HealthCheck("producer_service_health_check", {
    name: "producer-service-health-check",
    checkIntervalSec: 1,
    timeoutSec: 1,
    tcpHealthCheck: {
        port: 80,
    },
});
const producerServiceBackend = new gcp.compute.RegionBackendService("producer_service_backend", {
    name: "producer-service-backend",
    region: "us-central1",
    healthChecks: producerServiceHealthCheck.id,
});
const producerTargetService = new gcp.compute.ForwardingRule("producer_target_service", {
    name: "producer-forwarding-rule",
    region: "us-central1",
    loadBalancingScheme: "INTERNAL",
    backendService: producerServiceBackend.id,
    allPorts: true,
    network: producerNet.name,
    subnetwork: producerSubnet.name,
});
const producerServiceAttachment = new gcp.compute.ServiceAttachment("producer_service_attachment", {
    name: "producer-service",
    region: "us-central1",
    description: "A service attachment configured with Terraform",
    enableProxyProtocol: true,
    connectionPreference: "ACCEPT_AUTOMATIC",
    natSubnets: [pscProducerSubnet.name],
    targetService: producerTargetService.id,
});
// Forwarding rule for VPC private service connect
const _default = new gcp.compute.ForwardingRule("default", {
    name: "psc-endpoint",
    region: "us-central1",
    loadBalancingScheme: "",
    target: producerServiceAttachment.id,
    network: consumerNet.name,
    ipAddress: consumerAddress.id,
    allowPscGlobalAccess: true,
});
import pulumi
import pulumi_gcp as gcp

# Consumer service endpoint
consumer_net = gcp.compute.Network("consumer_net",
    name="consumer-net",
    auto_create_subnetworks=False)
consumer_subnet = gcp.compute.Subnetwork("consumer_subnet",
    name="consumer-net",
    ip_cidr_range="10.0.0.0/16",
    region="us-central1",
    network=consumer_net.id)
consumer_address = gcp.compute.Address("consumer_address",
    name="website-ip-1",
    region="us-central1",
    subnetwork=consumer_subnet.id,
    address_type="INTERNAL")
# Producer service attachment
producer_net = gcp.compute.Network("producer_net",
    name="producer-net",
    auto_create_subnetworks=False)
psc_producer_subnet = gcp.compute.Subnetwork("psc_producer_subnet",
    name="producer-psc-net",
    ip_cidr_range="10.1.0.0/16",
    region="us-central1",
    purpose="PRIVATE_SERVICE_CONNECT",
    network=producer_net.id)
producer_subnet = gcp.compute.Subnetwork("producer_subnet",
    name="producer-net",
    ip_cidr_range="10.0.0.0/16",
    region="us-central1",
    network=producer_net.id)
producer_service_health_check = gcp.compute.HealthCheck("producer_service_health_check",
    name="producer-service-health-check",
    check_interval_sec=1,
    timeout_sec=1,
    tcp_health_check={
        "port": 80,
    })
producer_service_backend = gcp.compute.RegionBackendService("producer_service_backend",
    name="producer-service-backend",
    region="us-central1",
    health_checks=producer_service_health_check.id)
producer_target_service = gcp.compute.ForwardingRule("producer_target_service",
    name="producer-forwarding-rule",
    region="us-central1",
    load_balancing_scheme="INTERNAL",
    backend_service=producer_service_backend.id,
    all_ports=True,
    network=producer_net.name,
    subnetwork=producer_subnet.name)
producer_service_attachment = gcp.compute.ServiceAttachment("producer_service_attachment",
    name="producer-service",
    region="us-central1",
    description="A service attachment configured with Terraform",
    enable_proxy_protocol=True,
    connection_preference="ACCEPT_AUTOMATIC",
    nat_subnets=[psc_producer_subnet.name],
    target_service=producer_target_service.id)
# Forwarding rule for VPC private service connect
default = gcp.compute.ForwardingRule("default",
    name="psc-endpoint",
    region="us-central1",
    load_balancing_scheme="",
    target=producer_service_attachment.id,
    network=consumer_net.name,
    ip_address=consumer_address.id,
    allow_psc_global_access=True)
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 {
		// Consumer service endpoint
		consumerNet, err := compute.NewNetwork(ctx, "consumer_net", &compute.NetworkArgs{
			Name:                  pulumi.String("consumer-net"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		consumerSubnet, err := compute.NewSubnetwork(ctx, "consumer_subnet", &compute.SubnetworkArgs{
			Name:        pulumi.String("consumer-net"),
			IpCidrRange: pulumi.String("10.0.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Network:     consumerNet.ID(),
		})
		if err != nil {
			return err
		}
		consumerAddress, err := compute.NewAddress(ctx, "consumer_address", &compute.AddressArgs{
			Name:        pulumi.String("website-ip-1"),
			Region:      pulumi.String("us-central1"),
			Subnetwork:  consumerSubnet.ID(),
			AddressType: pulumi.String("INTERNAL"),
		})
		if err != nil {
			return err
		}
		// Producer service attachment
		producerNet, err := compute.NewNetwork(ctx, "producer_net", &compute.NetworkArgs{
			Name:                  pulumi.String("producer-net"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		pscProducerSubnet, err := compute.NewSubnetwork(ctx, "psc_producer_subnet", &compute.SubnetworkArgs{
			Name:        pulumi.String("producer-psc-net"),
			IpCidrRange: pulumi.String("10.1.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Purpose:     pulumi.String("PRIVATE_SERVICE_CONNECT"),
			Network:     producerNet.ID(),
		})
		if err != nil {
			return err
		}
		producerSubnet, err := compute.NewSubnetwork(ctx, "producer_subnet", &compute.SubnetworkArgs{
			Name:        pulumi.String("producer-net"),
			IpCidrRange: pulumi.String("10.0.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Network:     producerNet.ID(),
		})
		if err != nil {
			return err
		}
		producerServiceHealthCheck, err := compute.NewHealthCheck(ctx, "producer_service_health_check", &compute.HealthCheckArgs{
			Name:             pulumi.String("producer-service-health-check"),
			CheckIntervalSec: pulumi.Int(1),
			TimeoutSec:       pulumi.Int(1),
			TcpHealthCheck: &compute.HealthCheckTcpHealthCheckArgs{
				Port: pulumi.Int(80),
			},
		})
		if err != nil {
			return err
		}
		producerServiceBackend, err := compute.NewRegionBackendService(ctx, "producer_service_backend", &compute.RegionBackendServiceArgs{
			Name:         pulumi.String("producer-service-backend"),
			Region:       pulumi.String("us-central1"),
			HealthChecks: producerServiceHealthCheck.ID(),
		})
		if err != nil {
			return err
		}
		producerTargetService, err := compute.NewForwardingRule(ctx, "producer_target_service", &compute.ForwardingRuleArgs{
			Name:                pulumi.String("producer-forwarding-rule"),
			Region:              pulumi.String("us-central1"),
			LoadBalancingScheme: pulumi.String("INTERNAL"),
			BackendService:      producerServiceBackend.ID(),
			AllPorts:            pulumi.Bool(true),
			Network:             producerNet.Name,
			Subnetwork:          producerSubnet.Name,
		})
		if err != nil {
			return err
		}
		producerServiceAttachment, err := compute.NewServiceAttachment(ctx, "producer_service_attachment", &compute.ServiceAttachmentArgs{
			Name:                 pulumi.String("producer-service"),
			Region:               pulumi.String("us-central1"),
			Description:          pulumi.String("A service attachment configured with Terraform"),
			EnableProxyProtocol:  pulumi.Bool(true),
			ConnectionPreference: pulumi.String("ACCEPT_AUTOMATIC"),
			NatSubnets: pulumi.StringArray{
				pscProducerSubnet.Name,
			},
			TargetService: producerTargetService.ID(),
		})
		if err != nil {
			return err
		}
		// Forwarding rule for VPC private service connect
		_, err = compute.NewForwardingRule(ctx, "default", &compute.ForwardingRuleArgs{
			Name:                 pulumi.String("psc-endpoint"),
			Region:               pulumi.String("us-central1"),
			LoadBalancingScheme:  pulumi.String(""),
			Target:               producerServiceAttachment.ID(),
			Network:              consumerNet.Name,
			IpAddress:            consumerAddress.ID(),
			AllowPscGlobalAccess: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    // Consumer service endpoint
    var consumerNet = new Gcp.Compute.Network("consumer_net", new()
    {
        Name = "consumer-net",
        AutoCreateSubnetworks = false,
    });

    var consumerSubnet = new Gcp.Compute.Subnetwork("consumer_subnet", new()
    {
        Name = "consumer-net",
        IpCidrRange = "10.0.0.0/16",
        Region = "us-central1",
        Network = consumerNet.Id,
    });

    var consumerAddress = new Gcp.Compute.Address("consumer_address", new()
    {
        Name = "website-ip-1",
        Region = "us-central1",
        Subnetwork = consumerSubnet.Id,
        AddressType = "INTERNAL",
    });

    // Producer service attachment
    var producerNet = new Gcp.Compute.Network("producer_net", new()
    {
        Name = "producer-net",
        AutoCreateSubnetworks = false,
    });

    var pscProducerSubnet = new Gcp.Compute.Subnetwork("psc_producer_subnet", new()
    {
        Name = "producer-psc-net",
        IpCidrRange = "10.1.0.0/16",
        Region = "us-central1",
        Purpose = "PRIVATE_SERVICE_CONNECT",
        Network = producerNet.Id,
    });

    var producerSubnet = new Gcp.Compute.Subnetwork("producer_subnet", new()
    {
        Name = "producer-net",
        IpCidrRange = "10.0.0.0/16",
        Region = "us-central1",
        Network = producerNet.Id,
    });

    var producerServiceHealthCheck = new Gcp.Compute.HealthCheck("producer_service_health_check", new()
    {
        Name = "producer-service-health-check",
        CheckIntervalSec = 1,
        TimeoutSec = 1,
        TcpHealthCheck = new Gcp.Compute.Inputs.HealthCheckTcpHealthCheckArgs
        {
            Port = 80,
        },
    });

    var producerServiceBackend = new Gcp.Compute.RegionBackendService("producer_service_backend", new()
    {
        Name = "producer-service-backend",
        Region = "us-central1",
        HealthChecks = producerServiceHealthCheck.Id,
    });

    var producerTargetService = new Gcp.Compute.ForwardingRule("producer_target_service", new()
    {
        Name = "producer-forwarding-rule",
        Region = "us-central1",
        LoadBalancingScheme = "INTERNAL",
        BackendService = producerServiceBackend.Id,
        AllPorts = true,
        Network = producerNet.Name,
        Subnetwork = producerSubnet.Name,
    });

    var producerServiceAttachment = new Gcp.Compute.ServiceAttachment("producer_service_attachment", new()
    {
        Name = "producer-service",
        Region = "us-central1",
        Description = "A service attachment configured with Terraform",
        EnableProxyProtocol = true,
        ConnectionPreference = "ACCEPT_AUTOMATIC",
        NatSubnets = new[]
        {
            pscProducerSubnet.Name,
        },
        TargetService = producerTargetService.Id,
    });

    // Forwarding rule for VPC private service connect
    var @default = new Gcp.Compute.ForwardingRule("default", new()
    {
        Name = "psc-endpoint",
        Region = "us-central1",
        LoadBalancingScheme = "",
        Target = producerServiceAttachment.Id,
        Network = consumerNet.Name,
        IpAddress = consumerAddress.Id,
        AllowPscGlobalAccess = true,
    });

});
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.Address;
import com.pulumi.gcp.compute.AddressArgs;
import com.pulumi.gcp.compute.HealthCheck;
import com.pulumi.gcp.compute.HealthCheckArgs;
import com.pulumi.gcp.compute.inputs.HealthCheckTcpHealthCheckArgs;
import com.pulumi.gcp.compute.RegionBackendService;
import com.pulumi.gcp.compute.RegionBackendServiceArgs;
import com.pulumi.gcp.compute.ForwardingRule;
import com.pulumi.gcp.compute.ForwardingRuleArgs;
import com.pulumi.gcp.compute.ServiceAttachment;
import com.pulumi.gcp.compute.ServiceAttachmentArgs;
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) {
        // Consumer service endpoint
        var consumerNet = new Network("consumerNet", NetworkArgs.builder()
            .name("consumer-net")
            .autoCreateSubnetworks(false)
            .build());

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

        var consumerAddress = new Address("consumerAddress", AddressArgs.builder()
            .name("website-ip-1")
            .region("us-central1")
            .subnetwork(consumerSubnet.id())
            .addressType("INTERNAL")
            .build());

        // Producer service attachment
        var producerNet = new Network("producerNet", NetworkArgs.builder()
            .name("producer-net")
            .autoCreateSubnetworks(false)
            .build());

        var pscProducerSubnet = new Subnetwork("pscProducerSubnet", SubnetworkArgs.builder()
            .name("producer-psc-net")
            .ipCidrRange("10.1.0.0/16")
            .region("us-central1")
            .purpose("PRIVATE_SERVICE_CONNECT")
            .network(producerNet.id())
            .build());

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

        var producerServiceHealthCheck = new HealthCheck("producerServiceHealthCheck", HealthCheckArgs.builder()
            .name("producer-service-health-check")
            .checkIntervalSec(1)
            .timeoutSec(1)
            .tcpHealthCheck(HealthCheckTcpHealthCheckArgs.builder()
                .port(80)
                .build())
            .build());

        var producerServiceBackend = new RegionBackendService("producerServiceBackend", RegionBackendServiceArgs.builder()
            .name("producer-service-backend")
            .region("us-central1")
            .healthChecks(producerServiceHealthCheck.id())
            .build());

        var producerTargetService = new ForwardingRule("producerTargetService", ForwardingRuleArgs.builder()
            .name("producer-forwarding-rule")
            .region("us-central1")
            .loadBalancingScheme("INTERNAL")
            .backendService(producerServiceBackend.id())
            .allPorts(true)
            .network(producerNet.name())
            .subnetwork(producerSubnet.name())
            .build());

        var producerServiceAttachment = new ServiceAttachment("producerServiceAttachment", ServiceAttachmentArgs.builder()
            .name("producer-service")
            .region("us-central1")
            .description("A service attachment configured with Terraform")
            .enableProxyProtocol(true)
            .connectionPreference("ACCEPT_AUTOMATIC")
            .natSubnets(pscProducerSubnet.name())
            .targetService(producerTargetService.id())
            .build());

        // Forwarding rule for VPC private service connect
        var default_ = new ForwardingRule("default", ForwardingRuleArgs.builder()
            .name("psc-endpoint")
            .region("us-central1")
            .loadBalancingScheme("")
            .target(producerServiceAttachment.id())
            .network(consumerNet.name())
            .ipAddress(consumerAddress.id())
            .allowPscGlobalAccess(true)
            .build());

    }
}
resources:
  # Forwarding rule for VPC private service connect
  default:
    type: gcp:compute:ForwardingRule
    properties:
      name: psc-endpoint
      region: us-central1
      loadBalancingScheme: ""
      target: ${producerServiceAttachment.id}
      network: ${consumerNet.name}
      ipAddress: ${consumerAddress.id}
      allowPscGlobalAccess: true
  # Consumer service endpoint
  consumerNet:
    type: gcp:compute:Network
    name: consumer_net
    properties:
      name: consumer-net
      autoCreateSubnetworks: false
  consumerSubnet:
    type: gcp:compute:Subnetwork
    name: consumer_subnet
    properties:
      name: consumer-net
      ipCidrRange: 10.0.0.0/16
      region: us-central1
      network: ${consumerNet.id}
  consumerAddress:
    type: gcp:compute:Address
    name: consumer_address
    properties:
      name: website-ip-1
      region: us-central1
      subnetwork: ${consumerSubnet.id}
      addressType: INTERNAL
  # Producer service attachment
  producerNet:
    type: gcp:compute:Network
    name: producer_net
    properties:
      name: producer-net
      autoCreateSubnetworks: false
  producerSubnet:
    type: gcp:compute:Subnetwork
    name: producer_subnet
    properties:
      name: producer-net
      ipCidrRange: 10.0.0.0/16
      region: us-central1
      network: ${producerNet.id}
  pscProducerSubnet:
    type: gcp:compute:Subnetwork
    name: psc_producer_subnet
    properties:
      name: producer-psc-net
      ipCidrRange: 10.1.0.0/16
      region: us-central1
      purpose: PRIVATE_SERVICE_CONNECT
      network: ${producerNet.id}
  producerServiceAttachment:
    type: gcp:compute:ServiceAttachment
    name: producer_service_attachment
    properties:
      name: producer-service
      region: us-central1
      description: A service attachment configured with Terraform
      enableProxyProtocol: true
      connectionPreference: ACCEPT_AUTOMATIC
      natSubnets:
        - ${pscProducerSubnet.name}
      targetService: ${producerTargetService.id}
  producerTargetService:
    type: gcp:compute:ForwardingRule
    name: producer_target_service
    properties:
      name: producer-forwarding-rule
      region: us-central1
      loadBalancingScheme: INTERNAL
      backendService: ${producerServiceBackend.id}
      allPorts: true
      network: ${producerNet.name}
      subnetwork: ${producerSubnet.name}
  producerServiceBackend:
    type: gcp:compute:RegionBackendService
    name: producer_service_backend
    properties:
      name: producer-service-backend
      region: us-central1
      healthChecks: ${producerServiceHealthCheck.id}
  producerServiceHealthCheck:
    type: gcp:compute:HealthCheck
    name: producer_service_health_check
    properties:
      name: producer-service-health-check
      checkIntervalSec: 1
      timeoutSec: 1
      tcpHealthCheck:
        port: '80'

The target property references a ServiceAttachment from the producer side. Setting loadBalancingScheme to an empty string ("") indicates Private Service Connect usage. The ipAddress property specifies the consumer-side endpoint address, and allowPscGlobalAccess controls whether the endpoint can be accessed from other regions.

Forward all protocols with L3_DEFAULT

Some workloads need to handle multiple protocols or non-standard protocols. The L3_DEFAULT protocol forwards all IP traffic regardless of protocol type.

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

const healthCheck = new gcp.compute.RegionHealthCheck("health_check", {
    name: "health-check",
    region: "us-central1",
    tcpHealthCheck: {
        port: 80,
    },
});
const service = new gcp.compute.RegionBackendService("service", {
    region: "us-central1",
    name: "service",
    healthChecks: healthCheck.id,
    protocol: "UNSPECIFIED",
    loadBalancingScheme: "EXTERNAL",
});
const fwdRule = new gcp.compute.ForwardingRule("fwd_rule", {
    name: "l3-forwarding-rule",
    backendService: service.id,
    ipProtocol: "L3_DEFAULT",
    allPorts: true,
});
import pulumi
import pulumi_gcp as gcp

health_check = gcp.compute.RegionHealthCheck("health_check",
    name="health-check",
    region="us-central1",
    tcp_health_check={
        "port": 80,
    })
service = gcp.compute.RegionBackendService("service",
    region="us-central1",
    name="service",
    health_checks=health_check.id,
    protocol="UNSPECIFIED",
    load_balancing_scheme="EXTERNAL")
fwd_rule = gcp.compute.ForwardingRule("fwd_rule",
    name="l3-forwarding-rule",
    backend_service=service.id,
    ip_protocol="L3_DEFAULT",
    all_ports=True)
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 {
		healthCheck, err := compute.NewRegionHealthCheck(ctx, "health_check", &compute.RegionHealthCheckArgs{
			Name:   pulumi.String("health-check"),
			Region: pulumi.String("us-central1"),
			TcpHealthCheck: &compute.RegionHealthCheckTcpHealthCheckArgs{
				Port: pulumi.Int(80),
			},
		})
		if err != nil {
			return err
		}
		service, err := compute.NewRegionBackendService(ctx, "service", &compute.RegionBackendServiceArgs{
			Region:              pulumi.String("us-central1"),
			Name:                pulumi.String("service"),
			HealthChecks:        healthCheck.ID(),
			Protocol:            pulumi.String("UNSPECIFIED"),
			LoadBalancingScheme: pulumi.String("EXTERNAL"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewForwardingRule(ctx, "fwd_rule", &compute.ForwardingRuleArgs{
			Name:           pulumi.String("l3-forwarding-rule"),
			BackendService: service.ID(),
			IpProtocol:     pulumi.String("L3_DEFAULT"),
			AllPorts:       pulumi.Bool(true),
		})
		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 healthCheck = new Gcp.Compute.RegionHealthCheck("health_check", new()
    {
        Name = "health-check",
        Region = "us-central1",
        TcpHealthCheck = new Gcp.Compute.Inputs.RegionHealthCheckTcpHealthCheckArgs
        {
            Port = 80,
        },
    });

    var service = new Gcp.Compute.RegionBackendService("service", new()
    {
        Region = "us-central1",
        Name = "service",
        HealthChecks = healthCheck.Id,
        Protocol = "UNSPECIFIED",
        LoadBalancingScheme = "EXTERNAL",
    });

    var fwdRule = new Gcp.Compute.ForwardingRule("fwd_rule", new()
    {
        Name = "l3-forwarding-rule",
        BackendService = service.Id,
        IpProtocol = "L3_DEFAULT",
        AllPorts = true,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.RegionHealthCheck;
import com.pulumi.gcp.compute.RegionHealthCheckArgs;
import com.pulumi.gcp.compute.inputs.RegionHealthCheckTcpHealthCheckArgs;
import com.pulumi.gcp.compute.RegionBackendService;
import com.pulumi.gcp.compute.RegionBackendServiceArgs;
import com.pulumi.gcp.compute.ForwardingRule;
import com.pulumi.gcp.compute.ForwardingRuleArgs;
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 healthCheck = new RegionHealthCheck("healthCheck", RegionHealthCheckArgs.builder()
            .name("health-check")
            .region("us-central1")
            .tcpHealthCheck(RegionHealthCheckTcpHealthCheckArgs.builder()
                .port(80)
                .build())
            .build());

        var service = new RegionBackendService("service", RegionBackendServiceArgs.builder()
            .region("us-central1")
            .name("service")
            .healthChecks(healthCheck.id())
            .protocol("UNSPECIFIED")
            .loadBalancingScheme("EXTERNAL")
            .build());

        var fwdRule = new ForwardingRule("fwdRule", ForwardingRuleArgs.builder()
            .name("l3-forwarding-rule")
            .backendService(service.id())
            .ipProtocol("L3_DEFAULT")
            .allPorts(true)
            .build());

    }
}
resources:
  fwdRule:
    type: gcp:compute:ForwardingRule
    name: fwd_rule
    properties:
      name: l3-forwarding-rule
      backendService: ${service.id}
      ipProtocol: L3_DEFAULT
      allPorts: true
  service:
    type: gcp:compute:RegionBackendService
    properties:
      region: us-central1
      name: service
      healthChecks: ${healthCheck.id}
      protocol: UNSPECIFIED
      loadBalancingScheme: EXTERNAL
  healthCheck:
    type: gcp:compute:RegionHealthCheck
    name: health_check
    properties:
      name: health-check
      region: us-central1
      tcpHealthCheck:
        port: 80

Setting ipProtocol to L3_DEFAULT requires allPorts to be true, allowing the rule to forward packets regardless of destination port. The backend service must use UNSPECIFIED protocol to accept this traffic. This configuration works with target instances or backend services that handle multiple protocols.

Route traffic based on source IP ranges

Traffic steering allows different forwarding rules to share the same IP address but route traffic differently based on source IP ranges, enabling region-specific or client-specific routing policies.

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

const basic = new gcp.compute.Address("basic", {
    name: "website-ip",
    region: "us-central1",
});
const external = new gcp.compute.RegionBackendService("external", {
    name: "service-backend",
    region: "us-central1",
    loadBalancingScheme: "EXTERNAL",
});
const externalForwardingRule = new gcp.compute.ForwardingRule("external", {
    name: "external-forwarding-rule",
    region: "us-central1",
    ipAddress: basic.address,
    backendService: external.selfLink,
    loadBalancingScheme: "EXTERNAL",
});
const steering = new gcp.compute.ForwardingRule("steering", {
    name: "steering-rule",
    region: "us-central1",
    ipAddress: basic.address,
    backendService: external.selfLink,
    loadBalancingScheme: "EXTERNAL",
    sourceIpRanges: [
        "34.121.88.0/24",
        "35.187.239.137",
    ],
}, {
    dependsOn: [externalForwardingRule],
});
import pulumi
import pulumi_gcp as gcp

basic = gcp.compute.Address("basic",
    name="website-ip",
    region="us-central1")
external = gcp.compute.RegionBackendService("external",
    name="service-backend",
    region="us-central1",
    load_balancing_scheme="EXTERNAL")
external_forwarding_rule = gcp.compute.ForwardingRule("external",
    name="external-forwarding-rule",
    region="us-central1",
    ip_address=basic.address,
    backend_service=external.self_link,
    load_balancing_scheme="EXTERNAL")
steering = gcp.compute.ForwardingRule("steering",
    name="steering-rule",
    region="us-central1",
    ip_address=basic.address,
    backend_service=external.self_link,
    load_balancing_scheme="EXTERNAL",
    source_ip_ranges=[
        "34.121.88.0/24",
        "35.187.239.137",
    ],
    opts = pulumi.ResourceOptions(depends_on=[external_forwarding_rule]))
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 {
		basic, err := compute.NewAddress(ctx, "basic", &compute.AddressArgs{
			Name:   pulumi.String("website-ip"),
			Region: pulumi.String("us-central1"),
		})
		if err != nil {
			return err
		}
		external, err := compute.NewRegionBackendService(ctx, "external", &compute.RegionBackendServiceArgs{
			Name:                pulumi.String("service-backend"),
			Region:              pulumi.String("us-central1"),
			LoadBalancingScheme: pulumi.String("EXTERNAL"),
		})
		if err != nil {
			return err
		}
		externalForwardingRule, err := compute.NewForwardingRule(ctx, "external", &compute.ForwardingRuleArgs{
			Name:                pulumi.String("external-forwarding-rule"),
			Region:              pulumi.String("us-central1"),
			IpAddress:           basic.Address,
			BackendService:      external.SelfLink,
			LoadBalancingScheme: pulumi.String("EXTERNAL"),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewForwardingRule(ctx, "steering", &compute.ForwardingRuleArgs{
			Name:                pulumi.String("steering-rule"),
			Region:              pulumi.String("us-central1"),
			IpAddress:           basic.Address,
			BackendService:      external.SelfLink,
			LoadBalancingScheme: pulumi.String("EXTERNAL"),
			SourceIpRanges: pulumi.StringArray{
				pulumi.String("34.121.88.0/24"),
				pulumi.String("35.187.239.137"),
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			externalForwardingRule,
		}))
		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 basic = new Gcp.Compute.Address("basic", new()
    {
        Name = "website-ip",
        Region = "us-central1",
    });

    var external = new Gcp.Compute.RegionBackendService("external", new()
    {
        Name = "service-backend",
        Region = "us-central1",
        LoadBalancingScheme = "EXTERNAL",
    });

    var externalForwardingRule = new Gcp.Compute.ForwardingRule("external", new()
    {
        Name = "external-forwarding-rule",
        Region = "us-central1",
        IpAddress = basic.IPAddress,
        BackendService = external.SelfLink,
        LoadBalancingScheme = "EXTERNAL",
    });

    var steering = new Gcp.Compute.ForwardingRule("steering", new()
    {
        Name = "steering-rule",
        Region = "us-central1",
        IpAddress = basic.IPAddress,
        BackendService = external.SelfLink,
        LoadBalancingScheme = "EXTERNAL",
        SourceIpRanges = new[]
        {
            "34.121.88.0/24",
            "35.187.239.137",
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            externalForwardingRule,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Address;
import com.pulumi.gcp.compute.AddressArgs;
import com.pulumi.gcp.compute.RegionBackendService;
import com.pulumi.gcp.compute.RegionBackendServiceArgs;
import com.pulumi.gcp.compute.ForwardingRule;
import com.pulumi.gcp.compute.ForwardingRuleArgs;
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 basic = new Address("basic", AddressArgs.builder()
            .name("website-ip")
            .region("us-central1")
            .build());

        var external = new RegionBackendService("external", RegionBackendServiceArgs.builder()
            .name("service-backend")
            .region("us-central1")
            .loadBalancingScheme("EXTERNAL")
            .build());

        var externalForwardingRule = new ForwardingRule("externalForwardingRule", ForwardingRuleArgs.builder()
            .name("external-forwarding-rule")
            .region("us-central1")
            .ipAddress(basic.address())
            .backendService(external.selfLink())
            .loadBalancingScheme("EXTERNAL")
            .build());

        var steering = new ForwardingRule("steering", ForwardingRuleArgs.builder()
            .name("steering-rule")
            .region("us-central1")
            .ipAddress(basic.address())
            .backendService(external.selfLink())
            .loadBalancingScheme("EXTERNAL")
            .sourceIpRanges(            
                "34.121.88.0/24",
                "35.187.239.137")
            .build(), CustomResourceOptions.builder()
                .dependsOn(externalForwardingRule)
                .build());

    }
}
resources:
  steering:
    type: gcp:compute:ForwardingRule
    properties:
      name: steering-rule
      region: us-central1
      ipAddress: ${basic.address}
      backendService: ${external.selfLink}
      loadBalancingScheme: EXTERNAL
      sourceIpRanges:
        - 34.121.88.0/24
        - 35.187.239.137
    options:
      dependsOn:
        - ${externalForwardingRule}
  basic:
    type: gcp:compute:Address
    properties:
      name: website-ip
      region: us-central1
  external:
    type: gcp:compute:RegionBackendService
    properties:
      name: service-backend
      region: us-central1
      loadBalancingScheme: EXTERNAL
  externalForwardingRule:
    type: gcp:compute:ForwardingRule
    name: external
    properties:
      name: external-forwarding-rule
      region: us-central1
      ipAddress: ${basic.address}
      backendService: ${external.selfLink}
      loadBalancingScheme: EXTERNAL

The sourceIpRanges property lists IP addresses or CIDR ranges that trigger this forwarding rule. Multiple rules can share the same ipAddress as long as they have different sourceIpRanges. This configuration requires a base forwarding rule (without sourceIpRanges) to exist first, which handles traffic from all other sources.

Beyond these examples

These snippets focus on specific forwarding rule features: external and internal load balancing schemes, Private Service Connect endpoints, protocol and port configuration, and source-based traffic steering. They’re intentionally minimal rather than full load balancing deployments.

The examples reference pre-existing infrastructure such as backend services, target pools, or service attachments, VPC networks and subnetworks, and health checks and static IP addresses. They focus on configuring the forwarding rule rather than provisioning the complete load balancing stack.

To keep things focused, common forwarding rule patterns are omitted, including:

  • IPv6 configuration (ipVersion for dual-stack)
  • Service Directory registration (serviceDirectoryRegistrations)
  • Packet mirroring (isMirroringCollector)
  • Labels and metadata (labels property)
  • Network tier selection (networkTier for PREMIUM vs STANDARD)
  • Global access controls (allowGlobalAccess for cross-region)

These omissions are intentional: the goal is to illustrate how each forwarding rule feature is wired, not provide drop-in load balancing modules. See the ForwardingRule resource reference for all available configuration options.

Let's configure GCP Networking Forwarding Rules

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Immutability
What properties can't I change after creating a forwarding rule?
Most properties are immutable, including ipAddress, ipProtocol, ipVersion, name, network, networkTier, portRange, ports, allPorts, loadBalancingScheme, backendService, subnetwork, and region. Changing these requires recreating the resource.
Can I use both portRange and allPorts together?
No, the ports, portRange, and allPorts fields are mutually exclusive. Choose one port configuration method based on your use case.
Why am I getting a networkTier mismatch error?
If you specify ipAddress, the networkTier value must match the networkTier of the Address resource. Ensure both use the same tier (PREMIUM or STANDARD).
Port Configuration & Conflicts
Why am I getting an error about overlapping port ranges?
Two or more forwarding rules cannot use the same [IPAddress, IPProtocol] pair with overlapping portRanges. For external rules, this applies globally. For internal rules, this applies within the same VPC network.
How many ports can I specify in the ports field?
You can specify up to five ports by number, separated by commas. The ports can be contiguous or discontiguous. The ports field requires ipProtocol be TCP, UDP, or SCTP.
Protocol & IP Configuration
What's required when using L3_DEFAULT protocol?
When using ipProtocol: L3_DEFAULT, you must set allPorts: true. L3_DEFAULT can attach to target instances or backend services with UNSPECIFIED protocol, but cannot attach to backend services with TCP or UDP.
How do I configure IPv6 for internal load balancing?
Set ipVersion: IPV6 and ensure your subnetwork has stackType: IPV4_IPV6 and ipv6AccessType: INTERNAL. The subnetwork must support dual-stack configuration.
Private Service Connect (PSC)
How do I configure Private Service Connect forwarding rules?
Set loadBalancingScheme to an empty string (""), specify target as a ServiceAttachment, and configure network and ipAddress. Use allowPscGlobalAccess: true for cross-region access and noAutomateDnsZone: true to disable automatic DNS zone creation.
Why is my PSC forwarding rule name being rejected?
For Private Service Connect forwarding rules to Google APIs, the name must be 1-20 characters (not 1-63). Use lowercase letters and numbers, starting with a letter.
Load Balancing Schemes
What's the proxy subnetwork requirement for managed load balancers?
INTERNAL_MANAGED and EXTERNAL_MANAGED load balancing schemes require a proxy subnetwork with purpose: REGIONAL_MANAGED_PROXY. Create this subnetwork and add it as a dependency before creating the forwarding rule.
What's the difference between target and backendService?
Use backendService for Internal TCP/UDP Load Balancing and Network Load Balancing (required). Use target for other load balancer types like TargetPool, TargetHttpProxy, or ServiceAttachment.
How do I enable cross-region access for internal load balancers?
Set allowGlobalAccess: true to allow clients from all regions to access your internal load balancer. Otherwise, only clients in the same region can access it.
Advanced Features & Limitations
How many source IP ranges can I specify for traffic steering?
You can specify up to 64 source IP ranges using sourceIpRanges. This field only works with regional forwarding rules whose loadBalancingScheme is EXTERNAL.
Can I use packet mirroring with external load balancers?
No, isMirroringCollector can only be set to true for load balancers with loadBalancingScheme set to INTERNAL. External load balancers cannot be used as packet mirroring collectors.

Using a different cloud?

Explore networking guides for other cloud providers: