workerd error: Worker exceeded memory limit, isolate restarted
Fixes workerd "Worker exceeded memory limit" crashes where the isolate is restarted mid-request. Use it when a worker dies handling large files, big JSON bodies, or growing in-memory caches. Key trigger: the crash correlates with payload size or traffic volume, and the code buffers whole bodies with arrayBuffer, text, or json before processing.
TL;DR: Stop holding entire bodies in memory. Stream request and response bodies through the worker instead of buffering them, cap any in-memory caches, and move large blobs to R2. Each isolate gets a fixed memory ceiling and buffering past it restarts the isolate.
workerd error: Worker exceeded memory limit, isolate restarted- Confirm the pattern with tail. Run
wrangler tailand reproduce with a large payload.
Expected: the memory limit line appears, the isolate restarts, and the request never completes.
- Find the buffering. Search the handler for full-body reads: arrayBuffer, text, json, or Blob assembly over large inputs, plus any global Map or array that grows per request.
Expected: you can point at the lines that hold the whole payload at once.
- Stream instead of buffering. Pipe the body through instead of reading it whole:
return new Response(request.body, {
headers: { "content-type": "application/octet-stream" },
});Expected: memory stays flat regardless of body size because bytes flow through without accumulating.
- For transforms you must do in the worker, process the stream in chunks with a TransformStream and never assemble the full body.
Expected: tail shows no memory restarts even for your largest real payload.
- Move big blobs out of the worker entirely. Upload straight to R2 with a presigned URL, or store the artifact in R2 and have the worker pass only a key around.
Expected: the worker only ever handles small metadata, never the blob.
- Cap caches. If you keep a global Map cache, give it a max size and evict the oldest entries.
Expected: memory under sustained traffic plateaus instead of climbing.
Use this when
- the worker restarts with the memory limit error on large uploads, downloads, or exports
- buffering a full body with arrayBuffer, text, or json precedes the crash
- a global cache or accumulator grows with traffic and the worker dies after hours of uptime
- image, video, or archive proxying passes through the worker
Not for this skill when
- the error is about CPU time rather than memory
- the crash happens at startup before any request (that is bundle or init size)
- the failure is a subrequest or connection limit
- bodies are small but the worker still dies (look for a leak in timers or listeners instead)
Variant phrasings
- Cloudflare Worker out of memory isolate restarted
- "Worker exceeded memory limit" workerd
- worker crashes on large file upload
- isolate restarted large response worker
- memory limit proxying files through Cloudflare Worker
Why it happens
Each worker isolate gets a fixed memory ceiling (128 MB). Reading a whole body into memory with arrayBuffer or json allocates the entire payload at once, and globals persist across requests in the same isolate, so repeated large requests or a growing cache eventually cross the ceiling. workerd then restarts the isolate to protect the host, killing the in-flight request. Streaming keeps only a small window of bytes resident, which is why it fixes the crash regardless of payload size.
Edge cases
- Globals persist per isolate, not per request. A cache that looks fine in a single-request test can still grow unbounded across thousands of requests.
- Concatenating chunks into one string or buffer reintroduces the same problem. Process chunks and emit them, do not accumulate.
- JSON.parse needs the whole string, so "stream the JSON" still buffers unless you switch to incremental parsing or move parsing off the request path.
- R2 reads return streams. Keep them as streams all the way to the response instead of buffering.
- Multiple concurrent large requests share the isolate ceiling. Test with realistic concurrency, not just one big request at a time.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_5r5neJxYN0aBqpOMoj1z5g
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.