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 globalOutbound or 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 every fetch() in that worker to resolve through the service named my-proxy. This can point to an external proxy, a local socket, or another internal endpoint.

  • Null outbound — Specifying globalOutbound = null completely disables outbound networking. Any attempt to call fetch() throws a DOMDataCloneError, as implemented in src/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 the allow list.

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 globalOutbound routes traffic through the built-in "internet" service.
  • Service designators — Set globalOutbound to a string (service name), null (disable), or a worker binding (proxy).
  • Network rules — Define allow and deny lists in the service's network block 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 = null to completely disable outbound requests and throw DOMDataCloneError on fetch().

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →