Streaming Workers No Longer Bill for Idle Time
Cloudflare has rolled out a runtime optimization that cuts the amount of time a Worker is considered "in use" during response streaming. The change applies automatically to all Unbound Workers, meaning many users will see a noticeable reduction in their bills for gigabyte-seconds of duration-memory.
Why the Change Was Needed
Workers are frequently used as HTTP proxies, where JavaScript rewrites requests and responses as they pass through. In many cases, however, the Worker only manipulates headers and lets the response body stream through untouched from the origin to the client. In those situations, the Worker's application code has completed its work as soon as the response headers are sent — but the runtime previously kept the Worker marked as active until the last body byte had streamed past.
Under the Workers Unbound pricing model, billing is based on the time a Worker is in use, measured as duration-memory. That meant users were paying for streaming time during which no application logic was actually executing.
Marking Workers Idle During Streaming
On December 15–16, Cloudflare updated how the runtime handles requests that stream responses without body modification. Once the response headers are returned, the system now marks the Worker as idle. The runtime no longer needs to keep request state in memory; only the low-level native sockets are tracked while the bytes are pumped through. A Worker in this state could even be evicted before the stream ends, though that would be unlikely unless the stream runs for an unusually long time.

The practical effect: the average time a Worker is considered "in use" per request has dropped by roughly 70%. The actual improvement varies widely by workload — some Workers see no benefit, while others see an even larger reduction. The optimization is invisible to applications and external observers; behavior is identical to before. Only the billing impact differs.
Beyond Simple Proxying
The same idle-time logic extends to several other common patterns where a Worker arranges for data to flow without per-message or per-chunk application code.
WebSocket proxying. Once a Worker sets up a WebSocket proxy, it is no longer considered in use while proxying, as long as the Worker code isn't handling individual messages. This applies to regular stateless Workers, but not to Durable Objects, which are not typically used for proxying.
export default {
async fetch(request: Request) {
//Do anything before
const upgradeHeader = request.headers.get('Upgrade')
if (upgradeHeader || upgradeHeader === 'websocket') {
return await fetch(request)
}
//Or with other requests
}
}
Cache reads. Returning a response from a cache.match call marks the Worker idle as soon as response headers are sent.
export default {
async fetch(request: Request) {
let response = await caches.default.match('https://example.com')
if (response) {
return response
}
// get/create response and put into cache
}
}
KV streaming. The same applies when streaming a value from Workers KV. The nuance: the value must be fetched as a ReadableStream so it can be passed directly into a Response. Retrieving a KV value as a string or JSON object and then constructing a response will not trigger the optimization.
interface Env {
MY_KV_NAME: KVNamespace
}
export default {
async fetch(request: Request, env: Env) {
const readableStream = await env.MY_KV_NAME.get('hello_world.pdf', { type: 'stream' })
if (readableStream) {
return new Response(readableStream, { headers: { 'content-type': 'application/pdf' } })
}
},
}
Automatic Savings
Users already on the Unbound plan did not need to take any action — their bills simply dropped. For those exploring Unbound, Cloudflare notes that egress fees have also been removed recently, making the per-usage pricing model more attractive for complex workloads. The change is part of a broader push to make Workers cheaper, alongside upcoming features like Service Bindings and continued performance work.



