Using network_id: 'cf1:network' with remote: true
A VPC Network binding with network_id: 'cf1:network' and remote: true gives a Worker access to private destinations outside the local VPC scope. In practice, that means one binding can reach services exposed through Cloudflare Tunnel, Cloudflare Mesh, subnet routes, hostname routes, and destinations attached through Cloudflare WAN on-ramps.
The useful part is that the binding stays fixed while the destination changes at runtime. The Worker does not need a different binding for each path type. It needs a destination URL that resolves to the service you want, then Cloudflare handles the private reachability behind the scenes.
Runtime destination selection with fetch() and the private network binding
fetch() is the switch. The URL passed into it decides which private destination gets the request, while the binding points at the private network itself.
A config like this is the pattern:
jsonc
{
“vpcnetworks”: [
{
“binding”: “PRIVATENETWORK”,
“network_id”: “cf1:network”,
“remote”: true
}
]
}
And at runtime:
js
const response = await env.PRIVATE_NETWORK.fetch(‘http://10.50.0.100:8080/api’);
That keeps the app logic boring, which is usually a good sign. The Worker can pick a private target by URL without swapping bindings or branching on transport type.
Point the binding at PRIVATE_NETWORK and keep the URL as the switch
That model only works cleanly if the URL really identifies the service boundary. If the URL is vague, the routing gets vague too. A binding to PRIVATE_NETWORK is not a routing policy by itself, it is a reachability handle. The request still needs a destination that makes sense at runtime.
What changes when the target sits behind WAN on-ramps
WAN on-ramp destinations add a return-path problem. The request may enter through Cloudflare Workers VPC, but the reply has to get back through the same private connectivity chain. For WAN-connected services, that usually means the return traffic needs to map back across GRE, IPsec, or CNI without the application trying to understand any of it.
That boundary matters because stateful flows expect symmetry. If a reply leaves by a different route from the one that carried the request in, the session can fail in ways that look annoyingly ordinary: timeouts, broken handshakes, or traffic that works once and then sulks.
The practical rule is simple. Keep the Worker logic focused on destination choice, and keep the network side responsible for reply routing. Do not split the application into separate code paths just because one service lives behind an IPsec on-ramp and another sits behind a Tunnel route.
Map reply traffic back through GRE, IPsec, and CNI without splitting the app logic
The routing layout has to preserve return traffic for the private service, not merely reach it. GRE, IPsec, and CNI each sit in the path differently, but the Worker should not care which one is in play. The binding and destination URL stay the same; the network plumbing does the ugly bit.
That is the part to check first when a WAN target behaves strangely. Reachability is one thing. Reply routing is the bit that decides whether the flow survives beyond the first packet.
Tuning the boundary before you ship it
Treat the config boundary as a network contract, not a nice-to-have. remote: true broadens reach, which is useful, but it also makes it easier to send traffic into a path you have not finished wiring back out of. The failure often shows up only when the destination answers.
Test the return path with real traffic, not just the declared policy. A service that accepts a request and then loses the reply path is exactly the sort of thing that looks fine in a diagram and broken at 3 a.m.
Test the dump path, not the policy wording
Run an actual request through the binding and watch where the reply goes. If the service sits behind a WAN on-ramp, check that the reverse path is mapped correctly across GRE, IPsec, or CNI. If it is not, the app can stay unchanged while the flow still falls over.



