How to Configure Global Outbound and Network Services in Workerd
You configure global outbound and network services in workerd by setting the globalOutbound field in your worker's Cap'n Proto configuration to a service name, null, or another worker binding, while defining network access rules in the service's network block with allow and deny lists.
Workerd, the open-source JavaScript/Wasm runtime from Cloudflare, routes all outbound network traffic—such as fetch() requests—through a configurable global outbound service. This architecture lets you control internet access, implement proxy layers, or completely sandbox workers by editing the Cap'n Proto schemas in src/workerd/server/workerd.capnp.
Understanding the Global Outbound Selector
The globalOutbound field in a worker definition determines where outbound traffic resolves. According to src/workerd/server/workerd.capnp (lines 998–1002), this field accepts a ServiceDesignator that can reference three distinct targets:
-
Default "internet" service — If you omit
globalOutboundor leave it unset, the runtime automatically uses the built-in service named"internet". This service must be defined elsewhere in your configuration and typically provides unrestricted public internet access. -
Custom service binding — Setting
globalOutbound = "my-proxy"forces everyfetch()in that worker to resolve through the service namedmy-proxy. This can point to an external proxy, a local socket, or another internal endpoint. -
Null outbound — Specifying
globalOutbound = nullcompletely disables outbound networking. Any attempt to callfetch()throws aDOMDataCloneError, as implemented insrc/workerd/server/server.c++around line 4074. -
Worker-to-Worker proxy — The selector can reference another worker binding, allowing a worker to act as a middleware proxy for all outbound requests from its parent.
Network Access Control in Service Definitions
Every service in workerd can include a network block that defines host-level allow and deny lists. In src/workerd/server/workerd.capnp (around lines 800–820), the Service struct contains these fields:
allow— An array of hostnames or IP addresses permitted for outbound connections. An empty list blocks all traffic.deny— An array of hosts explicitly blocked, even if they appear in theallowlist.
If both fields are omitted, the service grants unrestricted outbound access. The test case KJ_TEST("Server: network outbound with allow/deny") in src/workerd/server/server-test.c++ (lines 3289–3297) validates that requests to allowed hosts succeed while denied hosts return errors.
Practical Configuration Examples
Default Internet Outbound
The minimal configuration relies on the implicit "internet" service. Define an unrestricted network service and a worker without an explicit globalOutbound field:
services = [
(name = "internet", network = (allow = []))
]
sockets = [
(name = "main", address = "0.0.0.0:8080", service = "my-worker")
]
workers = [
(name = "my-worker", entrypoint = "worker.js")
]
Because globalOutbound is omitted, the worker inherits the default "internet" service and can reach any public host.
Custom Proxy Service
Route all outbound traffic through a specific proxy endpoint with restricted egress:
services = [
(name = "internet", network = (allow = [])),
(name = "my-proxy",
network = (allow = ["api.example.com"]),
external = (address = "10.0.0.5", http = (style = proxy))
)
]
workers = [
(name = "app",
entrypoint = "app.js",
globalOutbound = "my-proxy"
)
]
Here, the app worker sends every fetch() to the proxy at 10.0.0.5, and the proxy's allow list restricts egress to api.example.com only.
Worker-to-Worker Proxy
Use another worker as the global outbound to implement custom logic such as caching or authentication:
workers = [
(name = "outbound-proxy",
entrypoint = "proxy.js"
# Inherits default "internet" for its own outbound
),
(name = "frontend",
entrypoint = "frontend.js",
globalOutbound = (name = "outbound-proxy", worker = "outbound-proxy")
)
]
The frontend worker delegates all fetch() calls to the outbound-proxy worker, which can inspect, modify, or log requests before forwarding them.
Disabling Outbound Traffic
Create a fully sandboxed worker that cannot make network requests:
workers = [
(name = "sandboxed",
entrypoint = "sandbox.js",
globalOutbound = null
)
]
Any fetch() invocation inside sandboxed immediately throws a DOMDataCloneError.
Fine-Grained Allow and Deny Lists
Implement precise host-level restrictions using both lists:
services = [
(name = "restricted",
network = (
allow = ["private", "api.internal"],
deny = ["bad.example.com"]
)
)
]
workers = [
(name = "service",
entrypoint = "service.js",
globalOutbound = "restricted"
)
]
This configuration allows connections to private and api.internal, but explicitly blocks bad.example.com even if it resolves within the allowed ranges.
Runtime Implementation and Inheritance
The runtime wiring resides in src/workerd/server/server.c++ (lines 3998–4400), where the FutureSubrequestChannel globalOutbound handles request dispatch. When a worker loads another worker via the Worker API, inheritance rules defined in src/workerd/api/worker-loader.c++ (lines 115–165) apply: child workers inherit the parent's globalOutbound selector unless their own configuration explicitly overrides it or sets it to null.
Using Outbound Services from JavaScript
Regardless of the backend configuration, JavaScript code uses the standard fetch() API. The runtime automatically routes requests through the configured global outbound channel:
export default {
async fetch(request, env, ctx) {
// Automatically routed through the worker's globalOutbound service
const resp = await fetch("https://api.example.com/data");
return resp;
}
};
If the worker's globalOutbound is set to null, this call throws before the network stack is reached.
Summary
- Default behavior — Omitting
globalOutboundroutes traffic through the built-in"internet"service. - Service designators — Set
globalOutboundto a string (service name),null(disable), or a worker binding (proxy). - Network rules — Define
allowanddenylists in the service'snetworkblock to whitelist or blacklist specific hosts. - Inheritance — Child workers inherit the parent's global outbound unless explicitly overridden, as handled in
worker-loader.c++. - Sandboxing — Set
globalOutbound = nullto completely disable outbound requests and throwDOMDataCloneErroronfetch().
Frequently Asked Questions
What happens if I omit the globalOutbound field in my worker configuration?
The runtime defaults to the service named "internet". According to src/workerd/server/workerd.capnp, the globalOutbound @6 field has a default value of "internet", meaning the worker automatically uses that service for all outbound connections unless you specify otherwise.
How do allow and deny lists interact in network service definitions?
The deny list takes precedence over the allow list. If a host appears in both lists, the connection is blocked. If only allow is specified, only those hosts are reachable; if allow is empty, no outbound traffic is permitted. When both fields are absent, the service allows unrestricted access.
Can I use another Worker to process all outbound fetch requests?
Yes. Set globalOutbound to a worker binding designator like (name = "proxy", worker = "proxy-worker"). This routes every fetch() through the specified worker, which can implement custom logic before forwarding the request to its own global outbound service.
What error does workerd throw when globalOutbound is set to null?
When globalOutbound = null, any attempt to call fetch() throws a DOMDataCloneError. This behavior is explicitly implemented in src/workerd/server/server.c++ to signal that the worker has no outbound channel configured.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →