VPC Networks allow your Workers to access any service in your private network without pre-registering individual hosts or ports. You can bind to a specific Cloudflare Tunnel to reach any service behind that tunnel, or bind to Cloudflare Mesh to reach any Mesh node, client device, subnet route or hostname route announced through Cloudflare Tunnel or Mesh, or destination reachable through a Cloudflare WAN on-ramp (GRE, IPsec, or CNI).
At runtime, the URL you pass to fetch() or the address you pass to connect() determines the destination — any hostname or IP address reachable through the bound Cloudflare Tunnel or through Cloudflare Mesh. Use fetch() for HTTP traffic, and connect() for raw TCP connections (Redis, MQTT, custom protocols, and other non-HTTP services). This differs from VPC Services, which require you to create a separate binding for each target host and port combination.
Reference a specific Cloudflare Tunnel directly by its UUID:
{
"vpc_networks": [
{
"binding": "MY_VPC",
"tunnel_id": "550e8400-e29b-41d4-a716-446655440000",
"remote": true
}
]
}[[vpc_networks]]
binding = "MY_VPC"
tunnel_id = "550e8400-e29b-41d4-a716-446655440000"
remote = trueThe remote flag must be set to true to enable remote bindings during local development.
Cloudflare Mesh (formerly WARP Connector) connects your services, devices, and Workers through Cloudflare's global network. When you bind a Worker using network_id: "cf1:network", the binding uses your account's default virtual network. Your Worker can reach:
- Any Mesh node or client device in your account
- Subnet routes and hostname routes announced through Cloudflare Tunnel or Cloudflare Mesh
- Destinations reachable through Cloudflare WAN on-ramps (GRE, IPsec, and CNI)
- Public Internet destinations through Cloudflare Gateway — with your existing Zero Trust traffic policies enforced and traffic logged in DNS, HTTP, and Network logs
All of this without specifying a particular Cloudflare Tunnel UUID.
Use cf1:network when:
- Your Workers need to reach private services across multiple Cloudflare Tunnels, Mesh nodes, or Cloudflare WAN on-ramps
- You want to access your entire private network from a Worker without managing individual Cloudflare Tunnel bindings
- Your private network topology may change (new connections, new nodes, new routes) and you do not want to update Worker configuration each time
- You want Worker egress to public destinations to flow through Cloudflare Gateway for policy enforcement and visibility
Gateway drops HTTP traffic from Workers VPC if it matches a Forward policy (beta). Exclude Workers VPC traffic from your Forward policies.
Bind to Cloudflare Mesh using network_id: "cf1:network":
{
"vpc_networks": [
{
"binding": "MY_VPC",
"network_id": "cf1:network",
"remote": true
}
]
}[[vpc_networks]]
binding = "MY_VPC"
network_id = "cf1:network"
remote = trueSet network_id to cf1:vnet:<VNET_UUID> to access services through a specific configured virtual network. Separate bindings can select different virtual networks, including when those networks use overlapping IP ranges.
For example, the following bindings select production and staging virtual networks:
{
"vpc_networks": [
{
"binding": "PRODUCTION",
"network_id": "cf1:vnet:<PRODUCTION_VNET_UUID>",
"remote": true
},
{
"binding": "STAGING",
"network_id": "cf1:vnet:<STAGING_VNET_UUID>",
"remote": true
}
]
}[[vpc_networks]]
binding = "PRODUCTION"
network_id = "cf1:vnet:<PRODUCTION_VNET_UUID>"
remote = true
[[vpc_networks]]
binding = "STAGING"
network_id = "cf1:vnet:<STAGING_VNET_UUID>"
remote = trueEach binding can reach a different backend at the same private IP address:
export default {
async fetch(_request, env) {
const production = await env.PRODUCTION.fetch("http://10.0.1.50/status");
const staging = await env.STAGING.fetch("http://10.0.1.50/status");
return Response.json({
production: await production.text(),
staging: await staging.text(),
});
},
};export default {
async fetch(_request: Request, env: Env): Promise<Response> {
const production = await env.PRODUCTION.fetch(
"http://10.0.1.50/status",
);
const staging = await env.STAGING.fetch("http://10.0.1.50/status");
return Response.json({
production: await production.text(),
staging: await staging.text(),
});
},
} satisfies ExportedHandler<Env>;Access any HTTP service in your network at runtime using fetch():
export default {
async fetch(request: Request, env: Env) {
// Access a service by private IP
const response = await env.MY_VPC.fetch("http://10.0.1.50/data");
// Access another service on a different port
const dbResponse = await env.MY_VPC.fetch("http://10.0.5.42:5432");
return response;
},
};When a VPC Network cannot establish a connection to your target service, fetch() throws an exception.
Open raw TCP connections to any private destination using connect(). This is useful for non-HTTP protocols like Redis, Memcached, MQTT, or custom binary protocols:
export default {
async fetch(request: Request, env: Env) {
// Open a TCP connection to a private Redis instance
const socket = await env.MY_VPC.connect("10.0.1.50:6379");
// Write a Redis PING command
const writer = socket.writable.getWriter();
await writer.write(new TextEncoder().encode("PING\r\n"));
await writer.close();
return new Response(socket.readable);
},
};When a VPC Network cannot establish a TCP connection, connect() throws an exception.
Traffic sent through a VPC Network binding appears in Gateway network logs with Workers VPC as the Traffic Source.
Zero Trust Network Session Logs also record which Worker started each session. Workers VPC sessions report OnrampType as WORKERS_VPC, SourceName as the Worker name, and SourceID as the Worker's stable identifier. Use these fields to audit which Worker reached a private service, or to debug a failed connection.
VPC Networks and VPC Services both connect Workers to private infrastructure, but they make different trade-offs.
- Use VPC Services when you have a known set of targets and want each binding scoped to a specific host and port.
- Use VPC Networks when you need broad access — an entire Cloudflare Tunnel or routes in a virtual network — and want the URL in your
fetch()call to control routing at runtime.
The following table summarizes the differences:
| Feature | VPC Networks | VPC Services |
|---|---|---|
| Scope | Cloudflare Tunnel or routes in a virtual network | Specific host + port |
| Configuration | tunnel_id, cf1:network, or cf1:vnet:<UUID> |
service_id |
| Protocols | HTTP (fetch()) and TCP (connect()) |
HTTP (fetch()) or TCP (via Hyperdrive) |
| Service registration | Not required | Required for each target |
| Use when | Dynamic discovery or access to routes in a virtual network | Fixed, cataloged services |
- Set up Cloudflare Tunnel
- Set up Cloudflare Mesh
- Set up Cloudflare WAN
- Try the Connect Workers to Cloudflare Mesh example
- Learn about the Workers Binding API