Note: Dynamic Forwarding is currently in Preview.
Cloud Load Balancing is the backbone of scalable applications on Google Cloud, but as architectures grow in complexity, you might eventually bump into certain boundaries. Currently, load balancers have scale limits related to various resources—such as the number of backend services, backends, or URL maps.
But what if you need to scale up to hundreds of thousands of endpoints? Or what if you want to route traffic based on highly sophisticated, proprietary affinity rules?
Enter Dynamic Forwarding, a powerful new feature that delegates routing control decisions to an external processor. By leveraging the fact that modern load balancing in GCP uses Envoy components, Dynamic Forwarding allows you to create your own routing policies based on regular or custom HTTP headers.
You can even imagine cutting-edge use cases where load balancing backends are provisioned right at the time of the request, saving massive amounts of compute resources. It acts as a completely custom control plane extension for your cloud load balancers. Let’s dive into how it works and how you can set it up.
How does it work?
Normally, a load balancer maintains a full understanding of its backends, their health, and their capacity. Based on various metrics and load balancing algorithms, it selects the endpoint to which traffic should be forwarded.
Dynamic Forwarding turns this paradigm on its head. The fundamental element here is the extproc (External Processor). The extproc receives a request from the load balancer, evaluates your custom logic, and returns the exact IP address of the endpoint to which the request should be sent.
Because you have full control over the IP address returned by the extproc, the sky is truly the limit.
The core components
-
Extproc instance: A worker (often running in a container) that receives gRPC calls from the load balancer, makes a policy decision, and returns the target IP address.
-
Extproc configuration: The custom code executing the routing logic (in our upcoming example, Python code).
-
Service Extension configuration: Binds the forwarding rule with the callout backend (extproc) and dictates which traffic should trigger dynamic forwarding.
-
Dynamic Forwarding backend service: A special backend service that actually contains no static backends—hence the name “dynamic.”
The traffic flow
-
The load balancer receives an incoming HTTP/S request.
-
The Service Extension configured on the forwarding rule evaluates if the traffic matches the criteria to be sent to the “callout backend service” (which hosts the extproc worker).If there is a positive match, the load balancer makes a gRPC request to the extproc.
-
The extproc evaluates its policy and decides which target IP address should be returned.
-
The gRPC response is evaluated by the load balancer.
-
The request is forwarded directly to the endpoint IP address decided by the extproc.
Hands-on: configuring Dynamic Forwarding
The best way to learn is by doing. To help you understand the concepts, I’ve prepared a simplified (yet robust) version of a Dynamic Forwarding setup using Terraform.
Our goal: Send an HTTP request to the load balancer with a special header hinting which backend should be selected. When the LB sees a header with Host: example.com, it will forward it to the extproc. The extproc will check if the ip-to-return header is legitimate, and if so, instruct the LB to forward the traffic to that IP.
curl -H "Host: example.com" \
-H "ip-to-return: 10.10.20.101" \
-k https://<LB_IP_ADDDRESS>/server.php
Step 1: Deploy the environment
First, clone the repositories containing the Terraform scripts and Service Extensions code:
$git clone https://github.com/astianseb/dynamic-forwarding-rxlb-simple.git
$git clone https://github.com/GoogleCloudPlatform/service-extensions.git
Navigate to the Terraform directory, modify your terraform.tfvars to provide your GCP billing ID (the project will be created automatically), and deploy:
$terraform init
$terraform apply
Once the environment is up, you will have:
-
A forwarding rule with an external IP address.
-
Two backend services: callout-service (hosting a COS VM with a prebuilt extproc container) and dynamic-backend (which is empty).
-
Two GCE instances acting as our endpoints: vm-a (10.10.20.101) & vm-b (10.10.20.102).
Step 2: Create the service extension
Next, we bind our forwarding rule to the callout backend by creating a Service Extension. Note that you’ll need to update the PROJECT_ID based on your Terraform output.
export PROJECT_ID=<PROJECT_ID>
export REGION=europe-central2
$cat <<EOF > dynamic-ext.yaml
name: traffic-ext
forwardingRules:
- https://www.googleapis.com/compute/v1/projects/$PROJECT_ID/regions/$REGION/forwardingRules/sg-fwd-rule
loadBalancingScheme: EXTERNAL_MANAGED
extensionChains:
- name: "chain1"
matchCondition:
celExpression: 'request.host == "example.com"'
extensions:
- name: 'ext11'
authority: ext11.com
allowDynamicForwarding: true
service: https://www.googleapis.com/compute/v1/projects/$PROJECT_ID/regions/$REGION/backendServices/callout-service
failOpen: false
timeout: 0.1s
supportedEvents:
- REQUEST_HEADERS
EOF
$gcloud beta service-extensions lb-traffic-extensions import traffic-ext \
--source=dynamic-ext.yaml \
--location=$REGION \
--project=$PROJECT_ID
Step 3: Test the routing
The environment is ready! Let’s SSH into the callouts VM to watch the extproc in action:
$gcloud compute ssh callouts-vm --zone=europe-central2-b
$sudo docker logs -f callouts-container
From your local machine, send a request to your load balancer’s IP address (replace with your actual LB IP):
curl -H "Host: example.com" \
-H "ip-to-return: 10.10.20.101" \
-k https://<FORWARDING_RULE_IP>/server.php
In your callouts-container logs, you should see the gRPC evaluation intercepting the custom header and dynamically selecting the IP:
35.191.218.93 - - [23/Mar/2026 08:43:28] "GET / HTTP/1.1" 200 -
35.191.218.88 - - [23/Mar/2026 08:43:28] "GET / HTTP/1.1" 200 -
INFO:root:--- Received Request Headers ---
INFO:root:Header: :method = GET
INFO:root:Header: :scheme = https
INFO:root:Header: :authority = example.com
INFO:root:Header: :path = /server.php
INFO:root:Header: user-agent = curl/8.18.0
INFO:root:Header: accept = */*
INFO:root:Header: ip-to-return = 10.10.20.101
INFO:root:Header: x-forwarded-proto = https
INFO:root:Header: via = 1.1 google
INFO:root:Header: x-forwarded-for = 185.69.197.150,34.0.251.241
INFO:root:-------------------------------
DEBUG:root:Selected ip: 10.10.20.101
35.191.218.91 - - [23/Mar/2026 08:43:29] "GET / HTTP/1.1" 200 -
35.191.218.93 - - [23/Mar/2026 08:43:33] "GET / HTTP/1.1" 200 -
35.191.218.88 - - [23/Mar/2026 08:43:33] "GET / HTTP/1.1" 200 -
The underlying Python code making this policy decision is remarkably simple and can be reviewed here.
Step 4: Build your own extproc (Optional)
If you want to modify the routing logic and build your own container, you can do so using the service-extensions repo you cloned earlier:
$cd ./service-extensions/callouts/python
$docker build -f ./extproc/example/Dockerfile -t sg-dynamic-forwarding \
--build-arg copy_path=extproc/example/dynamic_forwarding/ \
--build-arg run_module=service_callout_example .
$docker login
$docker tag sg-dynamic-forwarding:latest <dockerhub_id>/sg-dynamic-forwarding:v1
$docker push <dockerhub_id>/sg-dynamic-forwarding:v1
Summary
Dynamic Forwarding represents a massive leap forward for Google Cloud Load Balancing flexibility. By leveraging Envoy-based External Processors (extproc) and Service Extensions, developers can bypass traditional scaling and routing limits. Instead of relying solely on static backend services and URL maps, you can programmatically dictate exactly which IP address a request should be forwarded to based on custom headers, real-time data, or complex affinity rules. Whether you are scaling to hundreds of thousands of endpoints or provisioning compute resources dynamically at request time, Dynamic Forwarding allows you to build a completely custom, control plane extensions for your scalability needs.
If you need more detailed information please consult documentation:
