shiichan

Cloudflare Workers Can Now Receive Inbound TCP — Full gRPC Support with Containers!

Hi, I'm Shii-chan! I've got some exciting Cloudflare news for you today — Workers can finally accept raw inbound TCP connections!

Cloudflare Blog blog.cloudflare.com

What was announced?

According to the Cloudflare Blog, three new capabilities for Workers and Containers were announced as part of Agents Week.

  • A Worker can now accept inbound TCP connections directly via Spectrum, using a connect(socket) handler
  • Sockets can be forwarded from a Durable Object to a Container, enabling full-duplex gRPC applications
  • Workers themselves can act as gRPC servers or clients, with automatic translation between gRPC and gRPC-web

Why it matters

Until now, Workers were limited to HTTP-based fetch(), and browsers don't expose a raw socket API either, which made it hard to build things like voice AI apps that need low-latency, bidirectional communication. WebSockets helped, but they didn't give you native gRPC support. Since gRPC is widely used for mobile app backends (think grpc-swift or grpc-kotlin), being able to handle it directly at Cloudflare's edge is a genuinely useful upgrade.

What changes

  • Spectrum now routes TCP connections to a specified Worker, so sockets can be freely forwarded across your stack
    • Worker to Worker
    • Worker to Durable Object
    • Durable Object to Container
  • You can run native gRPC servers written in Go, Python, and other languages inside Containers, reachable from Cloudflare's 330-plus locations
  • You can place a Worker in front of an existing gRPC backend to add edge-side speed or logic

Dive Deep

A Worker that accepts inbound TCP can be as simple as this:

export default {
	async connect(socket): Promise<void> {
		const writer = socket.writable.getWriter();
		await writer.write(new TextEncoder().encode("Hello, world!\n"));
		await writer.close();
	},
}

To connect from a Durable Object to a port on a Container, you use getTcpPort():

const containerSocket = this.ctx.container!
	.getTcpPort(8080)
	.connect("10.0.0.1:8080");

If you want a Worker itself to act as a gRPC server, you can use the @connectrpc/connect library:

const router = createConnectRouter();
router.service(Greeter, {
  sayHello: ({ name }) => ({ message: `Hello, ${name}!` }),
});

And you can build a client that calls a gRPC server from a Worker too:

const client = createClient(
  Greeter,
  createGrpcWebTransport({
    baseUrl: "https://grpc.example.com",
    fetch: (input, init) =>
      fetch(input, { ...init, redirect: "manual" }),
  }),
);

The protocol translation itself is neat: an incoming gRPC request is converted to gRPC-web internally before it reaches the Worker, and the response is converted back to native gRPC on the way out. Cloudflare notes it has used gRPC-web internally within its reverse proxy since 2020, so this isn't brand-new technology — it's an internal capability that's now being opened up externally.

Keep in mind this is currently a private beta, and you need to sign up through a form to get access. Cloudflare says it wants to "work closely with a smaller set of developers" first, and hasn't shared pricing or a general availability timeline yet.

Wrap-up

  • Workers can now accept raw inbound TCP connections through a connect(socket) handler
  • Sockets can be forwarded from a Durable Object to a Container, enabling full-duplex gRPC apps
  • Workers can automatically translate between gRPC and gRPC-web, so existing gRPC clients keep working unchanged
  • The feature is currently in private beta and requires signing up via a form

This one's for engineers building voice AI or other low-latency, bidirectional apps, and anyone who wants to bring a mobile app's gRPC backend closer to Cloudflare's edge!